diff --git a/gzhttp/compress.go b/gzhttp/compress.go index df1d2f1b72..72d4246fc5 100644 --- a/gzhttp/compress.go +++ b/gzhttp/compress.go @@ -226,7 +226,8 @@ func (w *GzipResponseWriter) startGzip(remain []byte) error { } h.Write(remain) } - jitRNG = binary.LittleEndian.Uint32(h.Sum(nil)) + var tmp [sha256.BlockSize]byte + jitRNG = binary.LittleEndian.Uint32(h.Sum(tmp[:0])) } else { // Get from rand.Reader var tmp [4]byte @@ -236,9 +237,8 @@ func (w *GzipResponseWriter) startGzip(remain []byte) error { } jitRNG = binary.LittleEndian.Uint32(tmp[:]) } - jit := string(w.randomJitter[:1+jitRNG%uint32(len(w.randomJitter)-1)]) - //fmt.Println("w.buf:", len(w.buf), "remain:", len(remain), "jitter:", len(jit)) - w.gw.(writer.GzipWriterExt).SetHeader(writer.Header{Comment: &jit}) + jit := w.randomJitter[:1+jitRNG%uint32(len(w.randomJitter)-1)] + w.gw.(writer.GzipWriterExt).SetHeader(writer.Header{Comment: jit}) } n, err := w.gw.Write(w.buf) @@ -457,6 +457,7 @@ func NewWrapper(opts ...option) (func(http.Handler) http.HandlerFunc, error) { } else { h.ServeHTTP(gw, r) } + w.Header().Del(HeaderNoCompression) } else { h.ServeHTTP(newNoGzipResponseWriter(w), r) w.Header().Del(HeaderNoCompression) @@ -786,10 +787,23 @@ func parseEncodings(s string) (codings, error) { return c, nil } +var errEmptyEncoding = errors.New("empty content-coding") + // parseCoding parses a single coding (content-coding with an optional qvalue), // as might appear in an Accept-Encoding header. It attempts to forgive minor // formatting errors. func parseCoding(s string) (coding string, qvalue float64, err error) { + // Avoid splitting if we can... + if len(s) == 0 { + return "", 0, errEmptyEncoding + } + if !strings.ContainsRune(s, ';') { + coding = strings.ToLower(strings.TrimSpace(s)) + if coding == "" { + err = errEmptyEncoding + } + return coding, DefaultQValue, err + } for n, part := range strings.Split(s, ";") { part = strings.TrimSpace(part) qvalue = DefaultQValue @@ -808,7 +822,7 @@ func parseCoding(s string) (coding string, qvalue float64, err error) { } if coding == "" { - err = fmt.Errorf("empty content-coding") + err = errEmptyEncoding } return @@ -850,6 +864,9 @@ const intSize = 32 << (^uint(0) >> 63) // atoi is equivalent to ParseInt(s, 10, 0), converted to type int. func atoi(s string) (int, bool) { + if len(s) == 0 { + return 0, false + } sLen := len(s) if intSize == 32 && (0 < sLen && sLen < 10) || intSize == 64 && (0 < sLen && sLen < 19) { diff --git a/gzhttp/compress_test.go b/gzhttp/compress_test.go index 9d086cdbc6..560371f71e 100644 --- a/gzhttp/compress_test.go +++ b/gzhttp/compress_test.go @@ -1438,19 +1438,50 @@ func TestContentTypeDetect(t *testing.T) { // -------------------------------------------------------------------- -func BenchmarkGzipHandler_S2k(b *testing.B) { benchmark(b, false, 2048, gzip.DefaultCompression) } -func BenchmarkGzipHandler_S20k(b *testing.B) { benchmark(b, false, 20480, gzip.DefaultCompression) } -func BenchmarkGzipHandler_S100k(b *testing.B) { benchmark(b, false, 102400, gzip.DefaultCompression) } -func BenchmarkGzipHandler_P2k(b *testing.B) { benchmark(b, true, 2048, gzip.DefaultCompression) } -func BenchmarkGzipHandler_P20k(b *testing.B) { benchmark(b, true, 20480, gzip.DefaultCompression) } -func BenchmarkGzipHandler_P100k(b *testing.B) { benchmark(b, true, 102400, gzip.DefaultCompression) } - -func BenchmarkGzipBestSpeedHandler_S2k(b *testing.B) { benchmark(b, false, 2048, gzip.BestSpeed) } -func BenchmarkGzipBestSpeedHandler_S20k(b *testing.B) { benchmark(b, false, 20480, gzip.BestSpeed) } -func BenchmarkGzipBestSpeedHandler_S100k(b *testing.B) { benchmark(b, false, 102400, gzip.BestSpeed) } -func BenchmarkGzipBestSpeedHandler_P2k(b *testing.B) { benchmark(b, true, 2048, gzip.BestSpeed) } -func BenchmarkGzipBestSpeedHandler_P20k(b *testing.B) { benchmark(b, true, 20480, gzip.BestSpeed) } -func BenchmarkGzipBestSpeedHandler_P100k(b *testing.B) { benchmark(b, true, 102400, gzip.BestSpeed) } +func BenchmarkGzipHandler_S2k(b *testing.B) { + benchmark(b, false, 2048, CompressionLevel(gzip.DefaultCompression)) +} +func BenchmarkGzipHandler_S20k(b *testing.B) { + benchmark(b, false, 20480, CompressionLevel(gzip.DefaultCompression)) +} +func BenchmarkGzipHandler_S100k(b *testing.B) { + benchmark(b, false, 102400, CompressionLevel(gzip.DefaultCompression)) +} +func BenchmarkGzipHandler_P2k(b *testing.B) { + benchmark(b, true, 2048, CompressionLevel(gzip.DefaultCompression)) +} +func BenchmarkGzipHandler_P20k(b *testing.B) { + benchmark(b, true, 20480, CompressionLevel(gzip.DefaultCompression)) +} +func BenchmarkGzipHandler_P100k(b *testing.B) { + benchmark(b, true, 102400, CompressionLevel(gzip.DefaultCompression)) +} + +func BenchmarkGzipBestSpeedHandler_S2k(b *testing.B) { + benchmark(b, false, 2048, CompressionLevel(gzip.BestSpeed)) +} +func BenchmarkGzipBestSpeedHandler_S20k(b *testing.B) { + benchmark(b, false, 20480, CompressionLevel(gzip.BestSpeed)) +} +func BenchmarkGzipBestSpeedHandler_S100k(b *testing.B) { + benchmark(b, false, 102400, CompressionLevel(gzip.BestSpeed)) +} +func BenchmarkGzipBestSpeedHandler_P2k(b *testing.B) { + benchmark(b, true, 2048, CompressionLevel(gzip.BestSpeed)) +} +func BenchmarkGzipBestSpeedHandler_P20k(b *testing.B) { + benchmark(b, true, 20480, CompressionLevel(gzip.BestSpeed)) +} +func BenchmarkGzipBestSpeedHandler_P100k(b *testing.B) { + benchmark(b, true, 102400, CompressionLevel(gzip.BestSpeed)) +} + +func Benchmark2kJitter(b *testing.B) { + benchmark(b, false, 2048, CompressionLevel(gzip.BestSpeed), RandomJitter(32, 0)) +} +func Benchmark2kJitterRNG(b *testing.B) { + benchmark(b, false, 2048, CompressionLevel(gzip.BestSpeed), RandomJitter(32, -1)) +} // -------------------------------------------------------------------- @@ -1462,7 +1493,7 @@ func gzipStrLevel(s []byte, lvl int) []byte { return b.Bytes() } -func benchmark(b *testing.B, parallel bool, size, level int) { +func benchmark(b *testing.B, parallel bool, size int, opts ...option) { bin, err := os.ReadFile("testdata/benchmark.json") if err != nil { b.Fatal(err) @@ -1470,7 +1501,7 @@ func benchmark(b *testing.B, parallel bool, size, level int) { req, _ := http.NewRequest("GET", "/whatever", nil) req.Header.Set("Accept-Encoding", "gzip") - handler := newTestHandlerLevel(bin[:size], level) + handler := newTestHandlerLevel(bin[:size], opts...) b.ReportAllocs() b.SetBytes(int64(size)) @@ -1514,8 +1545,8 @@ func newTestHandler(body []byte) http.Handler { })) } -func newTestHandlerLevel(body []byte, level int) http.Handler { - wrapper, err := NewWrapper(CompressionLevel(level)) +func newTestHandlerLevel(body []byte, opts ...option) http.Handler { + wrapper, err := NewWrapper(opts...) if err != nil { panic(err) } diff --git a/gzhttp/writer/gzkp/gzkp.go b/gzhttp/writer/gzkp/gzkp.go index e312acfb55..f862d9412f 100644 --- a/gzhttp/writer/gzkp/gzkp.go +++ b/gzhttp/writer/gzkp/gzkp.go @@ -64,13 +64,13 @@ func NewWriter(w io.Writer, level int) writer.GzipWriter { // SetHeader will override header with any non-nil values. func (pw *pooledWriter) SetHeader(h writer.Header) { if h.Name != nil { - pw.Name = *h.Name + pw.Name = string(h.Name) } if h.Extra != nil { pw.Extra = *h.Extra } if h.Comment != nil { - pw.Comment = *h.Comment + pw.Comment = string(h.Comment) } if h.ModTime != nil { pw.ModTime = *h.ModTime diff --git a/gzhttp/writer/gzstd/stdlib.go b/gzhttp/writer/gzstd/stdlib.go index 3b03e2cd81..eeaea4dccc 100644 --- a/gzhttp/writer/gzstd/stdlib.go +++ b/gzhttp/writer/gzstd/stdlib.go @@ -64,13 +64,13 @@ func NewWriter(w io.Writer, level int) writer.GzipWriter { // SetHeader will override header with any non-nil values. func (pw *pooledWriter) SetHeader(h writer.Header) { if h.Name != nil { - pw.Name = *h.Name + pw.Name = string(h.Name) } if h.Extra != nil { pw.Extra = *h.Extra } if h.Comment != nil { - pw.Comment = *h.Comment + pw.Comment = string(h.Comment) } if h.ModTime != nil { pw.ModTime = *h.ModTime diff --git a/gzhttp/writer/interface.go b/gzhttp/writer/interface.go index df9f5958e3..7a6a12370d 100644 --- a/gzhttp/writer/interface.go +++ b/gzhttp/writer/interface.go @@ -23,10 +23,10 @@ type GzipWriterExt interface { // Header provides nillable header fields. type Header struct { - Comment *string // comment + Comment []byte // comment, converted to string if set. Extra *[]byte // "extra data" ModTime *time.Time // modification time - Name *string // file name + Name []byte // file name, converted to string if set. OS *byte // operating system type }