Skip to content

Instantly share code, notes, and snippets.

@the42
Created March 2, 2012 07:34
Show Gist options
  • Save the42/1956518 to your computer and use it in GitHub Desktop.
Save the42/1956518 to your computer and use it in GitHub Desktop.
GZip encoding for GO V1 using custom responsewriter
package main
import (
"compress/gzip"
"io"
"net/http"
"strings"
)
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (w gzipResponseWriter) Write(b []byte) (int, 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 := gzip.NewWriter(w)
defer gz.Close()
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
fn(gzr, r)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("This is a test."))
}
func main() {
http.ListenAndServe(":1113", makeGzipHandler(handler))
}
@wathiede
Copy link

I had problems with this for responses that didn't explicitly set Content-Type. They were being auto-detected by the default ResponseWriter as 'Content-Type: application/x-gzip'. A simple fix is to define Write as:

func (w gzipResponseWriter) Write(b []byte) (int, error) {
    if "" == w.Header().Get("Content-Type") {
        // If no content type, apply sniffing algorithm to un-gzipped body.
        w.Header().Set("Content-Type", http.DetectContentType(b))
    }
    return w.Writer.Write(b)
}

@dbaroncelli
Copy link

In case you just want to encode a JSON (compressed or not, based on a flag):

func handler(w http.ResponseWriter, r *http.Request) {
    response := (THIS IS MY RESPONSE STRUCT)
    w.Header().Set("Content-Type", "application/json")
    if settings.COMPRESS_RESPONSE { // this is a global flag I defined
        w.Header().Set("Content-Encoding", "gzip")
        gz := gzip.NewWriter(w)
        json.NewEncoder(gz).Encode(response)
        gz.Close()
    } else {
        json.NewEncoder(w).Encode(response)
    }
}

@yonderblue
Copy link

I am very curious about these two parts:

type gzipResponseWriter struct {
    io.Writer
    http.ResponseWriter
}
gzipResponseWriter{Writer: gz, ResponseWriter: w}

So the two interfaces embedded in the struct (not so common?) make it satisfy all the http.ResponseWriter methods, but isn't the io.Writer method exactly the same as the http.ResponseWriter's since it IS an io.Writer?
Also would you mind sharing where I can get more details in the spec about referencing an embedded anonymous field by the type name?

@dradtke
Copy link

dradtke commented Jul 25, 2015

A struct field never has no name at all. In the case of embedding, it re-uses the type name as the field name, which becomes really apparent when accessing embedded struct fields and reflection.

@yonderblue
Copy link

Since a stackoverflow article links here and likey gets some traffic, I figured I would warn people that the .Close on the gzip writer writes some kind of footer, and if the response from the handler needs to be a 304 or 204 etc then that will break.
As far as I can see in the spec http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7 its ok to leave the Content-Encoding header in the response but there should not be any body.

@optimality
Copy link

Also, the error from gz.Close() isn't being checked, which leads to mysterious failures like golang/go#14975 (comment). If you use this, make sure to check that error.

@erikdubbelboer
Copy link

A added a new commit to my fork that fixes some issues: https://gist.github.com/erikdubbelboer/7df2b2b9f34f9f839a84

The issue from @optimality about not handling gz.Close errors still stands as everyone should handle this in their own way since http.HandlerFunc can't return errors.

@hrieke
Copy link

hrieke commented Jun 29, 2018

License?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment