diff --git a/gzhttp/compress.go b/gzhttp/compress.go index a9be8e979..28fe33195 100644 --- a/gzhttp/compress.go +++ b/gzhttp/compress.go @@ -464,6 +464,11 @@ func NewWrapper(opts ...option) (func(http.Handler) http.HandlerFunc, error) { return func(h http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Add(vary, acceptEncoding) + if c.allowCompressedRequests && contentGzip(r) { + r.Header.Del(contentEncoding) + r.Body = &gzipReader{body: r.Body} + } + if acceptsGzip(r) { gw := grwPool.Get().(*GzipResponseWriter) *gw = GzipResponseWriter{ @@ -536,17 +541,18 @@ func (pct parsedContentType) equals(mediaType string, params map[string]string) // Used for functional configuration. type config struct { - minSize int - level int - writer writer.GzipWriterFactory - contentTypes func(ct string) bool - keepAcceptRanges bool - setContentType bool - suffixETag string - dropETag bool - jitterBuffer int - randomJitter string - sha256Jitter bool + minSize int + level int + writer writer.GzipWriterFactory + contentTypes func(ct string) bool + keepAcceptRanges bool + setContentType bool + suffixETag string + dropETag bool + jitterBuffer int + randomJitter string + sha256Jitter bool + allowCompressedRequests bool } func (c *config) validate() error { @@ -579,6 +585,15 @@ func MinSize(size int) option { } } +// AllowCompressedRequests will enable or disable RFC 7694 compressed requests. +// By default this is Disabled. +// See https://datatracker.ietf.org/doc/html/rfc7694 +func AllowCompressedRequests(b bool) option { + return func(c *config) { + c.allowCompressedRequests = b + } +} + // CompressionLevel sets the compression level func CompressionLevel(level int) option { return func(c *config) { @@ -752,6 +767,12 @@ func RandomJitter(n, buffer int, paranoid bool) option { } } +// contentGzip returns true if the given HTTP request indicates that it gzipped. +func contentGzip(r *http.Request) bool { + // See more detail in `acceptsGzip` + return r.Method != http.MethodHead && r.Body != nil && parseEncodingGzip(r.Header.Get(contentEncoding)) > 0 +} + // acceptsGzip returns true if the given HTTP request indicates that it will // accept a gzipped response. func acceptsGzip(r *http.Request) bool { diff --git a/gzhttp/compress_test.go b/gzhttp/compress_test.go index c91de81b4..03c220d67 100644 --- a/gzhttp/compress_test.go +++ b/gzhttp/compress_test.go @@ -89,6 +89,41 @@ func TestMustNewGzipHandler(t *testing.T) { handler.ServeHTTP(res3, req3) assertEqual(t, http.DetectContentType([]byte(testBody)), res3.Header().Get("Content-Type")) + + // send compress request body with `AllowCompressedRequests` + handler = newTestHandlerLevel(testBody, AllowCompressedRequests(true)) + + var b bytes.Buffer + writerGzip := gzip.NewWriter(&b) + writerGzip.Write(testBody) + writerGzip.Close() + + req5, _ := http.NewRequest("POST", "/whatever", &b) + req5.Header.Set("Content-Encoding", "gzip") + resp5 := httptest.NewRecorder() + handler.ServeHTTP(resp5, req5) + res5 := resp5.Result() + + assertEqual(t, 200, res5.StatusCode) + + body, _ := io.ReadAll(res5.Body) + assertEqual(t, len(testBody), len(body)) + + // send compress request body without `AllowCompressedRequests` + writerGzip = gzip.NewWriter(&b) + writerGzip.Write(testBody) + writerGzip.Close() + + handler = newTestHandlerLevel(b.Bytes()) + + req6, _ := http.NewRequest("POST", "/whatever", &b) + resp6 := httptest.NewRecorder() + handler.ServeHTTP(resp6, req6) + res6 := resp6.Result() + + assertEqual(t, 200, res6.StatusCode) + body, _ = io.ReadAll(res6.Body) + assertEqual(t, b.Len(), len(body)) } func TestGzipHandlerSmallBodyNoCompression(t *testing.T) {