From 3c29c8422b256a84c5da95c08ff8d4b2d4c0db08 Mon Sep 17 00:00:00 2001 From: Eugene Zagidullin Date: Sat, 16 Jan 2016 23:44:18 +0200 Subject: [PATCH 1/3] Set headers on write --- gzip/gzip.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/gzip/gzip.go b/gzip/gzip.go index 9c8defe..cae10ca 100644 --- a/gzip/gzip.go +++ b/gzip/gzip.go @@ -94,14 +94,17 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha defer h.pool.Put(gz) gz.Reset(w) - // Set the appropriate gzip headers. - headers := w.Header() - headers.Set(headerContentEncoding, encodingGzip) - headers.Set(headerVary, headerAcceptEncoding) - // Wrap the original http.ResponseWriter with negroni.ResponseWriter // and create the gzipResponseWriter. nrw := negroni.NewResponseWriter(w) + + // Set headers only if WriteHeader has been called + nrw.Before(func(w negroni.ResponseWriter) { + headers := w.Header() + headers.Set(headerContentEncoding, encodingGzip) + headers.Set(headerVary, headerAcceptEncoding) + }) + grw := gzipResponseWriter{ gz, nrw, From fbc6d69bd102a1da4d98a51f291711e947f3ea0a Mon Sep 17 00:00:00 2001 From: Eugene Zagidullin Date: Tue, 19 Jan 2016 19:23:49 +0200 Subject: [PATCH 2/3] Skip suffixes --- gzip/gzip.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/gzip/gzip.go b/gzip/gzip.go index cae10ca..6ff8463 100644 --- a/gzip/gzip.go +++ b/gzip/gzip.go @@ -28,6 +28,11 @@ const ( NoCompression = gzip.NoCompression ) +type Options struct { + Level int + ExcludeSuffixes []string +} + // gzipResponseWriter is the ResponseWriter that negroni.ResponseWriter is // wrapped in. type gzipResponseWriter struct { @@ -48,14 +53,15 @@ func (grw gzipResponseWriter) Write(b []byte) (int, error) { // handler struct contains the ServeHTTP method type handler struct { pool sync.Pool + opt *Options } // Gzip returns a handler which will handle the Gzip compression in ServeHTTP. // Valid values for level are identical to those in the compress/gzip package. -func Gzip(level int) *handler { - h := &handler{} +func Gzip(opt *Options) *handler { + h := &handler{opt: opt} h.pool.New = func() interface{} { - gz, err := gzip.NewWriterLevel(ioutil.Discard, level) + gz, err := gzip.NewWriterLevel(ioutil.Discard, h.opt.Level) if err != nil { panic(err) } @@ -66,6 +72,14 @@ func Gzip(level int) *handler { // ServeHTTP wraps the http.ResponseWriter with a gzip.Writer. func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { + // Skip specified suffixes (images e.g.) + for _, suffix := range h.opt.ExcludeSuffixes { + if strings.HasSuffix(r.URL.Path, suffix) { + next(w, r) + return + } + } + // Skip compression if the client doesn't accept gzip encoding. if !strings.Contains(r.Header.Get(headerAcceptEncoding), encodingGzip) { next(w, r) From f6f689ed7164d9d9f9c72ea0a67bf1c73ba1e78d Mon Sep 17 00:00:00 2001 From: Eugene Zagidullin Date: Thu, 4 Feb 2016 20:14:38 +0200 Subject: [PATCH 3/3] Skip specified content-types --- gzip/gzip.go | 85 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/gzip/gzip.go b/gzip/gzip.go index 6ff8463..b6fe5c4 100644 --- a/gzip/gzip.go +++ b/gzip/gzip.go @@ -29,25 +29,70 @@ const ( ) type Options struct { - Level int - ExcludeSuffixes []string + Level int + ExcludeContentTypes []string } // gzipResponseWriter is the ResponseWriter that negroni.ResponseWriter is // wrapped in. type gzipResponseWriter struct { - w *gzip.Writer + w *gzip.Writer + excludeContentTypes []string + skipCompression bool + wroteHeader bool negroni.ResponseWriter } // Write writes bytes to the gzip.Writer. It will also set the Content-Type // header using the net/http library content type detection if the Content-Type // header was not set yet. -func (grw gzipResponseWriter) Write(b []byte) (int, error) { - if len(grw.Header().Get(headerContentType)) == 0 { - grw.Header().Set(headerContentType, http.DetectContentType(b)) +func (grw *gzipResponseWriter) Write(b []byte) (int, error) { + if !grw.wroteHeader { + // It is not too late to set Content-Type! + contentType := grw.Header().Get(headerContentType) + if len(contentType) == 0 { + contentType = http.DetectContentType(b) + grw.Header().Set(headerContentType, contentType) + } + + for _, ct := range grw.excludeContentTypes { + if contentType == ct { + grw.skipCompression = true + break + } + } + + if !grw.skipCompression { + grw.Header().Set(headerContentEncoding, encodingGzip) + grw.Header().Set(headerVary, headerAcceptEncoding) + } + + grw.wroteHeader = true + } + + if grw.skipCompression { + return grw.ResponseWriter.Write(b) // bypass + } else { + return grw.w.Write(b) + } +} + +func (grw *gzipResponseWriter) WriteHeader(s int) { + contentType := grw.Header().Get(headerContentType) + for _, ct := range grw.excludeContentTypes { + if contentType == ct { + grw.skipCompression = true + break + } } - return grw.w.Write(b) + + if !grw.skipCompression { + grw.Header().Set(headerContentEncoding, encodingGzip) + grw.Header().Set(headerVary, headerAcceptEncoding) + } + + grw.wroteHeader = true + grw.ResponseWriter.WriteHeader(s) } // handler struct contains the ServeHTTP method @@ -72,14 +117,6 @@ func Gzip(opt *Options) *handler { // ServeHTTP wraps the http.ResponseWriter with a gzip.Writer. func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) { - // Skip specified suffixes (images e.g.) - for _, suffix := range h.opt.ExcludeSuffixes { - if strings.HasSuffix(r.URL.Path, suffix) { - next(w, r) - return - } - } - // Skip compression if the client doesn't accept gzip encoding. if !strings.Contains(r.Header.Get(headerAcceptEncoding), encodingGzip) { next(w, r) @@ -112,24 +149,20 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha // and create the gzipResponseWriter. nrw := negroni.NewResponseWriter(w) - // Set headers only if WriteHeader has been called - nrw.Before(func(w negroni.ResponseWriter) { - headers := w.Header() - headers.Set(headerContentEncoding, encodingGzip) - headers.Set(headerVary, headerAcceptEncoding) - }) - grw := gzipResponseWriter{ - gz, - nrw, + w: gz, + excludeContentTypes: h.opt.ExcludeContentTypes, + ResponseWriter: nrw, } // Call the next handler supplying the gzipResponseWriter instead of // the original. - next(grw, r) + next(&grw, r) // Delete the content length after we know we have been written to. grw.Header().Del(headerContentLength) - gz.Close() + if !grw.skipCompression { + gz.Close() + } }