Roll your own gzip-encoded HTTP handler

While I’m sure we’ll have “out of the box” support for gzip-compressed HTTP responses pretty soon, it’s quite easy to do it yourself.

package main

import (
    "compress/gzip"
    "http"
    "io"
    "os"
    "strings"
)

type gzipResponseWriter struct {
    io.Writer
    http.ResponseWriter
}

func (w gzipResponseWriter) Write(b []byte) (int, os.Error) {
    return w.Writer.Write(b)
}

func makeGzipHandler(fn http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
            fn(w, r)
            return
        }
        w.Header().Set("Content-Encoding", "gzip")
        gz, err := gzip.NewWriter(w)
        if err != nil {
            http.Error(w, err.String(), http.StatusInternalServerError)
            return
        }
        defer gz.Close()
        fn(gzipResponseWriter{Writer: gz, ResponseWriter: w}, r)
    }
}

func handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("This is a test."))
}

func main() {
    http.ListenAndServe(":8081", makeGzipHandler(handler))
}

One nicety this demonstrates is how easy it is to substitute a subset of one interface’s methods with those of another. The gzipResponseWriter has two embedded values. The methods from http.ReponseWriter that don’t conflict with the io.Writer are simply inherited. But because the http.ResponseWriter and io.Writer both have a Write method, we must write a shim Write method to pass through to the io.Writer.