...

Source file src/github.com/klauspost/compress/gzhttp/compress_test.go

Documentation: github.com/klauspost/compress/gzhttp

     1  package gzhttp
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"math/rand"
     9  	"net"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/http/httptrace"
    13  	"net/textproto"
    14  	"net/url"
    15  	"os"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  
    20  	"github.com/klauspost/compress/gzip"
    21  )
    22  
    23  var (
    24  	smallTestBody = []byte("aaabbcaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbc")
    25  	testBody      = []byte("aaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbccc aaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbccc aaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbccc aaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbccc aaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbccc aaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbccc aaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbcccaaabbbccc")
    26  )
    27  
    28  func TestParseEncodings(t *testing.T) {
    29  	examples := map[string]codings{
    30  
    31  		// Examples from RFC 2616
    32  		"compress, gzip":                     {"compress": 1.0, "gzip": 1.0},
    33  		",,,,":                               {},
    34  		"":                                   {},
    35  		"*":                                  {"*": 1.0},
    36  		"compress;q=0.5, gzip;q=1.0":         {"compress": 0.5, "gzip": 1.0},
    37  		"gzip;q=1.0, identity; q=0.5, *;q=0": {"gzip": 1.0, "identity": 0.5, "*": 0.0},
    38  
    39  		// More random stuff
    40  		"AAA;q=1":     {"aaa": 1.0},
    41  		"BBB ; q = 2": {"bbb": 1.0},
    42  	}
    43  
    44  	for eg, exp := range examples {
    45  		t.Run(eg, func(t *testing.T) {
    46  			act, _ := parseEncodings(eg)
    47  			assertEqual(t, exp, act)
    48  			gz := parseEncodingGzip(eg)
    49  			assertEqual(t, exp["gzip"], gz)
    50  		})
    51  	}
    52  }
    53  
    54  func TestMustNewGzipHandler(t *testing.T) {
    55  	// This just exists to provide something for GzipHandler to wrap.
    56  	handler := newTestHandler(testBody)
    57  
    58  	// requests without accept-encoding are passed along as-is
    59  
    60  	req1, _ := http.NewRequest("GET", "/whatever", nil)
    61  	resp1 := httptest.NewRecorder()
    62  	handler.ServeHTTP(resp1, req1)
    63  	res1 := resp1.Result()
    64  
    65  	assertEqual(t, 200, res1.StatusCode)
    66  	assertEqual(t, "", res1.Header.Get("Content-Encoding"))
    67  	assertEqual(t, "Accept-Encoding", res1.Header.Get("Vary"))
    68  	assertEqual(t, testBody, resp1.Body.Bytes())
    69  
    70  	// but requests with accept-encoding:gzip are compressed if possible
    71  
    72  	req2, _ := http.NewRequest("GET", "/whatever", nil)
    73  	req2.Header.Set("Accept-Encoding", "gzip")
    74  	resp2 := httptest.NewRecorder()
    75  	handler.ServeHTTP(resp2, req2)
    76  	res2 := resp2.Result()
    77  
    78  	assertEqual(t, 200, res2.StatusCode)
    79  	assertEqual(t, "gzip", res2.Header.Get("Content-Encoding"))
    80  	assertEqual(t, "Accept-Encoding", res2.Header.Get("Vary"))
    81  	assertEqual(t, gzipStrLevel(testBody, gzip.DefaultCompression), resp2.Body.Bytes())
    82  
    83  	// content-type header is correctly set based on uncompressed body
    84  
    85  	req3, _ := http.NewRequest("GET", "/whatever", nil)
    86  	req3.Header.Set("Accept-Encoding", "gzip")
    87  	res3 := httptest.NewRecorder()
    88  	handler.ServeHTTP(res3, req3)
    89  
    90  	assertEqual(t, http.DetectContentType([]byte(testBody)), res3.Header().Get("Content-Type"))
    91  }
    92  
    93  func TestGzipHandlerSmallBodyNoCompression(t *testing.T) {
    94  	handler := newTestHandler(smallTestBody)
    95  
    96  	req, _ := http.NewRequest("GET", "/whatever", nil)
    97  	req.Header.Set("Accept-Encoding", "gzip")
    98  	resp := httptest.NewRecorder()
    99  	handler.ServeHTTP(resp, req)
   100  	res := resp.Result()
   101  
   102  	// with less than 1400 bytes the response should not be gzipped
   103  
   104  	assertEqual(t, 200, res.StatusCode)
   105  	assertEqual(t, "", res.Header.Get("Content-Encoding"))
   106  	assertEqual(t, "Accept-Encoding", res.Header.Get("Vary"))
   107  	assertEqual(t, smallTestBody, resp.Body.Bytes())
   108  
   109  }
   110  
   111  func TestGzipHandlerAlreadyCompressed(t *testing.T) {
   112  	handler := newTestHandler(testBody)
   113  
   114  	req, _ := http.NewRequest("GET", "/gzipped", nil)
   115  	req.Header.Set("Accept-Encoding", "gzip")
   116  	res := httptest.NewRecorder()
   117  	handler.ServeHTTP(res, req)
   118  
   119  	assertEqual(t, testBody, res.Body.Bytes())
   120  }
   121  
   122  func TestGzipHandlerRangeReply(t *testing.T) {
   123  	handler := GzipHandler(
   124  		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   125  			w.Header().Set("Content-Range", "bytes 0-300/804")
   126  			w.WriteHeader(http.StatusOK)
   127  			w.Write([]byte(testBody))
   128  		}))
   129  	req, _ := http.NewRequest("GET", "/gzipped", nil)
   130  	req.Header.Set("Accept-Encoding", "gzip")
   131  
   132  	resp := httptest.NewRecorder()
   133  	handler.ServeHTTP(resp, req)
   134  	res := resp.Result()
   135  	assertEqual(t, 200, res.StatusCode)
   136  	assertEqual(t, "", res.Header.Get("Content-Encoding"))
   137  	assertEqual(t, testBody, resp.Body.Bytes())
   138  }
   139  
   140  func TestGzipHandlerAcceptRange(t *testing.T) {
   141  	handler := GzipHandler(
   142  		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   143  			w.Header().Set("Accept-Ranges", "bytes")
   144  			w.WriteHeader(http.StatusOK)
   145  			w.Write([]byte(testBody))
   146  		}))
   147  	req, _ := http.NewRequest("GET", "/gzipped", nil)
   148  	req.Header.Set("Accept-Encoding", "gzip")
   149  
   150  	resp := httptest.NewRecorder()
   151  	handler.ServeHTTP(resp, req)
   152  	res := resp.Result()
   153  	assertEqual(t, 200, res.StatusCode)
   154  	assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   155  	assertEqual(t, "", res.Header.Get("Accept-Ranges"))
   156  	zr, err := gzip.NewReader(resp.Body)
   157  	assertNil(t, err)
   158  	got, err := io.ReadAll(zr)
   159  	assertNil(t, err)
   160  	assertEqual(t, testBody, got)
   161  }
   162  
   163  func TestGzipHandlerKeepAcceptRange(t *testing.T) {
   164  	wrapper, err := NewWrapper(KeepAcceptRanges())
   165  	assertNil(t, err)
   166  	handler := wrapper(
   167  		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   168  			w.Header().Set("Accept-Ranges", "bytes")
   169  			w.WriteHeader(http.StatusOK)
   170  			w.Write([]byte(testBody))
   171  		}))
   172  	req, _ := http.NewRequest("GET", "/gzipped", nil)
   173  	req.Header.Set("Accept-Encoding", "gzip")
   174  
   175  	resp := httptest.NewRecorder()
   176  	handler.ServeHTTP(resp, req)
   177  	res := resp.Result()
   178  	assertEqual(t, 200, res.StatusCode)
   179  	assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   180  	assertEqual(t, "bytes", res.Header.Get("Accept-Ranges"))
   181  	zr, err := gzip.NewReader(resp.Body)
   182  	assertNil(t, err)
   183  	got, err := io.ReadAll(zr)
   184  	assertNil(t, err)
   185  	assertEqual(t, testBody, got)
   186  }
   187  
   188  func TestGzipHandlerSuffixETag(t *testing.T) {
   189  	wrapper, err := NewWrapper(SuffixETag("-gzip"))
   190  	assertNil(t, err)
   191  
   192  	handlerWithETag := wrapper(
   193  		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   194  			w.Header().Set("ETag", `W/"1234"`)
   195  			w.WriteHeader(http.StatusOK)
   196  			w.Write([]byte(testBody))
   197  		}))
   198  	handlerWithoutETag := wrapper(
   199  		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   200  			w.WriteHeader(http.StatusOK)
   201  			w.Write([]byte(testBody))
   202  		}))
   203  
   204  	req, _ := http.NewRequest("GET", "/gzipped", nil)
   205  	req.Header.Set("Accept-Encoding", "gzip")
   206  
   207  	respWithEtag := httptest.NewRecorder()
   208  	respWithoutEtag := httptest.NewRecorder()
   209  	handlerWithETag.ServeHTTP(respWithEtag, req)
   210  	handlerWithoutETag.ServeHTTP(respWithoutEtag, req)
   211  
   212  	resWithEtag := respWithEtag.Result()
   213  	assertEqual(t, 200, resWithEtag.StatusCode)
   214  	assertEqual(t, "gzip", resWithEtag.Header.Get("Content-Encoding"))
   215  	assertEqual(t, `W/"1234-gzip"`, resWithEtag.Header.Get("ETag"))
   216  	zr, err := gzip.NewReader(resWithEtag.Body)
   217  	assertNil(t, err)
   218  	got, err := io.ReadAll(zr)
   219  	assertNil(t, err)
   220  	assertEqual(t, testBody, got)
   221  
   222  	resWithoutEtag := respWithoutEtag.Result()
   223  	assertEqual(t, 200, resWithoutEtag.StatusCode)
   224  	assertEqual(t, "gzip", resWithoutEtag.Header.Get("Content-Encoding"))
   225  	assertEqual(t, "", resWithoutEtag.Header.Get("ETag"))
   226  	zr, err = gzip.NewReader(resWithoutEtag.Body)
   227  	assertNil(t, err)
   228  	got, err = io.ReadAll(zr)
   229  	assertNil(t, err)
   230  	assertEqual(t, testBody, got)
   231  }
   232  
   233  func TestGzipHandlerDropETag(t *testing.T) {
   234  	wrapper, err := NewWrapper(DropETag())
   235  	assertNil(t, err)
   236  
   237  	handlerCompressed := wrapper(
   238  		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   239  			w.Header().Set("ETag", `W/"1234"`)
   240  			w.WriteHeader(http.StatusOK)
   241  			w.Write([]byte(testBody))
   242  		}))
   243  	handlerUncompressed := wrapper(
   244  		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   245  			w.Header().Set("ETag", `W/"1234"`)
   246  			w.Header().Set(HeaderNoCompression, "true")
   247  			w.WriteHeader(http.StatusOK)
   248  			w.Write([]byte(testBody))
   249  		}))
   250  
   251  	req, _ := http.NewRequest("GET", "/gzipped", nil)
   252  	req.Header.Set("Accept-Encoding", "gzip")
   253  
   254  	respCompressed := httptest.NewRecorder()
   255  	respUncompressed := httptest.NewRecorder()
   256  	handlerCompressed.ServeHTTP(respCompressed, req)
   257  	handlerUncompressed.ServeHTTP(respUncompressed, req)
   258  
   259  	resCompressed := respCompressed.Result()
   260  	assertEqual(t, 200, resCompressed.StatusCode)
   261  	assertEqual(t, "gzip", resCompressed.Header.Get("Content-Encoding"))
   262  	assertEqual(t, "", resCompressed.Header.Get("ETag"))
   263  	zr, err := gzip.NewReader(resCompressed.Body)
   264  	assertNil(t, err)
   265  	got, err := io.ReadAll(zr)
   266  	assertNil(t, err)
   267  	assertEqual(t, testBody, got)
   268  
   269  	resUncompressed := respUncompressed.Result()
   270  	assertEqual(t, 200, resUncompressed.StatusCode)
   271  	assertEqual(t, "", resUncompressed.Header.Get("Content-Encoding"))
   272  	assertEqual(t, `W/"1234"`, resUncompressed.Header.Get("ETag"))
   273  	got, err = io.ReadAll(resUncompressed.Body)
   274  	assertNil(t, err)
   275  	assertEqual(t, testBody, got)
   276  }
   277  
   278  func TestNewGzipLevelHandler(t *testing.T) {
   279  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   280  		w.WriteHeader(http.StatusOK)
   281  		w.Write(testBody)
   282  	})
   283  
   284  	for lvl := gzip.StatelessCompression; lvl <= gzip.BestCompression; lvl++ {
   285  		t.Run(fmt.Sprint(lvl), func(t *testing.T) {
   286  			wrapper, err := NewWrapper(CompressionLevel(lvl))
   287  			assertNil(t, err)
   288  
   289  			req, _ := http.NewRequest("GET", "/whatever", nil)
   290  			req.Header.Set("Accept-Encoding", "gzip")
   291  			resp := httptest.NewRecorder()
   292  			wrapper(handler).ServeHTTP(resp, req)
   293  			res := resp.Result()
   294  
   295  			assertEqual(t, 200, res.StatusCode)
   296  			assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   297  			assertEqual(t, "Accept-Encoding", res.Header.Get("Vary"))
   298  			got := gzipStrLevel(testBody, lvl)
   299  			if lvl != gzip.StatelessCompression {
   300  				assertEqual(t, got, resp.Body.Bytes())
   301  			}
   302  			t.Log(lvl, len(got))
   303  		})
   304  	}
   305  }
   306  
   307  func TestNewGzipLevelHandlerReturnsErrorForInvalidLevels(t *testing.T) {
   308  	var err error
   309  	_, err = NewWrapper(CompressionLevel(-42))
   310  	assertNotNil(t, err)
   311  
   312  	_, err = NewWrapper(CompressionLevel(42))
   313  	assertNotNil(t, err)
   314  }
   315  
   316  func TestMustNewGzipLevelHandlerWillPanic(t *testing.T) {
   317  	defer func() {
   318  		if r := recover(); r != nil {
   319  			t.Error("panic was called with", r)
   320  		}
   321  	}()
   322  
   323  	_ = GzipHandler(nil)
   324  }
   325  
   326  func TestGzipHandlerNoBody(t *testing.T) {
   327  	tests := []struct {
   328  		statusCode      int
   329  		contentEncoding string
   330  		emptyBody       bool
   331  		body            []byte
   332  	}{
   333  		// Body must be empty.
   334  		{http.StatusNoContent, "", true, nil},
   335  		{http.StatusNotModified, "", true, nil},
   336  		// Body is going to get gzip'd no matter what.
   337  		{http.StatusOK, "", true, []byte{}},
   338  		{http.StatusOK, "gzip", false, []byte(testBody)},
   339  	}
   340  
   341  	for num, test := range tests {
   342  		t.Run(fmt.Sprintf("test-%d", num), func(t *testing.T) {
   343  			handler := GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   344  				w.WriteHeader(test.statusCode)
   345  				if test.body != nil {
   346  					w.Write(test.body)
   347  				}
   348  			}))
   349  
   350  			rec := httptest.NewRecorder()
   351  			req := httptest.NewRequest(http.MethodGet, "/", nil)
   352  			req.Header.Set("Accept-Encoding", "gzip")
   353  			handler.ServeHTTP(rec, req)
   354  
   355  			body, err := io.ReadAll(rec.Body)
   356  			if err != nil {
   357  				t.Fatalf("Unexpected error reading response body: %v", err)
   358  			}
   359  
   360  			header := rec.Header()
   361  			assertEqual(t, test.contentEncoding, header.Get("Content-Encoding"))
   362  			assertEqual(t, "Accept-Encoding", header.Get("Vary"))
   363  			if test.emptyBody {
   364  				assertEqual(t, 0, len(body))
   365  			} else {
   366  				assertNotEqual(t, 0, len(body))
   367  				assertNotEqual(t, test.body, body)
   368  			}
   369  		})
   370  
   371  	}
   372  }
   373  
   374  func TestGzipHandlerContentLength(t *testing.T) {
   375  	testBodyBytes := []byte(testBody)
   376  	tests := []struct {
   377  		bodyLen   int
   378  		bodies    [][]byte
   379  		emptyBody bool
   380  	}{
   381  		{len(testBody), [][]byte{testBodyBytes}, false},
   382  		// each of these writes is less than the DefaultMinSize
   383  		{len(testBody), [][]byte{testBodyBytes[:200], testBodyBytes[200:]}, false},
   384  		// without a defined Content-Length it should still gzip
   385  		{0, [][]byte{testBodyBytes[:200], testBodyBytes[200:]}, false},
   386  		// simulate a HEAD request with an empty write (to populate headers)
   387  		{len(testBody), [][]byte{nil}, true},
   388  	}
   389  
   390  	// httptest.NewRecorder doesn't give you access to the Content-Length
   391  	// header so instead, we create a server on a random port and make
   392  	// a request to that instead
   393  	ln, err := net.Listen("tcp", "localhost:")
   394  	if err != nil {
   395  		t.Fatalf("failed creating listen socket: %v", err)
   396  	}
   397  	defer ln.Close()
   398  	srv := &http.Server{
   399  		Handler: nil,
   400  	}
   401  	go srv.Serve(ln)
   402  
   403  	for num, test := range tests {
   404  		t.Run(fmt.Sprintf("test-%d", num), func(t *testing.T) {
   405  			srv.Handler = GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   406  				if test.bodyLen > 0 {
   407  					w.Header().Set("Content-Length", strconv.Itoa(test.bodyLen))
   408  				}
   409  				for _, b := range test.bodies {
   410  					w.Write(b)
   411  				}
   412  			}))
   413  			req := &http.Request{
   414  				Method: "GET",
   415  				URL:    &url.URL{Path: "/", Scheme: "http", Host: ln.Addr().String()},
   416  				Header: make(http.Header),
   417  				Close:  true,
   418  			}
   419  			req.Header.Set("Accept-Encoding", "gzip")
   420  			res, err := http.DefaultClient.Do(req)
   421  			if err != nil {
   422  				t.Fatalf("Unexpected error making http request in test iteration %d: %v", num, err)
   423  			}
   424  			defer res.Body.Close()
   425  
   426  			body, err := io.ReadAll(res.Body)
   427  			if err != nil {
   428  				t.Fatalf("Unexpected error reading response body in test iteration %d: %v", num, err)
   429  			}
   430  
   431  			l, err := strconv.Atoi(res.Header.Get("Content-Length"))
   432  			if err != nil {
   433  				t.Fatalf("Unexpected error parsing Content-Length in test iteration %d: %v", num, err)
   434  			}
   435  			if test.emptyBody {
   436  				assertEqual(t, 0, len(body))
   437  				assertEqual(t, 0, l)
   438  			} else {
   439  				assertEqual(t, len(body), l)
   440  			}
   441  			assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   442  			assertNotEqual(t, test.bodyLen, l)
   443  		})
   444  	}
   445  }
   446  
   447  func TestGzipHandlerMinSizeMustBePositive(t *testing.T) {
   448  	_, err := NewWrapper(MinSize(-1))
   449  	assertNotNil(t, err)
   450  }
   451  
   452  func TestGzipHandlerMinSize(t *testing.T) {
   453  	responseLength := 0
   454  	b := []byte{'x'}
   455  
   456  	wrapper, _ := NewWrapper(MinSize(128))
   457  	handler := wrapper(http.HandlerFunc(
   458  		func(w http.ResponseWriter, r *http.Request) {
   459  			// Write responses one byte at a time to ensure that the flush
   460  			// mechanism, if used, is working properly.
   461  			for i := 0; i < responseLength; i++ {
   462  				n, err := w.Write(b)
   463  				assertEqual(t, 1, n)
   464  				assertNil(t, err)
   465  			}
   466  		},
   467  	))
   468  
   469  	r, _ := http.NewRequest("GET", "/whatever", &bytes.Buffer{})
   470  	r.Header.Add("Accept-Encoding", "gzip")
   471  
   472  	// Short response is not compressed
   473  	responseLength = 127
   474  	w := httptest.NewRecorder()
   475  	handler.ServeHTTP(w, r)
   476  	if w.Result().Header.Get(contentEncoding) == "gzip" {
   477  		t.Error("Expected uncompressed response, got compressed")
   478  	}
   479  
   480  	// Long response is not compressed
   481  	responseLength = 128
   482  	w = httptest.NewRecorder()
   483  	handler.ServeHTTP(w, r)
   484  	if w.Result().Header.Get(contentEncoding) != "gzip" {
   485  		t.Error("Expected compressed response, got uncompressed")
   486  	}
   487  }
   488  
   489  type panicOnSecondWriteHeaderWriter struct {
   490  	http.ResponseWriter
   491  	headerWritten bool
   492  }
   493  
   494  func (w *panicOnSecondWriteHeaderWriter) WriteHeader(s int) {
   495  	if w.headerWritten {
   496  		panic("header already written")
   497  	}
   498  	w.headerWritten = true
   499  	w.ResponseWriter.WriteHeader(s)
   500  }
   501  
   502  func TestGzipHandlerDoubleWriteHeader(t *testing.T) {
   503  	handler := GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   504  		w.Header().Set("Content-Length", "15000")
   505  		// Specifically write the header here
   506  		w.WriteHeader(304)
   507  		// Ensure that after a Write the header isn't triggered again on close
   508  		w.Write(nil)
   509  	}))
   510  	wrapper := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   511  		w = &panicOnSecondWriteHeaderWriter{
   512  			ResponseWriter: w,
   513  		}
   514  		handler.ServeHTTP(w, r)
   515  	})
   516  
   517  	rec := httptest.NewRecorder()
   518  	// TODO: in Go1.7 httptest.NewRequest was introduced this should be used
   519  	// once 1.6 is not longer supported.
   520  	req := &http.Request{
   521  		Method:     "GET",
   522  		URL:        &url.URL{Path: "/"},
   523  		Proto:      "HTTP/1.1",
   524  		ProtoMinor: 1,
   525  		RemoteAddr: "192.0.2.1:1234",
   526  		Header:     make(http.Header),
   527  	}
   528  	req.Header.Set("Accept-Encoding", "gzip")
   529  	wrapper.ServeHTTP(rec, req)
   530  	body, err := io.ReadAll(rec.Body)
   531  	if err != nil {
   532  		t.Fatalf("Unexpected error reading response body: %v", err)
   533  	}
   534  	assertEqual(t, 0, len(body))
   535  	header := rec.Header()
   536  	assertEqual(t, "gzip", header.Get("Content-Encoding"))
   537  	assertEqual(t, "Accept-Encoding", header.Get("Vary"))
   538  	assertEqual(t, 304, rec.Code)
   539  }
   540  
   541  func TestStatusCodes(t *testing.T) {
   542  	handler := GzipHandler(http.NotFoundHandler())
   543  	r := httptest.NewRequest("GET", "/", nil)
   544  	r.Header.Set("Accept-Encoding", "gzip")
   545  	w := httptest.NewRecorder()
   546  	handler.ServeHTTP(w, r)
   547  
   548  	result := w.Result()
   549  	if result.StatusCode != 404 {
   550  		t.Errorf("StatusCode should have been 404 but was %d", result.StatusCode)
   551  	}
   552  }
   553  
   554  func TestFlushBeforeWrite(t *testing.T) {
   555  	b := []byte(testBody)
   556  	handler := GzipHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   557  		rw.WriteHeader(http.StatusNotFound)
   558  		rw.(http.Flusher).Flush()
   559  		rw.Write(b)
   560  	}))
   561  	r := httptest.NewRequest(http.MethodGet, "/", nil)
   562  	r.Header.Set("Accept-Encoding", "gzip")
   563  	w := httptest.NewRecorder()
   564  	handler.ServeHTTP(w, r)
   565  
   566  	res := w.Result()
   567  	assertEqual(t, http.StatusNotFound, res.StatusCode)
   568  	assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   569  	assertNotEqual(t, b, w.Body.Bytes())
   570  }
   571  
   572  func TestFlushAfterWrite(t *testing.T) {
   573  	b := testBody[:1000]
   574  	handler := GzipHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   575  		rw.WriteHeader(http.StatusOK)
   576  		rw.Write(b[0:1])
   577  		rw.(http.Flusher).Flush()
   578  		for i := range b[1:] {
   579  			rw.Write(b[i+1 : i+2])
   580  		}
   581  	}))
   582  	r := httptest.NewRequest(http.MethodGet, "/", nil)
   583  	r.Header.Set("Accept-Encoding", "gzip")
   584  	w := httptest.NewRecorder()
   585  	handler.ServeHTTP(w, r)
   586  
   587  	res := w.Result()
   588  	assertEqual(t, http.StatusOK, res.StatusCode)
   589  	assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   590  	gr, err := gzip.NewReader(w.Body)
   591  	assertNil(t, err)
   592  	got, err := io.ReadAll(gr)
   593  	assertNil(t, err)
   594  	assertEqual(t, b, got)
   595  }
   596  
   597  func TestFlushAfterWrite2(t *testing.T) {
   598  	b := testBody[:1050]
   599  	handler := GzipHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   600  		for i := range b {
   601  			rw.Write(b[i : i+1])
   602  		}
   603  		rw.(http.Flusher).Flush()
   604  	}))
   605  	r := httptest.NewRequest(http.MethodGet, "/", nil)
   606  	r.Header.Set("Accept-Encoding", "gzip")
   607  	w := httptest.NewRecorder()
   608  	handler.ServeHTTP(w, r)
   609  
   610  	res := w.Result()
   611  	assertEqual(t, http.StatusOK, res.StatusCode)
   612  	assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   613  	gr, err := gzip.NewReader(w.Body)
   614  	assertNil(t, err)
   615  	got, err := io.ReadAll(gr)
   616  	assertNil(t, err)
   617  	assertEqual(t, b, got)
   618  }
   619  
   620  func TestFlushAfterWrite3(t *testing.T) {
   621  	b := []byte(nil)
   622  	gz, err := NewWrapper(MinSize(1000), CompressionLevel(gzip.BestSpeed))
   623  	if err != nil {
   624  		// Static params, so this is very unlikely.
   625  		t.Fatal(err, "Unable to initialize server")
   626  	}
   627  	handler := gz(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   628  		rw.WriteHeader(http.StatusOK)
   629  		//rw.Write(nil)
   630  		rw.(http.Flusher).Flush()
   631  	}))
   632  	r := httptest.NewRequest(http.MethodGet, "/", nil)
   633  	r.Header.Set("Accept-Encoding", "gzip")
   634  	w := httptest.NewRecorder()
   635  	handler.ServeHTTP(w, r)
   636  
   637  	res := w.Result()
   638  	assertEqual(t, http.StatusOK, res.StatusCode)
   639  	assertEqual(t, "", res.Header.Get("Content-Encoding"))
   640  	assertEqual(t, b, w.Body.Bytes())
   641  }
   642  
   643  func TestImplementCloseNotifier(t *testing.T) {
   644  	request := httptest.NewRequest(http.MethodGet, "/", nil)
   645  	request.Header.Set(acceptEncoding, "gzip")
   646  	GzipHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   647  		_, ok := rw.(http.CloseNotifier)
   648  		// response writer must implement http.CloseNotifier
   649  		assertEqual(t, true, ok)
   650  	})).ServeHTTP(&mockRWCloseNotify{}, request)
   651  }
   652  
   653  func TestImplementFlusherAndCloseNotifier(t *testing.T) {
   654  	request := httptest.NewRequest(http.MethodGet, "/", nil)
   655  	request.Header.Set(acceptEncoding, "gzip")
   656  	GzipHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   657  		_, okCloseNotifier := rw.(http.CloseNotifier)
   658  		// response writer must implement http.CloseNotifier
   659  		assertEqual(t, true, okCloseNotifier)
   660  		_, okFlusher := rw.(http.Flusher)
   661  		// "response writer must implement http.Flusher"
   662  		assertEqual(t, true, okFlusher)
   663  	})).ServeHTTP(&mockRWCloseNotify{}, request)
   664  }
   665  
   666  func TestNotImplementCloseNotifier(t *testing.T) {
   667  	request := httptest.NewRequest(http.MethodGet, "/", nil)
   668  	request.Header.Set(acceptEncoding, "gzip")
   669  	GzipHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   670  		_, ok := rw.(http.CloseNotifier)
   671  		// response writer must not implement http.CloseNotifier
   672  		assertEqual(t, false, ok)
   673  	})).ServeHTTP(httptest.NewRecorder(), request)
   674  }
   675  
   676  type mockRWCloseNotify struct{}
   677  
   678  func (m *mockRWCloseNotify) CloseNotify() <-chan bool {
   679  	panic("implement me")
   680  }
   681  
   682  func (m *mockRWCloseNotify) Header() http.Header {
   683  	return http.Header{}
   684  }
   685  
   686  func (m *mockRWCloseNotify) Write([]byte) (int, error) {
   687  	panic("implement me")
   688  }
   689  
   690  func (m *mockRWCloseNotify) WriteHeader(int) {
   691  	panic("implement me")
   692  }
   693  
   694  func TestIgnoreSubsequentWriteHeader(t *testing.T) {
   695  	handler := GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   696  		w.WriteHeader(500)
   697  		w.WriteHeader(404)
   698  	}))
   699  	r := httptest.NewRequest("GET", "/", nil)
   700  	r.Header.Set("Accept-Encoding", "gzip")
   701  	w := httptest.NewRecorder()
   702  	handler.ServeHTTP(w, r)
   703  
   704  	result := w.Result()
   705  	if result.StatusCode != 500 {
   706  		t.Errorf("StatusCode should have been 500 but was %d", result.StatusCode)
   707  	}
   708  }
   709  
   710  func TestDontWriteWhenNotWrittenTo(t *testing.T) {
   711  	// When using gzip as middleware without ANY writes in the handler,
   712  	// ensure the gzip middleware doesn't touch the actual ResponseWriter
   713  	// either.
   714  
   715  	handler0 := GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   716  	}))
   717  
   718  	handler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   719  		handler0.ServeHTTP(w, r)
   720  		w.WriteHeader(404) // this only works if gzip didn't do a WriteHeader(200)
   721  	})
   722  
   723  	r := httptest.NewRequest("GET", "/", nil)
   724  	r.Header.Set("Accept-Encoding", "gzip")
   725  	w := httptest.NewRecorder()
   726  	handler1.ServeHTTP(w, r)
   727  
   728  	result := w.Result()
   729  	if result.StatusCode != 404 {
   730  		t.Errorf("StatusCode should have been 404 but was %d", result.StatusCode)
   731  	}
   732  }
   733  
   734  var contentTypeTests = []struct {
   735  	name                 string
   736  	contentType          string
   737  	acceptedContentTypes []string
   738  	expectedGzip         bool
   739  }{
   740  	{
   741  		name:                 "Always gzip when content types are empty",
   742  		contentType:          "",
   743  		acceptedContentTypes: []string{},
   744  		expectedGzip:         true,
   745  	},
   746  	{
   747  		name:                 "MIME match",
   748  		contentType:          "application/json",
   749  		acceptedContentTypes: []string{"application/json"},
   750  		expectedGzip:         true,
   751  	},
   752  	{
   753  		name:                 "MIME no match",
   754  		contentType:          "text/xml",
   755  		acceptedContentTypes: []string{"application/json"},
   756  		expectedGzip:         false,
   757  	},
   758  	{
   759  		name:                 "MIME match with no other directive ignores non-MIME directives",
   760  		contentType:          "application/json; charset=utf-8",
   761  		acceptedContentTypes: []string{"application/json"},
   762  		expectedGzip:         true,
   763  	},
   764  	{
   765  		name:                 "MIME match with other directives requires all directives be equal, different charset",
   766  		contentType:          "application/json; charset=ascii",
   767  		acceptedContentTypes: []string{"application/json; charset=utf-8"},
   768  		expectedGzip:         false,
   769  	},
   770  	{
   771  		name:                 "MIME match with other directives requires all directives be equal, same charset",
   772  		contentType:          "application/json; charset=utf-8",
   773  		acceptedContentTypes: []string{"application/json; charset=utf-8"},
   774  		expectedGzip:         true,
   775  	},
   776  	{
   777  		name:                 "MIME match with other directives requires all directives be equal, missing charset",
   778  		contentType:          "application/json",
   779  		acceptedContentTypes: []string{"application/json; charset=ascii"},
   780  		expectedGzip:         false,
   781  	},
   782  	{
   783  		name:                 "MIME match case insensitive",
   784  		contentType:          "Application/Json",
   785  		acceptedContentTypes: []string{"application/json"},
   786  		expectedGzip:         true,
   787  	},
   788  	{
   789  		name:                 "MIME match ignore whitespace",
   790  		contentType:          "application/json;charset=utf-8",
   791  		acceptedContentTypes: []string{"application/json;            charset=utf-8"},
   792  		expectedGzip:         true,
   793  	},
   794  }
   795  
   796  func TestContentTypes(t *testing.T) {
   797  	for _, tt := range contentTypeTests {
   798  		t.Run(tt.name, func(t *testing.T) {
   799  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   800  				w.WriteHeader(http.StatusOK)
   801  				w.Header().Set("Content-Type", tt.contentType)
   802  				w.Write(testBody)
   803  			})
   804  
   805  			wrapper, err := NewWrapper(ContentTypes(tt.acceptedContentTypes))
   806  			assertNil(t, err)
   807  
   808  			req, _ := http.NewRequest("GET", "/whatever", nil)
   809  			req.Header.Set("Accept-Encoding", "gzip")
   810  			resp := httptest.NewRecorder()
   811  			wrapper(handler).ServeHTTP(resp, req)
   812  			res := resp.Result()
   813  
   814  			assertEqual(t, 200, res.StatusCode)
   815  			if tt.expectedGzip {
   816  				assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   817  			} else {
   818  				assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   819  			}
   820  		})
   821  		t.Run("not-"+tt.name, func(t *testing.T) {
   822  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   823  				w.WriteHeader(http.StatusOK)
   824  				w.Header().Set("Content-Type", tt.contentType)
   825  				w.Write(testBody)
   826  			})
   827  
   828  			wrapper, err := NewWrapper(ExceptContentTypes(tt.acceptedContentTypes))
   829  			assertNil(t, err)
   830  
   831  			req, _ := http.NewRequest("GET", "/whatever", nil)
   832  			req.Header.Set("Accept-Encoding", "gzip")
   833  			resp := httptest.NewRecorder()
   834  			wrapper(handler).ServeHTTP(resp, req)
   835  			res := resp.Result()
   836  
   837  			assertEqual(t, 200, res.StatusCode)
   838  			if !tt.expectedGzip {
   839  				assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   840  			} else {
   841  				assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   842  			}
   843  		})
   844  		t.Run("disable-"+tt.name, func(t *testing.T) {
   845  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   846  				w.Header().Set("Content-Type", tt.contentType)
   847  				w.Header().Set(HeaderNoCompression, "plz")
   848  				w.WriteHeader(http.StatusOK)
   849  				w.Write(testBody)
   850  			})
   851  
   852  			wrapper, err := NewWrapper(ContentTypes(tt.acceptedContentTypes))
   853  			assertNil(t, err)
   854  
   855  			req, _ := http.NewRequest("GET", "/whatever", nil)
   856  			req.Header.Set("Accept-Encoding", "gzip")
   857  			resp := httptest.NewRecorder()
   858  			wrapper(handler).ServeHTTP(resp, req)
   859  			res := resp.Result()
   860  
   861  			assertEqual(t, 200, res.StatusCode)
   862  			assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   863  			_, ok := res.Header[HeaderNoCompression]
   864  			assertEqual(t, false, ok)
   865  		})
   866  		t.Run("head-req"+tt.name, func(t *testing.T) {
   867  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   868  				w.Header().Set("Content-Type", tt.contentType)
   869  				w.Header().Set(HeaderNoCompression, "plz")
   870  				w.WriteHeader(http.StatusOK)
   871  			})
   872  
   873  			wrapper, err := NewWrapper(ContentTypes(tt.acceptedContentTypes))
   874  			assertNil(t, err)
   875  
   876  			req, _ := http.NewRequest("HEAD", "/whatever", nil)
   877  			req.Header.Set("Accept-Encoding", "gzip")
   878  			resp := httptest.NewRecorder()
   879  			wrapper(handler).ServeHTTP(resp, req)
   880  			res := resp.Result()
   881  
   882  			assertEqual(t, 200, res.StatusCode)
   883  			assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   884  			_, ok := res.Header[HeaderNoCompression]
   885  			assertEqual(t, false, ok)
   886  		})
   887  		t.Run("head-req-no-ok"+tt.name, func(t *testing.T) {
   888  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   889  				w.Header().Set("Content-Type", tt.contentType)
   890  				w.Header().Set(HeaderNoCompression, "plz")
   891  			})
   892  
   893  			wrapper, err := NewWrapper(ContentTypes(tt.acceptedContentTypes))
   894  			assertNil(t, err)
   895  
   896  			req, _ := http.NewRequest("HEAD", "/whatever", nil)
   897  			req.Header.Set("Accept-Encoding", "gzip")
   898  			resp := httptest.NewRecorder()
   899  			wrapper(handler).ServeHTTP(resp, req)
   900  			res := resp.Result()
   901  
   902  			assertEqual(t, 200, res.StatusCode)
   903  			assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   904  			_, ok := res.Header[HeaderNoCompression]
   905  			assertEqual(t, false, ok)
   906  		})
   907  		t.Run("req-no-ok-write"+tt.name, func(t *testing.T) {
   908  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   909  				w.Header().Set("Content-Type", tt.contentType)
   910  				w.Header().Set(HeaderNoCompression, "plz")
   911  				w.Write(testBody)
   912  			})
   913  
   914  			wrapper, err := NewWrapper(ContentTypes(tt.acceptedContentTypes))
   915  			assertNil(t, err)
   916  
   917  			req, _ := http.NewRequest("GET", "/whatever", nil)
   918  			req.Header.Set("Accept-Encoding", "")
   919  			resp := httptest.NewRecorder()
   920  			wrapper(handler).ServeHTTP(resp, req)
   921  			res := resp.Result()
   922  
   923  			assertEqual(t, 200, res.StatusCode)
   924  			assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   925  			_, ok := res.Header[HeaderNoCompression]
   926  			assertEqual(t, false, ok)
   927  		})
   928  	}
   929  }
   930  
   931  func TestFlush(t *testing.T) {
   932  	for _, tt := range contentTypeTests {
   933  		t.Run(tt.name, func(t *testing.T) {
   934  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   935  				w.WriteHeader(http.StatusOK)
   936  				w.Header().Set("Content-Type", tt.contentType)
   937  				tb := testBody
   938  				for len(tb) > 0 {
   939  					// Write 100 bytes per run
   940  					// Detection should not be affected (we send 100 bytes)
   941  					toWrite := 100
   942  					if toWrite > len(tb) {
   943  						toWrite = len(tb)
   944  					}
   945  					_, err := w.Write(tb[:toWrite])
   946  					if err != nil {
   947  						t.Fatal(err)
   948  					}
   949  					// Flush between each write
   950  					w.(http.Flusher).Flush()
   951  					tb = tb[toWrite:]
   952  				}
   953  			})
   954  
   955  			wrapper, err := NewWrapper(ContentTypes(tt.acceptedContentTypes))
   956  			assertNil(t, err)
   957  
   958  			req, _ := http.NewRequest("GET", "/whatever", nil)
   959  			req.Header.Set("Accept-Encoding", "gzip")
   960  			// This doesn't allow checking flushes, but we validate if content is correct.
   961  			resp := httptest.NewRecorder()
   962  			wrapper(handler).ServeHTTP(resp, req)
   963  			res := resp.Result()
   964  
   965  			assertEqual(t, 200, res.StatusCode)
   966  			if tt.expectedGzip {
   967  				assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   968  				zr, err := gzip.NewReader(resp.Body)
   969  				assertNil(t, err)
   970  				got, err := io.ReadAll(zr)
   971  				assertNil(t, err)
   972  				assertEqual(t, testBody, got)
   973  
   974  			} else {
   975  				assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
   976  				got, err := io.ReadAll(resp.Body)
   977  				assertNil(t, err)
   978  				assertEqual(t, testBody, got)
   979  			}
   980  		})
   981  	}
   982  }
   983  
   984  func TestRandomJitter(t *testing.T) {
   985  	r := httptest.NewRequest("GET", "/", nil)
   986  	r.Header.Set("Accept-Encoding", "gzip")
   987  
   988  	// 4KB input, incompressible to avoid compression variations.
   989  	rng := rand.New(rand.NewSource(0))
   990  	payload := make([]byte, 4096)
   991  	_, err := io.ReadFull(rng, payload)
   992  	if err != nil {
   993  		t.Fatal(err)
   994  	}
   995  
   996  	wrapper, err := NewWrapper(RandomJitter(256, 1024, false), MinSize(10))
   997  	if err != nil {
   998  		t.Fatal(err)
   999  	}
  1000  	writePayload := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1001  		w.Write(payload)
  1002  	})
  1003  	referenceHandler := GzipHandler(writePayload)
  1004  	w := httptest.NewRecorder()
  1005  	referenceHandler.ServeHTTP(w, r)
  1006  	result := w.Result()
  1007  	refBody, err := io.ReadAll(result.Body)
  1008  	if err != nil {
  1009  		t.Fatal(err)
  1010  	}
  1011  	t.Logf("Unmodified length: %d", len(refBody))
  1012  
  1013  	handler := wrapper(writePayload)
  1014  	w = httptest.NewRecorder()
  1015  	handler.ServeHTTP(w, r)
  1016  
  1017  	result = w.Result()
  1018  	b, err := io.ReadAll(result.Body)
  1019  	if err != nil {
  1020  		t.Fatal(err)
  1021  	}
  1022  
  1023  	if len(refBody) == len(b) {
  1024  		t.Fatal("padding was not applied")
  1025  	}
  1026  
  1027  	if err != nil {
  1028  		t.Fatal(err)
  1029  	}
  1030  	changed := false
  1031  	for i := 0; i < 10; i++ {
  1032  		w = httptest.NewRecorder()
  1033  		handler.ServeHTTP(w, r)
  1034  		result = w.Result()
  1035  		b2, err := io.ReadAll(result.Body)
  1036  		if err != nil {
  1037  			t.Fatal(err)
  1038  		}
  1039  		changed = changed || len(b2) != len(b)
  1040  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-len(refBody))
  1041  		if len(b2) <= len(refBody) {
  1042  			t.Errorf("no padding applied,")
  1043  		}
  1044  		if i == 0 && changed {
  1045  			t.Error("length changed without payload change", len(b), "->", len(b2))
  1046  		}
  1047  		// Mutate...
  1048  		payload[0]++
  1049  		b = b2
  1050  	}
  1051  	if !changed {
  1052  		t.Errorf("no change after 9 attempts")
  1053  	}
  1054  
  1055  	// Write one byte at the time to test buffer flushing.
  1056  	handler = wrapper(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1057  		for i := range payload {
  1058  			w.Write([]byte{payload[i]})
  1059  		}
  1060  	}))
  1061  
  1062  	for i := 0; i < 10; i++ {
  1063  		w = httptest.NewRecorder()
  1064  		handler.ServeHTTP(w, r)
  1065  		result = w.Result()
  1066  		b2, err := io.ReadAll(result.Body)
  1067  		if err != nil {
  1068  			t.Fatal(err)
  1069  		}
  1070  
  1071  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-len(refBody))
  1072  		if len(b2) <= len(refBody) {
  1073  			t.Errorf("no padding applied,")
  1074  		}
  1075  		if i > 0 && len(b2) != len(b) {
  1076  			t.Error("length changed without payload change", len(b), "->", len(b2))
  1077  		}
  1078  		// Mutate, buf after the buffer...
  1079  		payload[2048]++
  1080  		b = b2
  1081  	}
  1082  
  1083  	// Write less than buffer
  1084  	handler = wrapper(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1085  		w.Write(payload[:512])
  1086  	}))
  1087  	changed = false
  1088  	for i := 0; i < 10; i++ {
  1089  		w = httptest.NewRecorder()
  1090  		handler.ServeHTTP(w, r)
  1091  		result = w.Result()
  1092  		b2, err := io.ReadAll(result.Body)
  1093  		if err != nil {
  1094  			t.Fatal(err)
  1095  		}
  1096  		if i > 0 {
  1097  			changed = changed || len(b2) != len(b)
  1098  		}
  1099  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-512)
  1100  		if len(b2) <= 512 {
  1101  			t.Errorf("no padding applied,")
  1102  		}
  1103  		// Mutate...
  1104  		payload[500]++
  1105  		b = b2
  1106  	}
  1107  	if !changed {
  1108  		t.Errorf("no change after 9 attempts")
  1109  	}
  1110  
  1111  	// Write less than buffer, with flush in between.
  1112  	// Checksum should be of all before flush.
  1113  	handler = wrapper(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1114  		w.Write(payload[:256])
  1115  		w.(http.Flusher).Flush()
  1116  		w.Write(payload[256:512])
  1117  	}))
  1118  
  1119  	changed = false
  1120  	for i := 0; i < 10; i++ {
  1121  		w = httptest.NewRecorder()
  1122  		handler.ServeHTTP(w, r)
  1123  		result = w.Result()
  1124  		b2, err := io.ReadAll(result.Body)
  1125  		if err != nil {
  1126  			t.Fatal(err)
  1127  		}
  1128  		if i > 0 {
  1129  			changed = changed || len(b2) != len(b)
  1130  		}
  1131  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-512)
  1132  		if len(b2) <= 512 {
  1133  			t.Errorf("no padding applied,")
  1134  		}
  1135  		// Mutate...
  1136  		payload[200]++
  1137  		b = b2
  1138  	}
  1139  	if !changed {
  1140  		t.Errorf("no change after 9 attempts")
  1141  	}
  1142  
  1143  	// Mutate *after* the flush.
  1144  	// Should no longer affect length.
  1145  	for i := 0; i < 10; i++ {
  1146  		w = httptest.NewRecorder()
  1147  		handler.ServeHTTP(w, r)
  1148  		result = w.Result()
  1149  		b2, err := io.ReadAll(result.Body)
  1150  		if err != nil {
  1151  			t.Fatal(err)
  1152  		}
  1153  		if i > 0 {
  1154  			changed = len(b2) != len(b)
  1155  			if changed {
  1156  				t.Errorf("mutating after flush seems to have affected output")
  1157  			}
  1158  		}
  1159  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-512)
  1160  		if len(b2) <= 512 {
  1161  			t.Errorf("no padding applied,")
  1162  		}
  1163  		// Mutate...
  1164  		payload[400]++
  1165  		b = b2
  1166  	}
  1167  
  1168  	// Test non-content aware jitter
  1169  	wrapper, err = NewWrapper(RandomJitter(256, -1, false), MinSize(10))
  1170  	if err != nil {
  1171  		t.Fatal(err)
  1172  	}
  1173  	handler = wrapper(writePayload)
  1174  	changed = false
  1175  	for i := 0; i < 10; i++ {
  1176  		w = httptest.NewRecorder()
  1177  		handler.ServeHTTP(w, r)
  1178  		result = w.Result()
  1179  		b2, err := io.ReadAll(result.Body)
  1180  		if err != nil {
  1181  			t.Fatal(err)
  1182  		}
  1183  		if i > 0 {
  1184  			changed = changed || len(b2) != len(b)
  1185  		}
  1186  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-len(refBody))
  1187  		if len(b2) <= len(refBody) {
  1188  			t.Errorf("no padding applied,")
  1189  		}
  1190  
  1191  		// Do not mutate...
  1192  		// Update last payload.
  1193  		b = b2
  1194  	}
  1195  	if !changed {
  1196  		t.Errorf("no change after 9 attempts")
  1197  	}
  1198  }
  1199  
  1200  func TestRandomJitterParanoid(t *testing.T) {
  1201  	r := httptest.NewRequest("GET", "/", nil)
  1202  	r.Header.Set("Accept-Encoding", "gzip")
  1203  
  1204  	// 4KB input, incompressible to avoid compression variations.
  1205  	rng := rand.New(rand.NewSource(0))
  1206  	payload := make([]byte, 4096)
  1207  	_, err := io.ReadFull(rng, payload)
  1208  	if err != nil {
  1209  		t.Fatal(err)
  1210  	}
  1211  
  1212  	wrapper, err := NewWrapper(RandomJitter(256, 1024, true), MinSize(10))
  1213  	if err != nil {
  1214  		t.Fatal(err)
  1215  	}
  1216  	writePayload := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1217  		w.Write(payload)
  1218  	})
  1219  	referenceHandler := GzipHandler(writePayload)
  1220  	w := httptest.NewRecorder()
  1221  	referenceHandler.ServeHTTP(w, r)
  1222  	result := w.Result()
  1223  	refBody, err := io.ReadAll(result.Body)
  1224  	if err != nil {
  1225  		t.Fatal(err)
  1226  	}
  1227  	t.Logf("Unmodified length: %d", len(refBody))
  1228  
  1229  	handler := wrapper(writePayload)
  1230  	w = httptest.NewRecorder()
  1231  	handler.ServeHTTP(w, r)
  1232  
  1233  	result = w.Result()
  1234  	b, err := io.ReadAll(result.Body)
  1235  	if err != nil {
  1236  		t.Fatal(err)
  1237  	}
  1238  
  1239  	if len(refBody) == len(b) {
  1240  		t.Fatal("padding was not applied")
  1241  	}
  1242  
  1243  	if err != nil {
  1244  		t.Fatal(err)
  1245  	}
  1246  	changed := false
  1247  	for i := 0; i < 10; i++ {
  1248  		w = httptest.NewRecorder()
  1249  		handler.ServeHTTP(w, r)
  1250  		result = w.Result()
  1251  		b2, err := io.ReadAll(result.Body)
  1252  		if err != nil {
  1253  			t.Fatal(err)
  1254  		}
  1255  		changed = changed || len(b2) != len(b)
  1256  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-len(refBody))
  1257  		if len(b2) <= len(refBody) {
  1258  			t.Errorf("no padding applied,")
  1259  		}
  1260  		if i == 0 && changed {
  1261  			t.Error("length changed without payload change", len(b), "->", len(b2))
  1262  		}
  1263  		// Mutate...
  1264  		payload[0]++
  1265  		b = b2
  1266  	}
  1267  	if !changed {
  1268  		t.Errorf("no change after 9 attempts")
  1269  	}
  1270  
  1271  	// Write one byte at the time to test buffer flushing.
  1272  	handler = wrapper(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1273  		for i := range payload {
  1274  			w.Write([]byte{payload[i]})
  1275  		}
  1276  	}))
  1277  
  1278  	for i := 0; i < 10; i++ {
  1279  		w = httptest.NewRecorder()
  1280  		handler.ServeHTTP(w, r)
  1281  		result = w.Result()
  1282  		b2, err := io.ReadAll(result.Body)
  1283  		if err != nil {
  1284  			t.Fatal(err)
  1285  		}
  1286  
  1287  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-len(refBody))
  1288  		if len(b2) <= len(refBody) {
  1289  			t.Errorf("no padding applied,")
  1290  		}
  1291  		if i > 0 && len(b2) != len(b) {
  1292  			t.Error("length changed without payload change", len(b), "->", len(b2))
  1293  		}
  1294  		// Mutate, buf after the buffer...
  1295  		payload[2048]++
  1296  		b = b2
  1297  	}
  1298  
  1299  	// Write less than buffer
  1300  	handler = wrapper(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1301  		w.Write(payload[:512])
  1302  	}))
  1303  	changed = false
  1304  	for i := 0; i < 10; i++ {
  1305  		w = httptest.NewRecorder()
  1306  		handler.ServeHTTP(w, r)
  1307  		result = w.Result()
  1308  		b2, err := io.ReadAll(result.Body)
  1309  		if err != nil {
  1310  			t.Fatal(err)
  1311  		}
  1312  		if i > 0 {
  1313  			changed = changed || len(b2) != len(b)
  1314  		}
  1315  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-512)
  1316  		if len(b2) <= 512 {
  1317  			t.Errorf("no padding applied,")
  1318  		}
  1319  		// Mutate...
  1320  		payload[500]++
  1321  		b = b2
  1322  	}
  1323  	if !changed {
  1324  		t.Errorf("no change after 9 attempts")
  1325  	}
  1326  
  1327  	// Write less than buffer, with flush in between.
  1328  	// Checksum should be of all before flush.
  1329  	handler = wrapper(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1330  		w.Write(payload[:256])
  1331  		w.(http.Flusher).Flush()
  1332  		w.Write(payload[256:512])
  1333  	}))
  1334  
  1335  	changed = false
  1336  	for i := 0; i < 10; i++ {
  1337  		w = httptest.NewRecorder()
  1338  		handler.ServeHTTP(w, r)
  1339  		result = w.Result()
  1340  		b2, err := io.ReadAll(result.Body)
  1341  		if err != nil {
  1342  			t.Fatal(err)
  1343  		}
  1344  		if i > 0 {
  1345  			changed = changed || len(b2) != len(b)
  1346  		}
  1347  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-512)
  1348  		if len(b2) <= 512 {
  1349  			t.Errorf("no padding applied,")
  1350  		}
  1351  		// Mutate...
  1352  		payload[200]++
  1353  		b = b2
  1354  	}
  1355  	if !changed {
  1356  		t.Errorf("no change after 9 attempts")
  1357  	}
  1358  
  1359  	// Mutate *after* the flush.
  1360  	// Should no longer affect length.
  1361  	for i := 0; i < 10; i++ {
  1362  		w = httptest.NewRecorder()
  1363  		handler.ServeHTTP(w, r)
  1364  		result = w.Result()
  1365  		b2, err := io.ReadAll(result.Body)
  1366  		if err != nil {
  1367  			t.Fatal(err)
  1368  		}
  1369  		if i > 0 {
  1370  			changed = len(b2) != len(b)
  1371  			if changed {
  1372  				t.Errorf("mutating after flush seems to have affected output")
  1373  			}
  1374  		}
  1375  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-512)
  1376  		if len(b2) <= 512 {
  1377  			t.Errorf("no padding applied,")
  1378  		}
  1379  		// Mutate...
  1380  		payload[400]++
  1381  		b = b2
  1382  	}
  1383  
  1384  	// Test non-content aware jitter
  1385  	wrapper, err = NewWrapper(RandomJitter(256, -1, true), MinSize(10))
  1386  	if err != nil {
  1387  		t.Fatal(err)
  1388  	}
  1389  	handler = wrapper(writePayload)
  1390  	changed = false
  1391  	for i := 0; i < 10; i++ {
  1392  		w = httptest.NewRecorder()
  1393  		handler.ServeHTTP(w, r)
  1394  		result = w.Result()
  1395  		b2, err := io.ReadAll(result.Body)
  1396  		if err != nil {
  1397  			t.Fatal(err)
  1398  		}
  1399  		if i > 0 {
  1400  			changed = changed || len(b2) != len(b)
  1401  		}
  1402  		t.Logf("attempt %d length: %d. padding: %d.", i, len(b2), len(b2)-len(refBody))
  1403  		if len(b2) <= len(refBody) {
  1404  			t.Errorf("no padding applied,")
  1405  		}
  1406  
  1407  		// Do not mutate...
  1408  		// Update last payload.
  1409  		b = b2
  1410  	}
  1411  	if !changed {
  1412  		t.Errorf("no change after 9 attempts")
  1413  	}
  1414  }
  1415  
  1416  var contentTypeTest2 = []struct {
  1417  	name         string
  1418  	contentType  string
  1419  	expectedGzip bool
  1420  }{
  1421  	{
  1422  		name:         "Always gzip when content types are empty",
  1423  		contentType:  "",
  1424  		expectedGzip: true,
  1425  	},
  1426  	{
  1427  		name:         "MIME match",
  1428  		contentType:  "application/json",
  1429  		expectedGzip: true,
  1430  	},
  1431  	{
  1432  		name:         "MIME no match",
  1433  		contentType:  "text/xml",
  1434  		expectedGzip: true,
  1435  	},
  1436  
  1437  	{
  1438  		name:         "MIME match case insensitive",
  1439  		contentType:  "Video/Something",
  1440  		expectedGzip: false,
  1441  	},
  1442  	{
  1443  		name:         "MIME match case insensitive",
  1444  		contentType:  "audio/Something",
  1445  		expectedGzip: false,
  1446  	},
  1447  	{
  1448  		name:         "MIME match ignore whitespace",
  1449  		contentType:  " video/mp4",
  1450  		expectedGzip: false,
  1451  	},
  1452  	{
  1453  		name:         "without prefix..",
  1454  		contentType:  "avideo/mp4",
  1455  		expectedGzip: true,
  1456  	},
  1457  	{
  1458  		name:         "application/zip",
  1459  		contentType:  "application/zip;lalala",
  1460  		expectedGzip: false,
  1461  	},
  1462  	{
  1463  		name:         "x-zip-compressed",
  1464  		contentType:  "application/x-zip-compressed",
  1465  		expectedGzip: false,
  1466  	},
  1467  	{
  1468  		name:         "application/x-gzip",
  1469  		contentType:  "application/x-gzip",
  1470  		expectedGzip: false,
  1471  	},
  1472  }
  1473  
  1474  func TestDefaultContentTypes(t *testing.T) {
  1475  	for _, tt := range contentTypeTest2 {
  1476  		t.Run(tt.name, func(t *testing.T) {
  1477  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1478  				w.WriteHeader(http.StatusOK)
  1479  				w.Header().Set("Content-Type", tt.contentType)
  1480  				w.Write(testBody)
  1481  			})
  1482  
  1483  			wrapper, err := NewWrapper()
  1484  			assertNil(t, err)
  1485  
  1486  			req, _ := http.NewRequest("GET", "/whatever", nil)
  1487  			req.Header.Set("Accept-Encoding", "gzip")
  1488  			resp := httptest.NewRecorder()
  1489  			wrapper(handler).ServeHTTP(resp, req)
  1490  			res := resp.Result()
  1491  
  1492  			assertEqual(t, 200, res.StatusCode)
  1493  			if tt.expectedGzip {
  1494  				assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
  1495  			} else {
  1496  				assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
  1497  			}
  1498  		})
  1499  	}
  1500  }
  1501  
  1502  var sniffTests = []struct {
  1503  	desc        string
  1504  	data        []byte
  1505  	contentType string
  1506  }{
  1507  	// Some nonsense.
  1508  	{"Empty", []byte{}, "text/plain; charset=utf-8"},
  1509  	{"Binary", []byte{1, 2, 3}, "application/octet-stream"},
  1510  
  1511  	{"HTML document #1", []byte(`<HtMl><bOdY>blah blah blah</body></html>`), "text/html; charset=utf-8"},
  1512  	{"HTML document #2", []byte(`<HTML></HTML>`), "text/html; charset=utf-8"},
  1513  	{"HTML document #3 (leading whitespace)", []byte(`   <!DOCTYPE HTML>...`), "text/html; charset=utf-8"},
  1514  	{"HTML document #4 (leading CRLF)", []byte("\r\n<html>..."), "text/html; charset=utf-8"},
  1515  
  1516  	{"Plain text", []byte(`This is not HTML. It has ☃ though.`), "text/plain; charset=utf-8"},
  1517  
  1518  	{"XML", []byte("\n<?xml!"), "text/xml; charset=utf-8"},
  1519  
  1520  	// Image types.
  1521  	{"Windows icon", []byte("\x00\x00\x01\x00"), "image/x-icon"},
  1522  	{"Windows cursor", []byte("\x00\x00\x02\x00"), "image/x-icon"},
  1523  	{"BMP image", []byte("BM..."), "image/bmp"},
  1524  	{"GIF 87a", []byte(`GIF87a`), "image/gif"},
  1525  	{"GIF 89a", []byte(`GIF89a...`), "image/gif"},
  1526  	{"WEBP image", []byte("RIFF\x00\x00\x00\x00WEBPVP"), "image/webp"},
  1527  	{"PNG image", []byte("\x89PNG\x0D\x0A\x1A\x0A"), "image/png"},
  1528  	{"JPEG image", []byte("\xFF\xD8\xFF"), "image/jpeg"},
  1529  
  1530  	// Audio types.
  1531  	{"MIDI audio", []byte("MThd\x00\x00\x00\x06\x00\x01"), "audio/midi"},
  1532  	{"MP3 audio/MPEG audio", []byte("ID3\x03\x00\x00\x00\x00\x0f"), "audio/mpeg"},
  1533  	{"WAV audio #1", []byte("RIFFb\xb8\x00\x00WAVEfmt \x12\x00\x00\x00\x06"), "audio/wave"},
  1534  	{"WAV audio #2", []byte("RIFF,\x00\x00\x00WAVEfmt \x12\x00\x00\x00\x06"), "audio/wave"},
  1535  	{"AIFF audio #1", []byte("FORM\x00\x00\x00\x00AIFFCOMM\x00\x00\x00\x12\x00\x01\x00\x00\x57\x55\x00\x10\x40\x0d\xf3\x34"), "audio/aiff"},
  1536  
  1537  	{"OGG audio", []byte("OggS\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x7e\x46\x00\x00\x00\x00\x00\x00\x1f\xf6\xb4\xfc\x01\x1e\x01\x76\x6f\x72"), "application/ogg"},
  1538  	{"Must not match OGG", []byte("owow\x00"), "application/octet-stream"},
  1539  	{"Must not match OGG", []byte("oooS\x00"), "application/octet-stream"},
  1540  	{"Must not match OGG", []byte("oggS\x00"), "application/octet-stream"},
  1541  
  1542  	// Video types.
  1543  	{"MP4 video", []byte("\x00\x00\x00\x18ftypmp42\x00\x00\x00\x00mp42isom<\x06t\xbfmdat"), "video/mp4"},
  1544  	{"AVI video #1", []byte("RIFF,O\n\x00AVI LISTÀ"), "video/avi"},
  1545  	{"AVI video #2", []byte("RIFF,\n\x00\x00AVI LISTÀ"), "video/avi"},
  1546  
  1547  	// Font types.
  1548  	// {"MS.FontObject", []byte("\x00\x00")},
  1549  	{"TTF sample  I", []byte("\x00\x01\x00\x00\x00\x17\x01\x00\x00\x04\x01\x60\x4f"), "font/ttf"},
  1550  	{"TTF sample II", []byte("\x00\x01\x00\x00\x00\x0e\x00\x80\x00\x03\x00\x60\x46"), "font/ttf"},
  1551  
  1552  	{"OTTO sample  I", []byte("\x4f\x54\x54\x4f\x00\x0e\x00\x80\x00\x03\x00\x60\x42\x41\x53\x45"), "font/otf"},
  1553  
  1554  	{"woff sample  I", []byte("\x77\x4f\x46\x46\x00\x01\x00\x00\x00\x00\x30\x54\x00\x0d\x00\x00"), "font/woff"},
  1555  	{"woff2 sample", []byte("\x77\x4f\x46\x32\x00\x01\x00\x00\x00"), "font/woff2"},
  1556  	{"wasm sample", []byte("\x00\x61\x73\x6d\x01\x00"), "application/wasm"},
  1557  
  1558  	// Archive types
  1559  	{"RAR v1.5-v4.0", []byte("Rar!\x1A\x07\x00"), "application/x-rar-compressed"},
  1560  	{"RAR v5+", []byte("Rar!\x1A\x07\x01\x00"), "application/x-rar-compressed"},
  1561  	{"Incorrect RAR v1.5-v4.0", []byte("Rar \x1A\x07\x00"), "application/octet-stream"},
  1562  	{"Incorrect RAR v5+", []byte("Rar \x1A\x07\x01\x00"), "application/octet-stream"},
  1563  }
  1564  
  1565  func TestContentTypeDetect(t *testing.T) {
  1566  	for _, tt := range sniffTests {
  1567  		t.Run(tt.desc, func(t *testing.T) {
  1568  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1569  				w.WriteHeader(http.StatusOK)
  1570  				for i := range tt.data {
  1571  					// Do one byte writes...
  1572  					w.Write([]byte{tt.data[i]})
  1573  				}
  1574  				w.Write(testBody)
  1575  			})
  1576  
  1577  			wrapper, err := NewWrapper()
  1578  			assertNil(t, err)
  1579  
  1580  			req, _ := http.NewRequest("GET", "/whatever", nil)
  1581  			req.Header.Set("Accept-Encoding", "gzip")
  1582  			resp := httptest.NewRecorder()
  1583  			wrapper(handler).ServeHTTP(resp, req)
  1584  			res := resp.Result()
  1585  
  1586  			assertEqual(t, 200, res.StatusCode)
  1587  			assertEqual(t, tt.contentType, res.Header.Get("Content-Type"))
  1588  			shouldGZ := DefaultContentTypeFilter(tt.contentType)
  1589  			if shouldGZ {
  1590  				assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
  1591  			} else {
  1592  				assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
  1593  			}
  1594  		})
  1595  		t.Run(tt.desc+"empty", func(t *testing.T) {
  1596  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1597  				w.Header().Set("Content-Type", "")
  1598  				w.WriteHeader(http.StatusOK)
  1599  				for i := range tt.data {
  1600  					// Do one byte writes...
  1601  					w.Write([]byte{tt.data[i]})
  1602  				}
  1603  				w.Write(testBody)
  1604  			})
  1605  
  1606  			wrapper, err := NewWrapper()
  1607  			assertNil(t, err)
  1608  
  1609  			req, _ := http.NewRequest("GET", "/whatever", nil)
  1610  			req.Header.Set("Accept-Encoding", "gzip")
  1611  			resp := httptest.NewRecorder()
  1612  			wrapper(handler).ServeHTTP(resp, req)
  1613  			res := resp.Result()
  1614  
  1615  			assertEqual(t, 200, res.StatusCode)
  1616  			// Is Content-Type still empty?
  1617  			assertEqual(t, "", res.Header.Get("Content-Type"))
  1618  			shouldGZ := DefaultContentTypeFilter(tt.contentType)
  1619  			if shouldGZ {
  1620  				assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
  1621  			} else {
  1622  				assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
  1623  			}
  1624  		})
  1625  		t.Run(tt.desc+"flush", func(t *testing.T) {
  1626  			handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1627  				w.Header().Set("Content-Type", "")
  1628  				w.WriteHeader(http.StatusOK)
  1629  				for i := range tt.data {
  1630  					// Do one byte writes...
  1631  					w.Write([]byte{tt.data[i]})
  1632  				}
  1633  				w.(http.Flusher).Flush()
  1634  				w.Write(testBody)
  1635  			})
  1636  
  1637  			wrapper, err := NewWrapper()
  1638  			assertNil(t, err)
  1639  
  1640  			req, _ := http.NewRequest("GET", "/whatever", nil)
  1641  			req.Header.Set("Accept-Encoding", "gzip")
  1642  			resp := httptest.NewRecorder()
  1643  			wrapper(handler).ServeHTTP(resp, req)
  1644  			res := resp.Result()
  1645  
  1646  			assertEqual(t, 200, res.StatusCode)
  1647  			// Is Content-Type still empty?
  1648  			assertEqual(t, "", res.Header.Get("Content-Type"))
  1649  			shouldGZ := DefaultContentTypeFilter(tt.contentType)
  1650  			if shouldGZ {
  1651  				assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
  1652  			} else {
  1653  				assertNotEqual(t, "gzip", res.Header.Get("Content-Encoding"))
  1654  			}
  1655  		})
  1656  	}
  1657  }
  1658  
  1659  // --------------------------------------------------------------------
  1660  
  1661  func BenchmarkGzipHandler_S2k(b *testing.B) {
  1662  	benchmark(b, false, 2048, CompressionLevel(gzip.DefaultCompression))
  1663  }
  1664  func BenchmarkGzipHandler_S20k(b *testing.B) {
  1665  	benchmark(b, false, 20480, CompressionLevel(gzip.DefaultCompression))
  1666  }
  1667  func BenchmarkGzipHandler_S100k(b *testing.B) {
  1668  	benchmark(b, false, 102400, CompressionLevel(gzip.DefaultCompression))
  1669  }
  1670  func BenchmarkGzipHandler_P2k(b *testing.B) {
  1671  	benchmark(b, true, 2048, CompressionLevel(gzip.DefaultCompression))
  1672  }
  1673  func BenchmarkGzipHandler_P20k(b *testing.B) {
  1674  	benchmark(b, true, 20480, CompressionLevel(gzip.DefaultCompression))
  1675  }
  1676  func BenchmarkGzipHandler_P100k(b *testing.B) {
  1677  	benchmark(b, true, 102400, CompressionLevel(gzip.DefaultCompression))
  1678  }
  1679  
  1680  func BenchmarkGzipBestSpeedHandler_S2k(b *testing.B) {
  1681  	benchmark(b, false, 2048, CompressionLevel(gzip.BestSpeed))
  1682  }
  1683  func BenchmarkGzipBestSpeedHandler_S20k(b *testing.B) {
  1684  	benchmark(b, false, 20480, CompressionLevel(gzip.BestSpeed))
  1685  }
  1686  func BenchmarkGzipBestSpeedHandler_S100k(b *testing.B) {
  1687  	benchmark(b, false, 102400, CompressionLevel(gzip.BestSpeed))
  1688  }
  1689  func BenchmarkGzipBestSpeedHandler_P2k(b *testing.B) {
  1690  	benchmark(b, true, 2048, CompressionLevel(gzip.BestSpeed))
  1691  }
  1692  func BenchmarkGzipBestSpeedHandler_P20k(b *testing.B) {
  1693  	benchmark(b, true, 20480, CompressionLevel(gzip.BestSpeed))
  1694  }
  1695  func BenchmarkGzipBestSpeedHandler_P100k(b *testing.B) {
  1696  	benchmark(b, true, 102400, CompressionLevel(gzip.BestSpeed))
  1697  }
  1698  
  1699  func Benchmark2kJitter(b *testing.B) {
  1700  	benchmark(b, false, 2048, CompressionLevel(gzip.BestSpeed), RandomJitter(32, 0, false))
  1701  }
  1702  
  1703  func Benchmark2kJitterParanoid(b *testing.B) {
  1704  	benchmark(b, false, 2048, CompressionLevel(gzip.BestSpeed), RandomJitter(32, 0, true))
  1705  }
  1706  
  1707  func Benchmark2kJitterRNG(b *testing.B) {
  1708  	benchmark(b, false, 2048, CompressionLevel(gzip.BestSpeed), RandomJitter(32, -1, false))
  1709  }
  1710  
  1711  // --------------------------------------------------------------------
  1712  
  1713  func gzipStrLevel(s []byte, lvl int) []byte {
  1714  	var b bytes.Buffer
  1715  	w, _ := gzip.NewWriterLevel(&b, lvl)
  1716  	w.Write(s)
  1717  	w.Close()
  1718  	return b.Bytes()
  1719  }
  1720  
  1721  func benchmark(b *testing.B, parallel bool, size int, opts ...option) {
  1722  	bin, err := os.ReadFile("testdata/benchmark.json")
  1723  	if err != nil {
  1724  		b.Fatal(err)
  1725  	}
  1726  
  1727  	req, _ := http.NewRequest("GET", "/whatever", nil)
  1728  	req.Header.Set("Accept-Encoding", "gzip")
  1729  	handler := newTestHandlerLevel(bin[:size], opts...)
  1730  
  1731  	b.ReportAllocs()
  1732  	b.SetBytes(int64(size))
  1733  	if parallel {
  1734  		b.ResetTimer()
  1735  		b.RunParallel(func(pb *testing.PB) {
  1736  			for pb.Next() {
  1737  				runBenchmark(b, req, handler)
  1738  			}
  1739  		})
  1740  	} else {
  1741  		b.ResetTimer()
  1742  		for i := 0; i < b.N; i++ {
  1743  			runBenchmark(b, req, handler)
  1744  		}
  1745  	}
  1746  }
  1747  
  1748  func runBenchmark(b *testing.B, req *http.Request, handler http.Handler) {
  1749  	res := httptest.NewRecorder()
  1750  	handler.ServeHTTP(res, req)
  1751  	if code := res.Code; code != 200 {
  1752  		b.Fatalf("Expected 200 but got %d", code)
  1753  	} else if blen := res.Body.Len(); blen < 500 {
  1754  		b.Fatalf("Expected complete response body, but got %d bytes", blen)
  1755  	}
  1756  }
  1757  
  1758  func newTestHandler(body []byte) http.Handler {
  1759  	return GzipHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1760  		switch r.URL.Path {
  1761  		case "/gzipped":
  1762  			w.Header().Set("Content-Encoding", "gzip")
  1763  			w.Write(body)
  1764  		case "/zstd":
  1765  			w.Header().Set("Content-Encoding", "zstd")
  1766  			w.Write(body)
  1767  		default:
  1768  			w.Write(body)
  1769  		}
  1770  	}))
  1771  }
  1772  
  1773  func newTestHandlerLevel(body []byte, opts ...option) http.Handler {
  1774  	wrapper, err := NewWrapper(opts...)
  1775  	if err != nil {
  1776  		panic(err)
  1777  	}
  1778  	return wrapper(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1779  		switch r.URL.Path {
  1780  		case "/gzipped":
  1781  			w.Header().Set("Content-Encoding", "gzip")
  1782  			w.Write(body)
  1783  		default:
  1784  			w.Write(body)
  1785  		}
  1786  	}))
  1787  }
  1788  
  1789  func TestGzipHandlerNilContentType(t *testing.T) {
  1790  	// This just exists to provide something for GzipHandler to wrap.
  1791  	handler := newTestHandler(testBody)
  1792  
  1793  	// content-type header not set when provided nil
  1794  
  1795  	req, _ := http.NewRequest("GET", "/whatever", nil)
  1796  	req.Header.Set("Accept-Encoding", "gzip")
  1797  	res := httptest.NewRecorder()
  1798  	res.Header()["Content-Type"] = nil
  1799  	handler.ServeHTTP(res, req)
  1800  
  1801  	assertEqual(t, "", res.Header().Get("Content-Type"))
  1802  }
  1803  
  1804  // This test is an adapted version of net/http/httputil.Test1xxResponses test.
  1805  func Test1xxResponses(t *testing.T) {
  1806  	// do not test 1xx responses on builds prior to go1.20.
  1807  	if !shouldWrite1xxResponses() {
  1808  		return
  1809  	}
  1810  
  1811  	wrapper, _ := NewWrapper()
  1812  	handler := wrapper(http.HandlerFunc(
  1813  		func(w http.ResponseWriter, r *http.Request) {
  1814  			h := w.Header()
  1815  			h.Add("Link", "</style.css>; rel=preload; as=style")
  1816  			h.Add("Link", "</script.js>; rel=preload; as=script")
  1817  			w.WriteHeader(http.StatusEarlyHints)
  1818  
  1819  			h.Add("Link", "</foo.js>; rel=preload; as=script")
  1820  			w.WriteHeader(http.StatusProcessing)
  1821  
  1822  			w.Write(testBody)
  1823  		},
  1824  	))
  1825  
  1826  	frontend := httptest.NewServer(handler)
  1827  	defer frontend.Close()
  1828  	frontendClient := frontend.Client()
  1829  
  1830  	checkLinkHeaders := func(t *testing.T, expected, got []string) {
  1831  		t.Helper()
  1832  
  1833  		if len(expected) != len(got) {
  1834  			t.Errorf("Expected %d link headers; got %d", len(expected), len(got))
  1835  		}
  1836  
  1837  		for i := range expected {
  1838  			if i >= len(got) {
  1839  				t.Errorf("Expected %q link header; got nothing", expected[i])
  1840  
  1841  				continue
  1842  			}
  1843  
  1844  			if expected[i] != got[i] {
  1845  				t.Errorf("Expected %q link header; got %q", expected[i], got[i])
  1846  			}
  1847  		}
  1848  	}
  1849  
  1850  	var respCounter uint8
  1851  	trace := &httptrace.ClientTrace{
  1852  		Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
  1853  			switch code {
  1854  			case http.StatusEarlyHints:
  1855  				checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script"}, header["Link"])
  1856  			case http.StatusProcessing:
  1857  				checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script", "</foo.js>; rel=preload; as=script"}, header["Link"])
  1858  			default:
  1859  				t.Error("Unexpected 1xx response")
  1860  			}
  1861  
  1862  			respCounter++
  1863  
  1864  			return nil
  1865  		},
  1866  	}
  1867  	req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), "GET", frontend.URL, nil)
  1868  	req.Header.Set("Accept-Encoding", "gzip")
  1869  
  1870  	res, err := frontendClient.Do(req)
  1871  	if err != nil {
  1872  		t.Fatalf("Get: %v", err)
  1873  	}
  1874  
  1875  	defer res.Body.Close()
  1876  
  1877  	if respCounter != 2 {
  1878  		t.Errorf("Expected 2 1xx responses; got %d", respCounter)
  1879  	}
  1880  	checkLinkHeaders(t, []string{"</style.css>; rel=preload; as=style", "</script.js>; rel=preload; as=script", "</foo.js>; rel=preload; as=script"}, res.Header["Link"])
  1881  
  1882  	assertEqual(t, "gzip", res.Header.Get("Content-Encoding"))
  1883  
  1884  	body, _ := io.ReadAll(res.Body)
  1885  	assertEqual(t, gzipStrLevel(testBody, gzip.DefaultCompression), body)
  1886  }
  1887  
  1888  func TestContentTypeDetectWithJitter(t *testing.T) {
  1889  	t.Parallel()
  1890  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  1891  		content := `<!DOCTYPE html>` + strings.Repeat("foo", 400)
  1892  		w.Write([]byte(content))
  1893  	})
  1894  
  1895  	for _, tc := range []struct {
  1896  		name    string
  1897  		wrapper func(http.Handler) (http.Handler, error)
  1898  	}{
  1899  		{
  1900  			name: "no wrapping",
  1901  			wrapper: func(h http.Handler) (http.Handler, error) {
  1902  				return h, nil
  1903  			},
  1904  		},
  1905  		{
  1906  			name: "default",
  1907  			wrapper: func(h http.Handler) (http.Handler, error) {
  1908  				wrapper, err := NewWrapper()
  1909  				if err != nil {
  1910  					return nil, err
  1911  				}
  1912  				return wrapper(h), nil
  1913  			},
  1914  		},
  1915  		{
  1916  			name: "jitter, default buffer",
  1917  			wrapper: func(h http.Handler) (http.Handler, error) {
  1918  				wrapper, err := NewWrapper(RandomJitter(32, 0, false))
  1919  				if err != nil {
  1920  					return nil, err
  1921  				}
  1922  				return wrapper(h), nil
  1923  			},
  1924  		},
  1925  		{
  1926  			name: "jitter, small buffer",
  1927  			wrapper: func(h http.Handler) (http.Handler, error) {
  1928  				wrapper, err := NewWrapper(RandomJitter(32, DefaultMinSize, false))
  1929  				if err != nil {
  1930  					return nil, err
  1931  				}
  1932  				return wrapper(h), nil
  1933  			},
  1934  		},
  1935  	} {
  1936  		tc := tc
  1937  		t.Run(tc.name, func(t *testing.T) {
  1938  			t.Parallel()
  1939  
  1940  			handler, err := tc.wrapper(handler)
  1941  			assertNil(t, err)
  1942  
  1943  			req, resp := httptest.NewRequest(http.MethodGet, "/", nil), httptest.NewRecorder()
  1944  			req.Header.Add("Accept-Encoding", "gzip")
  1945  
  1946  			handler.ServeHTTP(resp, req)
  1947  
  1948  			assertEqual(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type"))
  1949  		})
  1950  	}
  1951  }
  1952  

View as plain text