go
56 lines · 7 steps
How gzip HTTP middleware works in Go
A net/http middleware that compresses responses on the fly while pooling gzip writers to avoid per-request allocations.
Explained by
highlit
1package middleware
2
3import (
4 "compress/gzip"
5 "io"
6 "net/http"
7 "strings"
8 "sync"
9)
10
11var gzipPool = sync.Pool{
12 New: func() any {
13 return gzip.NewWriter(io.Discard)
14 },
15}
16
17type gzipResponseWriter struct {
18 http.ResponseWriter
19 gw *gzip.Writer
20}
21
22func (w *gzipResponseWriter) Write(b []byte) (int, error) {
23 if w.Header().Get("Content-Type") == "" {
24 w.Header().Set("Content-Type", http.DetectContentType(b))
25 }
26 return w.gw.Write(b)
27}
28
29func (w *gzipResponseWriter) Flush() {
30 _ = w.gw.Flush()
31 if f, ok := w.ResponseWriter.(http.Flusher); ok {
32 f.Flush()
33 }
34}
35
36func Gzip(next http.Handler) http.Handler {
37 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
38 if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
39 next.ServeHTTP(w, r)
40 return
41 }
42
43 gw := gzipPool.Get().(*gzip.Writer)
44 gw.Reset(w)
45 defer func() {
46 _ = gw.Close()
47 gzipPool.Put(gw)
48 }()
49
50 w.Header().Set("Content-Encoding", "gzip")
51 w.Header().Add("Vary", "Accept-Encoding")
52 w.Header().Del("Content-Length")
53
54 next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, gw: gw}, r)
55 })
56}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Embedding http.ResponseWriter lets you override just Write while inheriting the rest of the interface for free.
- 2sync.Pool recycles expensive objects like gzip writers so high-traffic handlers avoid constant allocation.
- 3Compression middleware must negotiate via Accept-Encoding and fix up headers like Content-Encoding and Content-Length.
Related explainers
go
package events import ( "net/http"
Two-pass JSON dispatch in Gin
polymorphic-json
request-binding
validation
Intermediate
8 steps
go
package handlers type ListFilters struct { Status string `form:"status" binding:"omitempty,oneof=active archived all"`
Cross-field query validation in Gin
validation
struct-tags
query-binding
Intermediate
9 steps
rust
use std::collections::HashMap; #[derive(Debug)] pub struct RequestHead {
Parsing an HTTP request head in Rust
parsing
error-handling
iterators
Intermediate
9 steps
typescript
import { CallHandler, ExecutionContext, Injectable,
Recording HTTP metrics with a NestJS interceptor
interceptor
observability
prometheus
Intermediate
5 steps
go
package editor import ( "context"
How a debouncer coalesces bursts in Go
debounce
concurrency
timers
Intermediate
8 steps
go
package humanize import ( "fmt"
Parsing human-readable byte sizes in Go
parsing
regex
lookup-table
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-gzip-http-middleware-works-in-go-explained-go-6b57/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.