1
0
Fork 0

Add encodings option to the compression middleware

This commit is contained in:
Wolfgang Ellsässer 2024-08-07 16:20:04 +02:00 committed by GitHub
parent b611f967b7
commit 75881359ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 389 additions and 92 deletions

View file

@ -165,8 +165,7 @@ func (c *CircuitBreaker) SetDefaults() {
// +k8s:deepcopy-gen=true
// Compress holds the compress middleware configuration.
// This middleware compresses responses before sending them to the client, using gzip compression.
// More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
// This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression.
type Compress struct {
// ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
// `application/grpc` is always excluded.
@ -176,10 +175,16 @@ type Compress struct {
// MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed.
// Default: 1024.
MinResponseBodyBytes int `json:"minResponseBodyBytes,omitempty" toml:"minResponseBodyBytes,omitempty" yaml:"minResponseBodyBytes,omitempty" export:"true"`
// Encodings defines the list of supported compression algorithms.
Encodings []string `json:"encodings,omitempty" toml:"encodings,omitempty" yaml:"encodings,omitempty" export:"true"`
// DefaultEncoding specifies the default encoding if the `Accept-Encoding` header is not in the request or contains a wildcard (`*`).
DefaultEncoding string `json:"defaultEncoding,omitempty" toml:"defaultEncoding,omitempty" yaml:"defaultEncoding,omitempty" export:"true"`
}
func (c *Compress) SetDefaults() {
c.Encodings = []string{"zstd", "br", "gzip"}
}
// +k8s:deepcopy-gen=true
// DigestAuth holds the digest auth middleware configuration.

View file

@ -158,6 +158,11 @@ func (in *Compress) DeepCopyInto(out *Compress) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Encodings != nil {
in, out := &in.Encodings, &out.Encodings
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

View file

@ -137,6 +137,7 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
"traefik.http.middlewares.Middleware17.stripprefix.forceslash": "true",
"traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
"traefik.http.middlewares.Middleware19.compress.encodings": "foobar, fiibar",
"traefik.http.middlewares.Middleware19.compress.minresponsebodybytes": "42",
"traefik.http.middlewares.Middleware20.plugin.tomato.aaa": "foo1",
"traefik.http.middlewares.Middleware20.plugin.tomato.bbb": "foo2",
@ -493,6 +494,10 @@ func TestDecodeConfiguration(t *testing.T) {
"Middleware19": {
Compress: &dynamic.Compress{
MinResponseBodyBytes: 42,
Encodings: []string{
"foobar",
"fiibar",
},
},
},
"Middleware2": {
@ -1009,6 +1014,10 @@ func TestEncodeConfiguration(t *testing.T) {
"Middleware19": {
Compress: &dynamic.Compress{
MinResponseBodyBytes: 42,
Encodings: []string{
"foobar",
"fiibar",
},
},
},
"Middleware2": {
@ -1377,6 +1386,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware19.Compress.Encodings": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware19.Compress.MinResponseBodyBytes": "42",
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1",
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.bbb": "foo2",

View file

@ -22,13 +22,18 @@ type Encoding struct {
Weight *float64
}
func getCompressionType(acceptEncoding []string, defaultType string) string {
if defaultType == "" {
// Keeps the pre-existing default inside Traefik.
defaultType = brotliName
func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, supportedEncodings []string) string {
if defaultEncoding == "" {
if slices.Contains(supportedEncodings, brotliName) {
// Keeps the pre-existing default inside Traefik if brotli is a supported encoding.
defaultEncoding = brotliName
} else if len(supportedEncodings) > 0 {
// Otherwise use the first supported encoding.
defaultEncoding = supportedEncodings[0]
}
}
encodings, hasWeight := parseAcceptEncoding(acceptEncoding)
encodings, hasWeight := parseAcceptEncoding(acceptEncoding, supportedEncodings)
if hasWeight {
if len(encodings) == 0 {
@ -46,26 +51,26 @@ func getCompressionType(acceptEncoding []string, defaultType string) string {
}
if encoding.Type == wildcardName {
return defaultType
return defaultEncoding
}
return encoding.Type
}
for _, dt := range []string{zstdName, brotliName, gzipName} {
for _, dt := range supportedEncodings {
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == dt }) {
return dt
}
}
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == wildcardName }) {
return defaultType
return defaultEncoding
}
return identityName
}
func parseAcceptEncoding(acceptEncoding []string) ([]Encoding, bool) {
func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encoding, bool) {
var encodings []Encoding
var hasWeight bool
@ -76,10 +81,9 @@ func parseAcceptEncoding(acceptEncoding []string) ([]Encoding, bool) {
continue
}
switch parsed[0] {
case zstdName, brotliName, gzipName, identityName, wildcardName:
// supported encoding
default:
if !slices.Contains(supportedEncodings, parsed[0]) &&
parsed[0] != identityName &&
parsed[0] != wildcardName {
continue
}

View file

@ -6,73 +6,86 @@ import (
"github.com/stretchr/testify/assert"
)
func Test_getCompressionType(t *testing.T) {
func Test_getCompressionEncoding(t *testing.T) {
testCases := []struct {
desc string
values []string
defaultType string
expected string
desc string
acceptEncoding []string
defaultEncoding string
supportedEncodings []string
expected string
}{
{
desc: "br > gzip (no weight)",
values: []string{"gzip, br"},
expected: brotliName,
desc: "br > gzip (no weight)",
acceptEncoding: []string{"gzip, br"},
expected: brotliName,
},
{
desc: "zstd > br > gzip (no weight)",
values: []string{"zstd, gzip, br"},
expected: zstdName,
desc: "zstd > br > gzip (no weight)",
acceptEncoding: []string{"zstd, gzip, br"},
expected: zstdName,
},
{
desc: "known compression type (no weight)",
values: []string{"compress, gzip"},
expected: gzipName,
desc: "known compression encoding (no weight)",
acceptEncoding: []string{"compress, gzip"},
expected: gzipName,
},
{
desc: "unknown compression type (no weight), no encoding",
values: []string{"compress, rar"},
expected: identityName,
desc: "unknown compression encoding (no weight), no encoding",
acceptEncoding: []string{"compress, rar"},
expected: identityName,
},
{
desc: "wildcard return the default compression type",
values: []string{"*"},
expected: brotliName,
desc: "wildcard return the default compression encoding",
acceptEncoding: []string{"*"},
expected: brotliName,
},
{
desc: "wildcard return the custom default compression type",
values: []string{"*"},
defaultType: "foo",
expected: "foo",
desc: "wildcard return the custom default compression encoding",
acceptEncoding: []string{"*"},
defaultEncoding: "foo",
expected: "foo",
},
{
desc: "follows weight",
values: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"},
expected: gzipName,
desc: "follows weight",
acceptEncoding: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"},
expected: gzipName,
},
{
desc: "ignore unknown compression type",
values: []string{"compress;q=1.0, gzip;q=0.5"},
expected: gzipName,
desc: "ignore unknown compression encoding",
acceptEncoding: []string{"compress;q=1.0, gzip;q=0.5"},
expected: gzipName,
},
{
desc: "fallback on non-zero compression type",
values: []string{"compress;q=1.0, gzip, identity;q=0"},
expected: gzipName,
desc: "fallback on non-zero compression encoding",
acceptEncoding: []string{"compress;q=1.0, gzip, identity;q=0"},
expected: gzipName,
},
{
desc: "not acceptable (identity)",
values: []string{"compress;q=1.0, identity;q=0"},
expected: notAcceptable,
desc: "not acceptable (identity)",
acceptEncoding: []string{"compress;q=1.0, identity;q=0"},
expected: notAcceptable,
},
{
desc: "not acceptable (wildcard)",
values: []string{"compress;q=1.0, *;q=0"},
expected: notAcceptable,
desc: "not acceptable (wildcard)",
acceptEncoding: []string{"compress;q=1.0, *;q=0"},
expected: notAcceptable,
},
{
desc: "non-zero is higher than 0",
values: []string{"gzip, *;q=0"},
expected: gzipName,
desc: "non-zero is higher than 0",
acceptEncoding: []string{"gzip, *;q=0"},
expected: gzipName,
},
{
desc: "zstd forbidden, brotli first",
acceptEncoding: []string{"zstd, gzip, br"},
supportedEncodings: []string{brotliName, gzipName},
expected: brotliName,
},
{
desc: "follows weight, ignores forbidden encoding",
acceptEncoding: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"},
supportedEncodings: []string{zstdName, brotliName},
expected: brotliName,
},
}
@ -80,19 +93,24 @@ func Test_getCompressionType(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
encodingType := getCompressionType(test.values, test.defaultType)
if test.supportedEncodings == nil {
test.supportedEncodings = defaultSupportedEncodings
}
assert.Equal(t, test.expected, encodingType)
encoding := getCompressionEncoding(test.acceptEncoding, test.defaultEncoding, test.supportedEncodings)
assert.Equal(t, test.expected, encoding)
})
}
}
func Test_parseAcceptEncoding(t *testing.T) {
testCases := []struct {
desc string
values []string
expected []Encoding
assertWeight assert.BoolAssertionFunc
desc string
values []string
supportedEncodings []string
expected []Encoding
assertWeight assert.BoolAssertionFunc
}{
{
desc: "weight",
@ -105,6 +123,17 @@ func Test_parseAcceptEncoding(t *testing.T) {
},
assertWeight: assert.True,
},
{
desc: "weight with supported encodings",
values: []string{"br;q=1.0, zstd;q=0.9, gzip;q=0.8, *;q=0.1"},
supportedEncodings: []string{brotliName, gzipName},
expected: []Encoding{
{Type: brotliName, Weight: ptr[float64](1)},
{Type: gzipName, Weight: ptr(0.8)},
{Type: wildcardName, Weight: ptr(0.1)},
},
assertWeight: assert.True,
},
{
desc: "mixed",
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
@ -116,6 +145,16 @@ func Test_parseAcceptEncoding(t *testing.T) {
},
assertWeight: assert.True,
},
{
desc: "mixed with supported encodings",
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
supportedEncodings: []string{zstdName},
expected: []Encoding{
{Type: zstdName},
{Type: wildcardName, Weight: ptr[float64](0)},
},
assertWeight: assert.True,
},
{
desc: "no weight",
values: []string{"zstd, gzip, br, *"},
@ -127,6 +166,16 @@ func Test_parseAcceptEncoding(t *testing.T) {
},
assertWeight: assert.False,
},
{
desc: "no weight with supported encodings",
values: []string{"zstd, gzip, br, *"},
supportedEncodings: []string{"gzip"},
expected: []Encoding{
{Type: gzipName},
{Type: wildcardName},
},
assertWeight: assert.False,
},
{
desc: "weight and identity",
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
@ -137,13 +186,27 @@ func Test_parseAcceptEncoding(t *testing.T) {
},
assertWeight: assert.True,
},
{
desc: "weight and identity",
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
supportedEncodings: []string{"br"},
expected: []Encoding{
{Type: identityName, Weight: ptr(0.5)},
{Type: wildcardName, Weight: ptr[float64](0)},
},
assertWeight: assert.True,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
aes, hasWeight := parseAcceptEncoding(test.values)
if test.supportedEncodings == nil {
test.supportedEncodings = defaultSupportedEncodings
}
aes, hasWeight := parseAcceptEncoding(test.values, test.supportedEncodings)
assert.Equal(t, test.expected, aes)
test.assertWeight(t, hasWeight)

View file

@ -16,9 +16,11 @@ import (
const typeName = "Compress"
// DefaultMinSize is the default minimum size (in bytes) required to enable compression.
// defaultMinSize is the default minimum size (in bytes) required to enable compression.
// See https://github.com/klauspost/compress/blob/9559b037e79ad673c71f6ef7c732c00949014cd2/gzhttp/compress.go#L47.
const DefaultMinSize = 1024
const defaultMinSize = 1024
var defaultSupportedEncodings = []string{zstdName, brotliName, gzipName}
// Compress is a middleware that allows to compress the response.
type compress struct {
@ -27,6 +29,7 @@ type compress struct {
excludes []string
includes []string
minSize int
encodings []string
defaultEncoding string
brotliHandler http.Handler
@ -62,17 +65,30 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str
includes = append(includes, mediaType)
}
minSize := DefaultMinSize
minSize := defaultMinSize
if conf.MinResponseBodyBytes > 0 {
minSize = conf.MinResponseBodyBytes
}
if len(conf.Encodings) == 0 {
return nil, errors.New("at least one encoding must be specified")
}
for _, encoding := range conf.Encodings {
if !slices.Contains(defaultSupportedEncodings, encoding) {
return nil, fmt.Errorf("unsupported encoding: %s", encoding)
}
}
if conf.DefaultEncoding != "" && !slices.Contains(conf.Encodings, conf.DefaultEncoding) {
return nil, fmt.Errorf("unsupported default encoding: %s", conf.DefaultEncoding)
}
c := &compress{
next: next,
name: name,
excludes: excludes,
includes: includes,
minSize: minSize,
encodings: conf.Encodings,
defaultEncoding: conf.DefaultEncoding,
}
@ -131,7 +147,7 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return
}
c.chooseHandler(getCompressionType(acceptEncoding, c.defaultEncoding), rw, req)
c.chooseHandler(getCompressionEncoding(acceptEncoding, c.defaultEncoding, c.encodings), rw, req)
}
func (c *compress) chooseHandler(typ string, rw http.ResponseWriter, req *http.Request) {

View file

@ -102,7 +102,11 @@ func TestNegotiation(t *testing.T) {
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
_, _ = rw.Write(generateBytes(10))
})
handler, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: 1}, "testing")
cfg := dynamic.Compress{
MinResponseBodyBytes: 1,
Encodings: defaultSupportedEncodings,
}
handler, err := New(context.Background(), next, cfg, "testing")
require.NoError(t, err)
rw := httptest.NewRecorder()
@ -123,7 +127,7 @@ func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) {
_, err := rw.Write(baseBody)
assert.NoError(t, err)
})
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing")
handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err)
rw := httptest.NewRecorder()
@ -153,7 +157,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
})
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing")
handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err)
rw := httptest.NewRecorder()
@ -175,7 +179,7 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
})
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing")
handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err)
rw := httptest.NewRecorder()
@ -202,7 +206,7 @@ func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
})
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing")
handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err)
rw := httptest.NewRecorder()
@ -229,7 +233,7 @@ func TestShouldNotCompressWhenEmptyAcceptEncodingHeader(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
})
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing")
handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err)
rw := httptest.NewRecorder()
@ -251,7 +255,7 @@ func TestShouldNotCompressHeadRequest(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
})
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing")
handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err)
rw := httptest.NewRecorder()
@ -274,6 +278,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{
desc: "Exclude Request Content-Type",
conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
ExcludedContentTypes: []string{"text/event-stream"},
},
reqContentType: "text/event-stream",
@ -281,6 +286,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{
desc: "Exclude Response Content-Type",
conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
ExcludedContentTypes: []string{"text/event-stream"},
},
respContentType: "text/event-stream",
@ -288,6 +294,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{
desc: "Include Response Content-Type",
conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
IncludedContentTypes: []string{"text/plain"},
},
respContentType: "text/html",
@ -295,6 +302,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{
desc: "Ignoring application/grpc with exclude option",
conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
ExcludedContentTypes: []string{"application/json"},
},
reqContentType: "application/grpc",
@ -302,13 +310,16 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
{
desc: "Ignoring application/grpc with include option",
conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
IncludedContentTypes: []string{"application/json"},
},
reqContentType: "application/grpc",
},
{
desc: "Ignoring application/grpc with no option",
conf: dynamic.Compress{},
desc: "Ignoring application/grpc with no option",
conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
},
reqContentType: "application/grpc",
},
}
@ -358,6 +369,7 @@ func TestShouldCompressWhenSpecificContentType(t *testing.T) {
{
desc: "Include Response Content-Type",
conf: dynamic.Compress{
Encodings: defaultSupportedEncodings,
IncludedContentTypes: []string{"text/html"},
},
respContentType: "text/html",
@ -429,7 +441,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
compress, err := New(context.Background(), test.handler, dynamic.Compress{}, "testing")
compress, err := New(context.Background(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err)
ts := httptest.NewServer(compress)
@ -464,7 +476,7 @@ func TestShouldWriteHeaderWhenFlush(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
})
handler, err := New(context.Background(), next, dynamic.Compress{}, "testing")
handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err)
ts := httptest.NewServer(handler)
@ -515,7 +527,7 @@ func TestIntegrationShouldCompress(t *testing.T) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
compress, err := New(context.Background(), test.handler, dynamic.Compress{}, "testing")
compress, err := New(context.Background(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing")
require.NoError(t, err)
ts := httptest.NewServer(compress)
@ -571,8 +583,11 @@ func TestMinResponseBodyBytes(t *testing.T) {
http.Error(rw, err.Error(), http.StatusInternalServerError)
}
})
handler, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: test.minResponseBodyBytes}, "testing")
cfg := dynamic.Compress{
MinResponseBodyBytes: test.minResponseBodyBytes,
Encodings: defaultSupportedEncodings,
}
handler, err := New(context.Background(), next, cfg, "testing")
require.NoError(t, err)
rw := httptest.NewRecorder()
@ -607,8 +622,11 @@ func Test1xxResponses(t *testing.T) {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
compress, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: 1024}, "testing")
cfg := dynamic.Compress{
MinResponseBodyBytes: 1024,
Encodings: defaultSupportedEncodings,
}
compress, err := New(context.Background(), next, cfg, "testing")
require.NoError(t, err)
server := httptest.NewServer(compress)

View file

@ -304,7 +304,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
InFlightReq: middleware.Spec.InFlightReq,
Buffering: middleware.Spec.Buffering,
CircuitBreaker: circuitBreaker,
Compress: middleware.Spec.Compress,
Compress: createCompressMiddleware(middleware.Spec.Compress),
PassTLSClientCert: middleware.Spec.PassTLSClientCert,
Retry: retry,
ContentType: middleware.Spec.ContentType,
@ -655,14 +655,49 @@ func createCircuitBreakerMiddleware(circuitBreaker *traefikv1alpha1.CircuitBreak
return cb, nil
}
func createCompressMiddleware(compress *traefikv1alpha1.Compress) *dynamic.Compress {
if compress == nil {
return nil
}
c := &dynamic.Compress{}
c.SetDefaults()
if compress.ExcludedContentTypes != nil {
c.ExcludedContentTypes = compress.ExcludedContentTypes
}
if compress.IncludedContentTypes != nil {
c.IncludedContentTypes = compress.IncludedContentTypes
}
if compress.MinResponseBodyBytes != nil {
c.MinResponseBodyBytes = *compress.MinResponseBodyBytes
}
if compress.Encodings != nil {
c.Encodings = compress.Encodings
}
if compress.DefaultEncoding != nil {
c.DefaultEncoding = *compress.DefaultEncoding
}
return c
}
func createRateLimitMiddleware(rateLimit *traefikv1alpha1.RateLimit) (*dynamic.RateLimit, error) {
if rateLimit == nil {
return nil, nil
}
rl := &dynamic.RateLimit{Average: rateLimit.Average}
rl := &dynamic.RateLimit{}
rl.SetDefaults()
if rateLimit.Average != nil {
rl.Average = *rateLimit.Average
}
if rateLimit.Burst != nil {
rl.Burst = *rateLimit.Burst
}

View file

@ -46,7 +46,7 @@ type MiddlewareSpec struct {
InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"`
Buffering *dynamic.Buffering `json:"buffering,omitempty"`
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
Compress *dynamic.Compress `json:"compress,omitempty"`
Compress *Compress `json:"compress,omitempty"`
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
Retry *Retry `json:"retry,omitempty"`
ContentType *dynamic.ContentType `json:"contentType,omitempty"`
@ -188,7 +188,7 @@ type RateLimit struct {
// It defaults to 0, which means no rate limiting.
// The rate is actually defined by dividing Average by Period. So for a rate below 1req/s,
// one needs to define a Period larger than a second.
Average int64 `json:"average,omitempty"`
Average *int64 `json:"average,omitempty"`
// Period, in combination with Average, defines the actual maximum rate, such as:
// r = Average / Period. It defaults to a second.
Period *intstr.IntOrString `json:"period,omitempty"`
@ -203,6 +203,26 @@ type RateLimit struct {
// +k8s:deepcopy-gen=true
// Compress holds the compress middleware configuration.
// This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression.
// More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
type Compress struct {
// ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
// `application/grpc` is always excluded.
ExcludedContentTypes []string `json:"excludedContentTypes,omitempty"`
// IncludedContentTypes defines the list of content types to compare the Content-Type header of the responses before compressing.
IncludedContentTypes []string `json:"includedContentTypes,omitempty"`
// MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed.
// Default: 1024.
MinResponseBodyBytes *int `json:"minResponseBodyBytes,omitempty"`
// Encodings defines the list of supported compression algorithms.
Encodings []string `json:"encodings,omitempty"`
// DefaultEncoding specifies the default encoding if the `Accept-Encoding` header is not in the request or contains a wildcard (`*`).
DefaultEncoding *string `json:"defaultEncoding,omitempty"`
}
// +k8s:deepcopy-gen=true
// Retry holds the retry middleware configuration.
// This middleware reissues requests a given number of times to a backend server if that server does not reply.
// As soon as the server answers, the middleware stops retrying, regardless of the response status.

View file

@ -164,6 +164,47 @@ func (in *ClientTLS) DeepCopy() *ClientTLS {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Compress) DeepCopyInto(out *Compress) {
*out = *in
if in.ExcludedContentTypes != nil {
in, out := &in.ExcludedContentTypes, &out.ExcludedContentTypes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.IncludedContentTypes != nil {
in, out := &in.IncludedContentTypes, &out.IncludedContentTypes
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.MinResponseBodyBytes != nil {
in, out := &in.MinResponseBodyBytes, &out.MinResponseBodyBytes
*out = new(int)
**out = **in
}
if in.Encodings != nil {
in, out := &in.Encodings, &out.Encodings
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DefaultEncoding != nil {
in, out := &in.DefaultEncoding, &out.DefaultEncoding
*out = new(string)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Compress.
func (in *Compress) DeepCopy() *Compress {
if in == nil {
return nil
}
out := new(Compress)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DigestAuth) DeepCopyInto(out *DigestAuth) {
*out = *in
@ -776,7 +817,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
}
if in.Compress != nil {
in, out := &in.Compress, &out.Compress
*out = new(dynamic.Compress)
*out = new(Compress)
(*in).DeepCopyInto(*out)
}
if in.PassTLSClientCert != nil {
@ -975,6 +1016,11 @@ func (in *ObjectReference) DeepCopy() *ObjectReference {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RateLimit) DeepCopyInto(out *RateLimit) {
*out = *in
if in.Average != nil {
in, out := &in.Average, &out.Average
*out = new(int64)
**out = **in
}
if in.Period != nil {
in, out := &in.Period, &out.Period
*out = new(intstr.IntOrString)

View file

@ -207,6 +207,7 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/http/middlewares/Middleware02/buffering/retryExpression": "foobar",
"traefik/http/middlewares/Middleware02/buffering/maxRequestBodyBytes": "42",
"traefik/http/middlewares/Middleware02/buffering/memRequestBodyBytes": "42",
"traefik/http/middlewares/Middleware05/compress/encodings": "foobar, foobar",
"traefik/http/middlewares/Middleware05/compress/minResponseBodyBytes": "42",
"traefik/http/middlewares/Middleware18/retry/attempts": "42",
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0": "foobar",
@ -412,6 +413,10 @@ func Test_buildConfiguration(t *testing.T) {
"Middleware05": {
Compress: &dynamic.Compress{
MinResponseBodyBytes: 42,
Encodings: []string{
"foobar",
"foobar",
},
},
},
"Middleware08": {