github.com/brycereitano/goa@v0.0.0-20170315073847-8ffa6c85e265/middleware/gzip/middleware.go (about) 1 package gzip 2 3 import ( 4 "compress/gzip" 5 "io/ioutil" 6 "net/http" 7 "strings" 8 "sync" 9 10 "golang.org/x/net/context" 11 12 "github.com/goadesign/goa" 13 ) 14 15 // These compression constants are copied from the compress/gzip package. 16 const ( 17 encodingGzip = "gzip" 18 19 headerAcceptEncoding = "Accept-Encoding" 20 headerContentEncoding = "Content-Encoding" 21 headerContentLength = "Content-Length" 22 headerContentType = "Content-Type" 23 headerVary = "Vary" 24 headerSecWebSocketKey = "Sec-WebSocket-Key" 25 ) 26 27 // gzipResponseWriter wraps the http.ResponseWriter to provide gzip 28 // capabilities. 29 type gzipResponseWriter struct { 30 http.ResponseWriter 31 gzw *gzip.Writer 32 } 33 34 // Write writes bytes to the gzip.Writer. It will also set the Content-Type 35 // header using the net/http library content type detection if the Content-Type 36 // header was not set yet. 37 func (grw gzipResponseWriter) Write(b []byte) (int, error) { 38 if len(grw.Header().Get(headerContentType)) == 0 { 39 grw.Header().Set(headerContentType, http.DetectContentType(b)) 40 } 41 return grw.gzw.Write(b) 42 } 43 44 // handler struct contains the ServeHTTP method 45 type handler struct { 46 pool sync.Pool 47 } 48 49 // Middleware encodes the response using Gzip encoding and sets all the appropriate 50 // headers. If the Content-Type is not set, it will be set by calling 51 // http.DetectContentType on the data being written. 52 func Middleware(level int) goa.Middleware { 53 gzipPool := sync.Pool{ 54 New: func() interface{} { 55 gz, err := gzip.NewWriterLevel(ioutil.Discard, level) 56 if err != nil { 57 panic(err) 58 } 59 return gz 60 }, 61 } 62 return func(h goa.Handler) goa.Handler { 63 return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) (err error) { 64 // Skip compression if the client doesn't accept gzip encoding, is 65 // requesting a WebSocket or the data is already compressed. 66 if !strings.Contains(req.Header.Get(headerAcceptEncoding), encodingGzip) || 67 len(req.Header.Get(headerSecWebSocketKey)) > 0 || 68 req.Header.Get(headerContentEncoding) == encodingGzip { 69 return h(ctx, rw, req) 70 } 71 72 // Set the appropriate gzip headers. 73 resp := goa.ContextResponse(ctx) 74 resp.Header().Set(headerContentEncoding, encodingGzip) 75 resp.Header().Set(headerVary, headerAcceptEncoding) 76 77 // Retrieve gzip writer from the pool. Reset it to use the ResponseWriter. 78 // This allows us to re-use an already allocated buffer rather than 79 // allocating a new buffer for every request. 80 gz := gzipPool.Get().(*gzip.Writer) 81 82 // Get the original http.ResponseWriter 83 w := resp.SwitchWriter(nil) 84 // Reset our gzip writer to use the http.ResponseWriter 85 gz.Reset(w) 86 87 // Wrap the original http.ResponseWriter with our gzipResponseWriter 88 grw := gzipResponseWriter{ 89 ResponseWriter: w, 90 gzw: gz, 91 } 92 93 // Set the new http.ResponseWriter 94 resp.SwitchWriter(grw) 95 96 // Call the next handler supplying the gzipResponseWriter instead of 97 // the original. 98 err = h(ctx, rw, req) 99 if err != nil { 100 return 101 } 102 103 // Delete the content length after we know we have been written to. 104 grw.Header().Del(headerContentLength) 105 gz.Close() 106 gzipPool.Put(gz) 107 return 108 } 109 } 110 }