Refactor compress handler to make it generic

Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
Kevin Pollet 2024-10-10 16:04:04 +02:00 committed by GitHub
parent 4613ddd757
commit ef168b801c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 125 additions and 92 deletions

View file

@ -8,7 +8,9 @@ import (
"net/http" "net/http"
"slices" "slices"
"github.com/andybalholm/brotli"
"github.com/klauspost/compress/gzhttp" "github.com/klauspost/compress/gzhttp"
"github.com/klauspost/compress/zstd"
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@ -78,12 +80,12 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str
var err error var err error
c.zstdHandler, err = c.newCompressionHandler(zstdName, name) c.zstdHandler, err = c.newZstdHandler(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.brotliHandler, err = c.newCompressionHandler(brotliName, name) c.brotliHandler, err = c.newBrotliHandler(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -174,13 +176,34 @@ func (c *compress) newGzipHandler() (http.Handler, error) {
return wrapper(c.next), nil return wrapper(c.next), nil
} }
func (c *compress) newCompressionHandler(algo string, middlewareName string) (http.Handler, error) { func (c *compress) newBrotliHandler(middlewareName string) (http.Handler, error) {
cfg := Config{MinSize: c.minSize, Algorithm: algo, MiddlewareName: middlewareName} cfg := Config{MinSize: c.minSize, MiddlewareName: middlewareName}
if len(c.includes) > 0 { if len(c.includes) > 0 {
cfg.IncludedContentTypes = c.includes cfg.IncludedContentTypes = c.includes
} else { } else {
cfg.ExcludedContentTypes = c.excludes cfg.ExcludedContentTypes = c.excludes
} }
return NewCompressionHandler(cfg, c.next) newBrotliWriter := func(rw http.ResponseWriter) (CompressionWriter, string, error) {
return brotli.NewWriter(rw), brotliName, nil
}
return NewCompressionHandler(cfg, newBrotliWriter, c.next)
}
func (c *compress) newZstdHandler(middlewareName string) (http.Handler, error) {
cfg := Config{MinSize: c.minSize, MiddlewareName: middlewareName}
if len(c.includes) > 0 {
cfg.IncludedContentTypes = c.includes
} else {
cfg.ExcludedContentTypes = c.excludes
}
newZstdWriter := func(rw http.ResponseWriter) (CompressionWriter, string, error) {
writer, err := zstd.NewWriter(rw)
if err != nil {
return nil, "", fmt.Errorf("creating zstd writer: %w", err)
}
return writer, zstdName, nil
}
return NewCompressionHandler(cfg, newZstdWriter, c.next)
} }

View file

@ -10,8 +10,6 @@ import (
"net/http" "net/http"
"sync" "sync"
"github.com/andybalholm/brotli"
"github.com/klauspost/compress/zstd"
"github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares"
"github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/middlewares/observability"
) )
@ -24,6 +22,30 @@ const (
contentType = "Content-Type" contentType = "Content-Type"
) )
// CompressionWriter compresses the written bytes.
type CompressionWriter interface {
// Write data to the encoder.
// Input data will be buffered and as the buffer fills up
// content will be compressed and written to the output.
// When done writing, use Close to flush the remaining output
// and write CRC if requested.
Write(p []byte) (n int, err error)
// Flush will send the currently written data to output
// and block until everything has been written.
// This should only be used on rare occasions where pushing the currently queued data is critical.
Flush() error
// Close closes the underlying writers if/when appropriate.
// Note that the compressed writer should not be closed if we never used it,
// as it would otherwise send some extra "end of compression" bytes.
// Close also makes sure to flush whatever was left to write from the buffer.
Close() error
// Reset reinitializes the state of the encoder, allowing it to be reused.
Reset(w io.Writer)
}
// NewCompressionWriter returns a new CompressionWriter with its corresponding algorithm.
type NewCompressionWriter func(rw http.ResponseWriter) (CompressionWriter, string, error)
// Config is the Brotli handler configuration. // Config is the Brotli handler configuration.
type Config struct { type Config struct {
// ExcludedContentTypes is the list of content types for which we should not compress. // ExcludedContentTypes is the list of content types for which we should not compress.
@ -34,8 +56,6 @@ type Config struct {
IncludedContentTypes []string IncludedContentTypes []string
// MinSize is the minimum size (in bytes) required to enable compression. // MinSize is the minimum size (in bytes) required to enable compression.
MinSize int MinSize int
// Algorithm used for the compression (currently Brotli and Zstandard)
Algorithm string
// MiddlewareName use for logging purposes // MiddlewareName use for logging purposes
MiddlewareName string MiddlewareName string
} }
@ -46,15 +66,13 @@ type CompressionHandler struct {
excludedContentTypes []parsedContentType excludedContentTypes []parsedContentType
includedContentTypes []parsedContentType includedContentTypes []parsedContentType
next http.Handler next http.Handler
writerPool sync.Pool
writerPool sync.Pool
newWriter NewCompressionWriter
} }
// NewCompressionHandler returns a new compressing handler. // NewCompressionHandler returns a new compressing handler.
func NewCompressionHandler(cfg Config, next http.Handler) (http.Handler, error) { func NewCompressionHandler(cfg Config, newWriter NewCompressionWriter, next http.Handler) (http.Handler, error) {
if cfg.Algorithm == "" {
return nil, errors.New("compression algorithm undefined")
}
if cfg.MinSize < 0 { if cfg.MinSize < 0 {
return nil, errors.New("minimum size must be greater than or equal to zero") return nil, errors.New("minimum size must be greater than or equal to zero")
} }
@ -88,6 +106,7 @@ func NewCompressionHandler(cfg Config, next http.Handler) (http.Handler, error)
excludedContentTypes: excludedContentTypes, excludedContentTypes: excludedContentTypes,
includedContentTypes: includedContentTypes, includedContentTypes: includedContentTypes,
next: next, next: next,
newWriter: newWriter,
}, nil }, nil
} }
@ -117,70 +136,38 @@ func (c *CompressionHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request)
c.next.ServeHTTP(responseWriter, r) c.next.ServeHTTP(responseWriter, r)
} }
type compression interface { func (c *CompressionHandler) getCompressionWriter(rw http.ResponseWriter) (*compressionWriterWrapper, error) {
// Write data to the encoder. if writer, ok := c.writerPool.Get().(*compressionWriterWrapper); ok {
// Input data will be buffered and as the buffer fills up writer.Reset(rw)
// content will be compressed and written to the output.
// When done writing, use Close to flush the remaining output
// and write CRC if requested.
Write(p []byte) (n int, err error)
// Flush will send the currently written data to output
// and block until everything has been written.
// This should only be used on rare occasions where pushing the currently queued data is critical.
Flush() error
// Close closes the underlying writers if/when appropriate.
// Note that the compressed writer should not be closed if we never used it,
// as it would otherwise send some extra "end of compression" bytes.
// Close also makes sure to flush whatever was left to write from the buffer.
Close() error
// Reset reinitializes the state of the encoder, allowing it to be reused.
Reset(w io.Writer)
}
type compressionWriter struct {
compression
alg string
}
func (c *CompressionHandler) getCompressionWriter(rw io.Writer) (*compressionWriter, error) {
if writer, ok := c.writerPool.Get().(*compressionWriter); ok {
writer.compression.Reset(rw)
return writer, nil return writer, nil
} }
return newCompressionWriter(c.cfg.Algorithm, rw)
writer, algo, err := c.newWriter(rw)
if err != nil {
return nil, fmt.Errorf("creating compression writer: %w", err)
}
return &compressionWriterWrapper{CompressionWriter: writer, algo: algo}, nil
} }
func (c *CompressionHandler) putCompressionWriter(writer *compressionWriter) { func (c *CompressionHandler) putCompressionWriter(writer *compressionWriterWrapper) {
writer.Reset(nil) writer.Reset(nil)
c.writerPool.Put(writer) c.writerPool.Put(writer)
} }
func newCompressionWriter(algo string, in io.Writer) (*compressionWriter, error) { type compressionWriterWrapper struct {
switch algo { CompressionWriter
case brotliName: algo string
return &compressionWriter{compression: brotli.NewWriter(in), alg: algo}, nil
case zstdName:
writer, err := zstd.NewWriter(in)
if err != nil {
return nil, fmt.Errorf("creating zstd writer: %w", err)
}
return &compressionWriter{compression: writer, alg: algo}, nil
default:
return nil, fmt.Errorf("unknown compression algo: %s", algo)
}
} }
func (c *compressionWriter) ContentEncoding() string { func (c *compressionWriterWrapper) ContentEncoding() string {
return c.alg return c.algo
} }
// TODO: check whether we want to implement content-type sniffing (as gzip does) // TODO: check whether we want to implement content-type sniffing (as gzip does)
// TODO: check whether we should support Accept-Ranges (as gzip does, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges) // TODO: check whether we should support Accept-Ranges (as gzip does, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges)
type responseWriter struct { type responseWriter struct {
rw http.ResponseWriter rw http.ResponseWriter
compressionWriter *compressionWriter compressionWriter *compressionWriterWrapper
minSize int minSize int
excludedContentTypes []parsedContentType excludedContentTypes []parsedContentType

View file

@ -162,7 +162,7 @@ func Test_NoBody(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
h := mustNewCompressionHandler(t, Config{MinSize: 1024, Algorithm: zstdName}, next) h := mustNewCompressionHandler(t, Config{MinSize: 1024}, zstdName, next)
req := httptest.NewRequest(http.MethodGet, "/", nil) req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set(acceptEncoding, "zstd") req.Header.Set(acceptEncoding, "zstd")
@ -181,8 +181,7 @@ func Test_NoBody(t *testing.T) {
func Test_MinSize(t *testing.T) { func Test_MinSize(t *testing.T) {
cfg := Config{ cfg := Config{
MinSize: 128, MinSize: 128,
Algorithm: zstdName,
} }
var bodySize int var bodySize int
@ -197,7 +196,7 @@ func Test_MinSize(t *testing.T) {
} }
}) })
h := mustNewCompressionHandler(t, cfg, next) h := mustNewCompressionHandler(t, cfg, zstdName, next)
req, _ := http.NewRequest(http.MethodGet, "/whatever", &bytes.Buffer{}) req, _ := http.NewRequest(http.MethodGet, "/whatever", &bytes.Buffer{})
req.Header.Add(acceptEncoding, "zstd") req.Header.Add(acceptEncoding, "zstd")
@ -224,7 +223,7 @@ func Test_MultipleWriteHeader(t *testing.T) {
rw.WriteHeader(http.StatusNotFound) rw.WriteHeader(http.StatusNotFound)
}) })
h := mustNewCompressionHandler(t, Config{MinSize: 1024, Algorithm: zstdName}, next) h := mustNewCompressionHandler(t, Config{MinSize: 1024}, zstdName, next)
req := httptest.NewRequest(http.MethodGet, "/", nil) req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set(acceptEncoding, "zstd") req.Header.Set(acceptEncoding, "zstd")
@ -239,12 +238,14 @@ func Test_FlushBeforeWrite(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
cfg Config cfg Config
algo string
readerBuilder func(io.Reader) (io.Reader, error) readerBuilder func(io.Reader) (io.Reader, error)
acceptEncoding string acceptEncoding string
}{ }{
{ {
desc: "brotli", desc: "brotli",
cfg: Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Test"}, cfg: Config{MinSize: 1024, MiddlewareName: "Test"},
algo: brotliName,
readerBuilder: func(reader io.Reader) (io.Reader, error) { readerBuilder: func(reader io.Reader) (io.Reader, error) {
return brotli.NewReader(reader), nil return brotli.NewReader(reader), nil
}, },
@ -252,7 +253,8 @@ func Test_FlushBeforeWrite(t *testing.T) {
}, },
{ {
desc: "zstd", desc: "zstd",
cfg: Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Test"}, cfg: Config{MinSize: 1024, MiddlewareName: "Test"},
algo: zstdName,
readerBuilder: func(reader io.Reader) (io.Reader, error) { readerBuilder: func(reader io.Reader) (io.Reader, error) {
return zstd.NewReader(reader) return zstd.NewReader(reader)
}, },
@ -272,7 +274,7 @@ func Test_FlushBeforeWrite(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, next)) srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, test.algo, next))
defer srv.Close() defer srv.Close()
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody) req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
@ -302,12 +304,14 @@ func Test_FlushAfterWrite(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
cfg Config cfg Config
algo string
readerBuilder func(io.Reader) (io.Reader, error) readerBuilder func(io.Reader) (io.Reader, error)
acceptEncoding string acceptEncoding string
}{ }{
{ {
desc: "brotli", desc: "brotli",
cfg: Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Test"}, cfg: Config{MinSize: 1024, MiddlewareName: "Test"},
algo: brotliName,
readerBuilder: func(reader io.Reader) (io.Reader, error) { readerBuilder: func(reader io.Reader) (io.Reader, error) {
return brotli.NewReader(reader), nil return brotli.NewReader(reader), nil
}, },
@ -315,7 +319,8 @@ func Test_FlushAfterWrite(t *testing.T) {
}, },
{ {
desc: "zstd", desc: "zstd",
cfg: Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Test"}, cfg: Config{MinSize: 1024, MiddlewareName: "Test"},
algo: zstdName,
readerBuilder: func(reader io.Reader) (io.Reader, error) { readerBuilder: func(reader io.Reader) (io.Reader, error) {
return zstd.NewReader(reader) return zstd.NewReader(reader)
}, },
@ -338,7 +343,7 @@ func Test_FlushAfterWrite(t *testing.T) {
} }
}) })
srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, next)) srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, test.algo, next))
defer srv.Close() defer srv.Close()
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody) req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
@ -368,12 +373,14 @@ func Test_FlushAfterWriteNil(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
cfg Config cfg Config
algo string
readerBuilder func(io.Reader) (io.Reader, error) readerBuilder func(io.Reader) (io.Reader, error)
acceptEncoding string acceptEncoding string
}{ }{
{ {
desc: "brotli", desc: "brotli",
cfg: Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Test"}, cfg: Config{MinSize: 1024, MiddlewareName: "Test"},
algo: brotliName,
readerBuilder: func(reader io.Reader) (io.Reader, error) { readerBuilder: func(reader io.Reader) (io.Reader, error) {
return brotli.NewReader(reader), nil return brotli.NewReader(reader), nil
}, },
@ -381,7 +388,8 @@ func Test_FlushAfterWriteNil(t *testing.T) {
}, },
{ {
desc: "zstd", desc: "zstd",
cfg: Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Test"}, cfg: Config{MinSize: 1024, MiddlewareName: "Test"},
algo: zstdName,
readerBuilder: func(reader io.Reader) (io.Reader, error) { readerBuilder: func(reader io.Reader) (io.Reader, error) {
return zstd.NewReader(reader) return zstd.NewReader(reader)
}, },
@ -400,7 +408,7 @@ func Test_FlushAfterWriteNil(t *testing.T) {
rw.(http.Flusher).Flush() rw.(http.Flusher).Flush()
}) })
srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, next)) srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, test.algo, next))
defer srv.Close() defer srv.Close()
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody) req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
@ -430,12 +438,14 @@ func Test_FlushAfterAllWrites(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
cfg Config cfg Config
algo string
readerBuilder func(io.Reader) (io.Reader, error) readerBuilder func(io.Reader) (io.Reader, error)
acceptEncoding string acceptEncoding string
}{ }{
{ {
desc: "brotli", desc: "brotli",
cfg: Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Test"}, cfg: Config{MinSize: 1024, MiddlewareName: "Test"},
algo: brotliName,
readerBuilder: func(reader io.Reader) (io.Reader, error) { readerBuilder: func(reader io.Reader) (io.Reader, error) {
return brotli.NewReader(reader), nil return brotli.NewReader(reader), nil
}, },
@ -443,7 +453,8 @@ func Test_FlushAfterAllWrites(t *testing.T) {
}, },
{ {
desc: "zstd", desc: "zstd",
cfg: Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Test"}, cfg: Config{MinSize: 1024, MiddlewareName: "Test"},
algo: zstdName,
readerBuilder: func(reader io.Reader) (io.Reader, error) { readerBuilder: func(reader io.Reader) (io.Reader, error) {
return zstd.NewReader(reader) return zstd.NewReader(reader)
}, },
@ -461,7 +472,7 @@ func Test_FlushAfterAllWrites(t *testing.T) {
rw.(http.Flusher).Flush() rw.(http.Flusher).Flush()
}) })
srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, next)) srv := httptest.NewServer(mustNewCompressionHandler(t, test.cfg, test.algo, next))
defer srv.Close() defer srv.Close()
req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody) req, err := http.NewRequest(http.MethodGet, srv.URL, http.NoBody)
@ -556,7 +567,6 @@ func Test_ExcludedContentTypes(t *testing.T) {
cfg := Config{ cfg := Config{
MinSize: 1024, MinSize: 1024,
ExcludedContentTypes: test.excludedContentTypes, ExcludedContentTypes: test.excludedContentTypes,
Algorithm: zstdName,
} }
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
@ -568,7 +578,7 @@ func Test_ExcludedContentTypes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
h := mustNewCompressionHandler(t, cfg, next) h := mustNewCompressionHandler(t, cfg, zstdName, next)
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil) req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
req.Header.Set(acceptEncoding, zstdName) req.Header.Set(acceptEncoding, zstdName)
@ -667,7 +677,6 @@ func Test_IncludedContentTypes(t *testing.T) {
cfg := Config{ cfg := Config{
MinSize: 1024, MinSize: 1024,
IncludedContentTypes: test.includedContentTypes, IncludedContentTypes: test.includedContentTypes,
Algorithm: zstdName,
} }
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
@ -679,7 +688,7 @@ func Test_IncludedContentTypes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
h := mustNewCompressionHandler(t, cfg, next) h := mustNewCompressionHandler(t, cfg, zstdName, next)
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil) req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
req.Header.Set(acceptEncoding, zstdName) req.Header.Set(acceptEncoding, zstdName)
@ -778,7 +787,6 @@ func Test_FlushExcludedContentTypes(t *testing.T) {
cfg := Config{ cfg := Config{
MinSize: 1024, MinSize: 1024,
ExcludedContentTypes: test.excludedContentTypes, ExcludedContentTypes: test.excludedContentTypes,
Algorithm: zstdName,
} }
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
@ -803,7 +811,7 @@ func Test_FlushExcludedContentTypes(t *testing.T) {
} }
}) })
h := mustNewCompressionHandler(t, cfg, next) h := mustNewCompressionHandler(t, cfg, zstdName, next)
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil) req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
req.Header.Set(acceptEncoding, zstdName) req.Header.Set(acceptEncoding, zstdName)
@ -903,7 +911,6 @@ func Test_FlushIncludedContentTypes(t *testing.T) {
cfg := Config{ cfg := Config{
MinSize: 1024, MinSize: 1024,
IncludedContentTypes: test.includedContentTypes, IncludedContentTypes: test.includedContentTypes,
Algorithm: zstdName,
} }
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
@ -928,7 +935,7 @@ func Test_FlushIncludedContentTypes(t *testing.T) {
} }
}) })
h := mustNewCompressionHandler(t, cfg, next) h := mustNewCompressionHandler(t, cfg, zstdName, next)
req, _ := http.NewRequest(http.MethodGet, "/whatever", nil) req, _ := http.NewRequest(http.MethodGet, "/whatever", nil)
req.Header.Set(acceptEncoding, zstdName) req.Header.Set(acceptEncoding, zstdName)
@ -959,10 +966,26 @@ func Test_FlushIncludedContentTypes(t *testing.T) {
} }
} }
func mustNewCompressionHandler(t *testing.T, cfg Config, next http.Handler) http.Handler { func mustNewCompressionHandler(t *testing.T, cfg Config, algo string, next http.Handler) http.Handler {
t.Helper() t.Helper()
w, err := NewCompressionHandler(cfg, next) var writer NewCompressionWriter
switch algo {
case zstdName:
writer = func(rw http.ResponseWriter) (CompressionWriter, string, error) {
writer, err := zstd.NewWriter(rw)
require.NoError(t, err)
return writer, zstdName, nil
}
case brotliName:
writer = func(rw http.ResponseWriter) (CompressionWriter, string, error) {
return brotli.NewWriter(rw), brotliName, nil
}
default:
assert.Failf(t, "unknown compression algorithm: %s", algo)
}
w, err := NewCompressionHandler(cfg, writer, next)
require.NoError(t, err) require.NoError(t, err)
return w return w
@ -981,7 +1004,7 @@ func newTestBrotliHandler(t *testing.T, body []byte) http.Handler {
require.NoError(t, err) require.NoError(t, err)
}) })
return mustNewCompressionHandler(t, Config{MinSize: 1024, Algorithm: brotliName, MiddlewareName: "Compress"}, next) return mustNewCompressionHandler(t, Config{MinSize: 1024, MiddlewareName: "Compress"}, brotliName, next)
} }
func newTestZstandardHandler(t *testing.T, body []byte) http.Handler { func newTestZstandardHandler(t *testing.T, body []byte) http.Handler {
@ -997,7 +1020,7 @@ func newTestZstandardHandler(t *testing.T, body []byte) http.Handler {
require.NoError(t, err) require.NoError(t, err)
}) })
return mustNewCompressionHandler(t, Config{MinSize: 1024, Algorithm: zstdName, MiddlewareName: "Compress"}, next) return mustNewCompressionHandler(t, Config{MinSize: 1024, MiddlewareName: "Compress"}, zstdName, next)
} }
func Test_ParseContentType_equals(t *testing.T) { func Test_ParseContentType_equals(t *testing.T) {