go / gzip

I use a simple middleware to gzip HTTP responses, with a sync.Pool to reuse gzip writers.

The pattern

package gzip

import (
	"compress/gzip"
	"io"
	"net/http"
	"strings"
	"sync"
)

var pool = sync.Pool{
	New: func() any {
		w, _ := gzip.NewWriterLevel(nil, gzip.BestSpeed)
		return w
	},
}

func Middleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
			next.ServeHTTP(w, r)
			return
		}

		w.Header().Set("Content-Encoding", "gzip")
		w.Header().Add("Vary", "Accept-Encoding")

		gz := pool.Get().(*gzip.Writer)
		gz.Reset(w)
		defer func() {
			gz.Close()
			pool.Put(gz)
		}()

		next.ServeHTTP(&gzipResponseWriter{gz: gz, ResponseWriter: w}, r)
	})
}

type gzipResponseWriter struct {
	gz *gzip.Writer
	http.ResponseWriter
}

func (w *gzipResponseWriter) Write(b []byte) (int, error) {
	return w.gz.Write(b)
}

Key points:

Usage

mux := http.NewServeMux()
mux.HandleFunc("/api/data", handleData)

http.ListenAndServe(":8080", gzip.Middleware(mux))

When to use

When not to use

Streaming caveat

This middleware buffers the entire response before sending. For streaming responses, you'd need to call gz.Flush() periodically, but that reduces compression ratio.

← All articles