Skip to content

Commit 0d3b288

Browse files
committed
Implement a variant of nytimes/gziphandler#81
1 parent 4c42a46 commit 0d3b288

File tree

2 files changed

+83
-16
lines changed

2 files changed

+83
-16
lines changed

gzhttp/gzip.go

Lines changed: 60 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type GzipResponseWriter struct {
5454
buf []byte // Holds the first part of the write before reaching the minSize or the end of the write.
5555
ignore bool // If true, then we immediately passthru writes to the underlying ResponseWriter.
5656

57-
contentTypes []parsedContentType // Only compress if the response is one of these content-types. All are accepted if empty.
57+
contentTypeFilter func(ct string) bool // Only compress if the response is one of these content-types. All are accepted if empty.
5858
}
5959

6060
type GzipResponseWriterWithCloseNotify struct {
@@ -87,7 +87,7 @@ func (w *GzipResponseWriter) Write(b []byte) (int, error) {
8787
ce = w.Header().Get(contentEncoding)
8888
)
8989
// Only continue if they didn't already choose an encoding or a known unhandled content length or type.
90-
if ce == "" && (cl == 0 || cl >= w.minSize) && (ct == "" || handleContentType(w.contentTypes, ct)) {
90+
if ce == "" && (cl == 0 || cl >= w.minSize) && (ct == "" || w.contentTypeFilter(ct)) {
9191
// If the current buffer is less than minSize and a Content-Length isn't set, then wait until we have more data.
9292
if len(w.buf) < w.minSize && cl == 0 {
9393
return len(b), nil
@@ -106,7 +106,7 @@ func (w *GzipResponseWriter) Write(b []byte) (int, error) {
106106
w.Header().Set(contentType, ct)
107107
}
108108
// If the Content-Type is acceptable to GZIP, initialize the GZIP writer.
109-
if handleContentType(w.contentTypes, ct) {
109+
if w.contentTypeFilter(ct) {
110110
if err := w.startGzip(); err != nil {
111111
return 0, err
112112
}
@@ -273,6 +273,9 @@ func NewGzipHandler(opts ...option) (func(http.Handler) http.Handler, error) {
273273
Levels: gzkp.Levels,
274274
New: gzkp.NewWriter,
275275
},
276+
contentTypes: func(ct string) bool {
277+
return true
278+
},
276279
}
277280

278281
for _, o := range opts {
@@ -288,11 +291,11 @@ func NewGzipHandler(opts ...option) (func(http.Handler) http.Handler, error) {
288291
w.Header().Add(vary, acceptEncoding)
289292
if acceptsGzip(r) {
290293
gw := &GzipResponseWriter{
291-
ResponseWriter: w,
292-
gwFactory: c.writer,
293-
level: c.level,
294-
minSize: c.minSize,
295-
contentTypes: c.contentTypes,
294+
ResponseWriter: w,
295+
gwFactory: c.writer,
296+
level: c.level,
297+
minSize: c.minSize,
298+
contentTypeFilter: c.contentTypes,
296299
}
297300
defer gw.Close()
298301

@@ -344,7 +347,7 @@ type config struct {
344347
minSize int
345348
level int
346349
writer writer.GzipWriterFactory
347-
contentTypes []parsedContentType
350+
contentTypes func(ct string) bool
348351
}
349352

350353
func (c *config) validate() error {
@@ -403,31 +406,72 @@ func Implementation(writer writer.GzipWriterFactory) option {
403406
//
404407
// By default, responses are gzipped regardless of
405408
// Content-Type.
409+
//
410+
// Setting this will override any previous Content Type settings.
406411
func ContentTypes(types []string) option {
407412
return func(c *config) {
408-
c.contentTypes = []parsedContentType{}
413+
var contentTypes []parsedContentType
409414
for _, v := range types {
410415
mediaType, params, err := mime.ParseMediaType(v)
411416
if err == nil {
412-
c.contentTypes = append(c.contentTypes, parsedContentType{mediaType, params})
417+
contentTypes = append(contentTypes, parsedContentType{mediaType, params})
413418
}
414419
}
420+
c.contentTypes = func(ct string) bool {
421+
return handleContentType(contentTypes, ct)
422+
}
415423
}
416424
}
417425

418-
/*
419-
func ContentTypeFilter(func(contentType string) bool) {
426+
// ExceptContentTypes specifies a list of content types to compare
427+
// the Content-Type header to before compressing. If none
428+
// match, the response will be compressed.
429+
//
430+
// Content types are compared in a case-insensitive, whitespace-ignored
431+
// manner.
432+
//
433+
// A MIME type without any other directive will match a content type
434+
// that has the same MIME type, regardless of that content type's other
435+
// directives. I.e., "text/html" will match both "text/html" and
436+
// "text/html; charset=utf-8".
437+
//
438+
// A MIME type with any other directive will only match a content type
439+
// that has the same MIME type and other directives. I.e.,
440+
// "text/html; charset=utf-8" will only match "text/html; charset=utf-8".
441+
//
442+
// By default, responses are gzipped regardless of
443+
// Content-Type.
444+
//
445+
// Setting this will override any previous Content Type settings.
446+
func ExceptContentTypes(types []string) option {
420447
return func(c *config) {
421-
c.contentTypes = []parsedContentType{}
448+
var contentTypes []parsedContentType
422449
for _, v := range types {
423450
mediaType, params, err := mime.ParseMediaType(v)
424451
if err == nil {
425-
c.contentTypes = append(c.contentTypes, parsedContentType{mediaType, params})
452+
contentTypes = append(contentTypes, parsedContentType{mediaType, params})
426453
}
427454
}
455+
c.contentTypes = func(ct string) bool {
456+
return !handleContentType(contentTypes, ct)
457+
}
458+
}
459+
}
460+
461+
// ContentTypeFilter allows adding a custom content type filter.
462+
//
463+
// The supplied function must return true/false to indicate if content
464+
// should be compressed.
465+
//
466+
// When called no parsing of the content type 'ct' has been done.
467+
// It may have been set or auto-detected.
468+
//
469+
// Setting this will override any previous Content Type settings.
470+
func ContentTypeFilter(compress func(ct string) bool) option {
471+
return func(c *config) {
472+
c.contentTypes = compress
428473
}
429474
}
430-
*/
431475

432476
// GzipHandler wraps an HTTP handler, to transparently gzip the response body if
433477
// the client supports it (via the Accept-Encoding header). This will compress at

gzhttp/gzip_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,29 @@ func TestContentTypes(t *testing.T) {
579579
assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
580580
}
581581
})
582+
t.Run("not-"+tt.name, func(t *testing.T) {
583+
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
584+
w.WriteHeader(http.StatusOK)
585+
w.Header().Set("Content-Type", tt.contentType)
586+
io.WriteString(w, testBody)
587+
})
588+
589+
wrapper, err := NewGzipHandler(ExceptContentTypes(tt.acceptedContentTypes))
590+
assertNil(t, err)
591+
592+
req, _ := http.NewRequest("GET", "/whatever", nil)
593+
req.Header.Set("Accept-Encoding", "gzip")
594+
resp := httptest.NewRecorder()
595+
wrapper(handler).ServeHTTP(resp, req)
596+
res := resp.Result()
597+
598+
assertEqual(t, 200, res.StatusCode)
599+
if !tt.expectedGzip {
600+
assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
601+
} else {
602+
assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
603+
}
604+
})
582605
}
583606
}
584607

0 commit comments

Comments
 (0)