Add encodings option to the compression middleware
This commit is contained in:
parent
b611f967b7
commit
75881359ab
19 changed files with 389 additions and 92 deletions
|
@ -255,3 +255,48 @@ http:
|
||||||
[http.middlewares.test-compress.compress]
|
[http.middlewares.test-compress.compress]
|
||||||
defaultEncoding = "gzip"
|
defaultEncoding = "gzip"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `encodings`
|
||||||
|
|
||||||
|
_Optional, Default="zstd, br, gzip"_
|
||||||
|
|
||||||
|
`encodings` specifies the list of supported compression encodings.
|
||||||
|
At least one encoding value must be specified, and valid entries are `zstd` (Zstandard), `br` (Brotli), and `gzip` (Gzip).
|
||||||
|
The order of the list also sets the priority, the top entry has the highest priority.
|
||||||
|
|
||||||
|
```yaml tab="Docker & Swarm"
|
||||||
|
labels:
|
||||||
|
- "traefik.http.middlewares.test-compress.compress.encodings=zstd,br"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Kubernetes"
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: test-compress
|
||||||
|
spec:
|
||||||
|
compress:
|
||||||
|
encodings:
|
||||||
|
- zstd
|
||||||
|
- br
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Consul Catalog"
|
||||||
|
- "traefik.http.middlewares.test-compress.compress.encodings=zstd,br"
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
http:
|
||||||
|
middlewares:
|
||||||
|
test-compress:
|
||||||
|
compress:
|
||||||
|
encodings:
|
||||||
|
- zstd
|
||||||
|
- br
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[http.middlewares]
|
||||||
|
[http.middlewares.test-compress.compress]
|
||||||
|
encodings = ["zstd","br"]
|
||||||
|
```
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
- "traefik.http.middlewares.middleware05.circuitbreaker.responsecode=42"
|
- "traefik.http.middlewares.middleware05.circuitbreaker.responsecode=42"
|
||||||
- "traefik.http.middlewares.middleware06.compress=true"
|
- "traefik.http.middlewares.middleware06.compress=true"
|
||||||
- "traefik.http.middlewares.middleware06.compress.defaultencoding=foobar"
|
- "traefik.http.middlewares.middleware06.compress.defaultencoding=foobar"
|
||||||
|
- "traefik.http.middlewares.middleware06.compress.encodings=foobar, foobar"
|
||||||
- "traefik.http.middlewares.middleware06.compress.excludedcontenttypes=foobar, foobar"
|
- "traefik.http.middlewares.middleware06.compress.excludedcontenttypes=foobar, foobar"
|
||||||
- "traefik.http.middlewares.middleware06.compress.includedcontenttypes=foobar, foobar"
|
- "traefik.http.middlewares.middleware06.compress.includedcontenttypes=foobar, foobar"
|
||||||
- "traefik.http.middlewares.middleware06.compress.minresponsebodybytes=42"
|
- "traefik.http.middlewares.middleware06.compress.minresponsebodybytes=42"
|
||||||
|
|
|
@ -143,6 +143,7 @@
|
||||||
excludedContentTypes = ["foobar", "foobar"]
|
excludedContentTypes = ["foobar", "foobar"]
|
||||||
includedContentTypes = ["foobar", "foobar"]
|
includedContentTypes = ["foobar", "foobar"]
|
||||||
minResponseBodyBytes = 42
|
minResponseBodyBytes = 42
|
||||||
|
encodings = ["foobar", "foobar"]
|
||||||
defaultEncoding = "foobar"
|
defaultEncoding = "foobar"
|
||||||
[http.middlewares.Middleware07]
|
[http.middlewares.Middleware07]
|
||||||
[http.middlewares.Middleware07.contentType]
|
[http.middlewares.Middleware07.contentType]
|
||||||
|
|
|
@ -152,6 +152,9 @@ http:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
minResponseBodyBytes: 42
|
minResponseBodyBytes: 42
|
||||||
|
encodings:
|
||||||
|
- foobar
|
||||||
|
- foobar
|
||||||
defaultEncoding: foobar
|
defaultEncoding: foobar
|
||||||
Middleware07:
|
Middleware07:
|
||||||
contentType:
|
contentType:
|
||||||
|
|
|
@ -904,7 +904,7 @@ spec:
|
||||||
compress:
|
compress:
|
||||||
description: |-
|
description: |-
|
||||||
Compress holds the compress middleware configuration.
|
Compress holds the compress middleware configuration.
|
||||||
This middleware compresses responses before sending them to the client, using gzip compression.
|
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/
|
More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
|
||||||
properties:
|
properties:
|
||||||
defaultEncoding:
|
defaultEncoding:
|
||||||
|
@ -912,6 +912,12 @@ spec:
|
||||||
the `Accept-Encoding` header is not in the request or contains
|
the `Accept-Encoding` header is not in the request or contains
|
||||||
a wildcard (`*`).
|
a wildcard (`*`).
|
||||||
type: string
|
type: string
|
||||||
|
encodings:
|
||||||
|
description: Encodings defines the list of supported compression
|
||||||
|
algorithms.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
excludedContentTypes:
|
excludedContentTypes:
|
||||||
description: |-
|
description: |-
|
||||||
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
||||||
|
|
|
@ -22,6 +22,8 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
||||||
| `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` |
|
| `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` |
|
||||||
| `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` |
|
| `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` |
|
||||||
| `traefik/http/middlewares/Middleware06/compress/defaultEncoding` | `foobar` |
|
| `traefik/http/middlewares/Middleware06/compress/defaultEncoding` | `foobar` |
|
||||||
|
| `traefik/http/middlewares/Middleware06/compress/encodings/0` | `foobar` |
|
||||||
|
| `traefik/http/middlewares/Middleware06/compress/encodings/1` | `foobar` |
|
||||||
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` |
|
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` |
|
||||||
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` |
|
| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` |
|
||||||
| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` |
|
| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` |
|
||||||
|
|
|
@ -180,7 +180,7 @@ spec:
|
||||||
compress:
|
compress:
|
||||||
description: |-
|
description: |-
|
||||||
Compress holds the compress middleware configuration.
|
Compress holds the compress middleware configuration.
|
||||||
This middleware compresses responses before sending them to the client, using gzip compression.
|
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/
|
More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
|
||||||
properties:
|
properties:
|
||||||
defaultEncoding:
|
defaultEncoding:
|
||||||
|
@ -188,6 +188,12 @@ spec:
|
||||||
the `Accept-Encoding` header is not in the request or contains
|
the `Accept-Encoding` header is not in the request or contains
|
||||||
a wildcard (`*`).
|
a wildcard (`*`).
|
||||||
type: string
|
type: string
|
||||||
|
encodings:
|
||||||
|
description: Encodings defines the list of supported compression
|
||||||
|
algorithms.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
excludedContentTypes:
|
excludedContentTypes:
|
||||||
description: |-
|
description: |-
|
||||||
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
||||||
|
|
|
@ -904,7 +904,7 @@ spec:
|
||||||
compress:
|
compress:
|
||||||
description: |-
|
description: |-
|
||||||
Compress holds the compress middleware configuration.
|
Compress holds the compress middleware configuration.
|
||||||
This middleware compresses responses before sending them to the client, using gzip compression.
|
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/
|
More info: https://doc.traefik.io/traefik/v3.1/middlewares/http/compress/
|
||||||
properties:
|
properties:
|
||||||
defaultEncoding:
|
defaultEncoding:
|
||||||
|
@ -912,6 +912,12 @@ spec:
|
||||||
the `Accept-Encoding` header is not in the request or contains
|
the `Accept-Encoding` header is not in the request or contains
|
||||||
a wildcard (`*`).
|
a wildcard (`*`).
|
||||||
type: string
|
type: string
|
||||||
|
encodings:
|
||||||
|
description: Encodings defines the list of supported compression
|
||||||
|
algorithms.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
excludedContentTypes:
|
excludedContentTypes:
|
||||||
description: |-
|
description: |-
|
||||||
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
||||||
|
|
|
@ -165,8 +165,7 @@ func (c *CircuitBreaker) SetDefaults() {
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// Compress holds the compress middleware configuration.
|
// Compress holds the compress middleware configuration.
|
||||||
// This middleware compresses responses before sending them to the client, using gzip compression.
|
// 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 {
|
type Compress struct {
|
||||||
// ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
|
// 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.
|
// `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.
|
// MinResponseBodyBytes defines the minimum amount of bytes a response body must have to be compressed.
|
||||||
// Default: 1024.
|
// Default: 1024.
|
||||||
MinResponseBodyBytes int `json:"minResponseBodyBytes,omitempty" toml:"minResponseBodyBytes,omitempty" yaml:"minResponseBodyBytes,omitempty" export:"true"`
|
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 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"`
|
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
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// DigestAuth holds the digest auth middleware configuration.
|
// DigestAuth holds the digest auth middleware configuration.
|
||||||
|
|
|
@ -158,6 +158,11 @@ func (in *Compress) DeepCopyInto(out *Compress) {
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.Encodings != nil {
|
||||||
|
in, out := &in.Encodings, &out.Encodings
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,6 +137,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
|
"traefik.http.middlewares.Middleware17.stripprefix.prefixes": "foobar, fiibar",
|
||||||
"traefik.http.middlewares.Middleware17.stripprefix.forceslash": "true",
|
"traefik.http.middlewares.Middleware17.stripprefix.forceslash": "true",
|
||||||
"traefik.http.middlewares.Middleware18.stripprefixregex.regex": "foobar, fiibar",
|
"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.Middleware19.compress.minresponsebodybytes": "42",
|
||||||
"traefik.http.middlewares.Middleware20.plugin.tomato.aaa": "foo1",
|
"traefik.http.middlewares.Middleware20.plugin.tomato.aaa": "foo1",
|
||||||
"traefik.http.middlewares.Middleware20.plugin.tomato.bbb": "foo2",
|
"traefik.http.middlewares.Middleware20.plugin.tomato.bbb": "foo2",
|
||||||
|
@ -493,6 +494,10 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
"Middleware19": {
|
"Middleware19": {
|
||||||
Compress: &dynamic.Compress{
|
Compress: &dynamic.Compress{
|
||||||
MinResponseBodyBytes: 42,
|
MinResponseBodyBytes: 42,
|
||||||
|
Encodings: []string{
|
||||||
|
"foobar",
|
||||||
|
"fiibar",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware2": {
|
"Middleware2": {
|
||||||
|
@ -1009,6 +1014,10 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
"Middleware19": {
|
"Middleware19": {
|
||||||
Compress: &dynamic.Compress{
|
Compress: &dynamic.Compress{
|
||||||
MinResponseBodyBytes: 42,
|
MinResponseBodyBytes: 42,
|
||||||
|
Encodings: []string{
|
||||||
|
"foobar",
|
||||||
|
"fiibar",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware2": {
|
"Middleware2": {
|
||||||
|
@ -1377,6 +1386,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
|
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.Prefixes": "foobar, fiibar",
|
||||||
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
|
"traefik.HTTP.Middlewares.Middleware17.StripPrefix.ForceSlash": "true",
|
||||||
"traefik.HTTP.Middlewares.Middleware18.StripPrefixRegex.Regex": "foobar, fiibar",
|
"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.Middleware19.Compress.MinResponseBodyBytes": "42",
|
||||||
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1",
|
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.aaa": "foo1",
|
||||||
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.bbb": "foo2",
|
"traefik.HTTP.Middlewares.Middleware20.Plugin.tomato.bbb": "foo2",
|
||||||
|
|
|
@ -22,13 +22,18 @@ type Encoding struct {
|
||||||
Weight *float64
|
Weight *float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCompressionType(acceptEncoding []string, defaultType string) string {
|
func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, supportedEncodings []string) string {
|
||||||
if defaultType == "" {
|
if defaultEncoding == "" {
|
||||||
// Keeps the pre-existing default inside Traefik.
|
if slices.Contains(supportedEncodings, brotliName) {
|
||||||
defaultType = 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 hasWeight {
|
||||||
if len(encodings) == 0 {
|
if len(encodings) == 0 {
|
||||||
|
@ -46,26 +51,26 @@ func getCompressionType(acceptEncoding []string, defaultType string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if encoding.Type == wildcardName {
|
if encoding.Type == wildcardName {
|
||||||
return defaultType
|
return defaultEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
return encoding.Type
|
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 }) {
|
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == dt }) {
|
||||||
return dt
|
return dt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == wildcardName }) {
|
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == wildcardName }) {
|
||||||
return defaultType
|
return defaultEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
return identityName
|
return identityName
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAcceptEncoding(acceptEncoding []string) ([]Encoding, bool) {
|
func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encoding, bool) {
|
||||||
var encodings []Encoding
|
var encodings []Encoding
|
||||||
var hasWeight bool
|
var hasWeight bool
|
||||||
|
|
||||||
|
@ -76,10 +81,9 @@ func parseAcceptEncoding(acceptEncoding []string) ([]Encoding, bool) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch parsed[0] {
|
if !slices.Contains(supportedEncodings, parsed[0]) &&
|
||||||
case zstdName, brotliName, gzipName, identityName, wildcardName:
|
parsed[0] != identityName &&
|
||||||
// supported encoding
|
parsed[0] != wildcardName {
|
||||||
default:
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,73 +6,86 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_getCompressionType(t *testing.T) {
|
func Test_getCompressionEncoding(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
values []string
|
acceptEncoding []string
|
||||||
defaultType string
|
defaultEncoding string
|
||||||
expected string
|
supportedEncodings []string
|
||||||
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "br > gzip (no weight)",
|
desc: "br > gzip (no weight)",
|
||||||
values: []string{"gzip, br"},
|
acceptEncoding: []string{"gzip, br"},
|
||||||
expected: brotliName,
|
expected: brotliName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "zstd > br > gzip (no weight)",
|
desc: "zstd > br > gzip (no weight)",
|
||||||
values: []string{"zstd, gzip, br"},
|
acceptEncoding: []string{"zstd, gzip, br"},
|
||||||
expected: zstdName,
|
expected: zstdName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "known compression type (no weight)",
|
desc: "known compression encoding (no weight)",
|
||||||
values: []string{"compress, gzip"},
|
acceptEncoding: []string{"compress, gzip"},
|
||||||
expected: gzipName,
|
expected: gzipName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "unknown compression type (no weight), no encoding",
|
desc: "unknown compression encoding (no weight), no encoding",
|
||||||
values: []string{"compress, rar"},
|
acceptEncoding: []string{"compress, rar"},
|
||||||
expected: identityName,
|
expected: identityName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "wildcard return the default compression type",
|
desc: "wildcard return the default compression encoding",
|
||||||
values: []string{"*"},
|
acceptEncoding: []string{"*"},
|
||||||
expected: brotliName,
|
expected: brotliName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "wildcard return the custom default compression type",
|
desc: "wildcard return the custom default compression encoding",
|
||||||
values: []string{"*"},
|
acceptEncoding: []string{"*"},
|
||||||
defaultType: "foo",
|
defaultEncoding: "foo",
|
||||||
expected: "foo",
|
expected: "foo",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "follows weight",
|
desc: "follows weight",
|
||||||
values: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"},
|
acceptEncoding: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"},
|
||||||
expected: gzipName,
|
expected: gzipName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ignore unknown compression type",
|
desc: "ignore unknown compression encoding",
|
||||||
values: []string{"compress;q=1.0, gzip;q=0.5"},
|
acceptEncoding: []string{"compress;q=1.0, gzip;q=0.5"},
|
||||||
expected: gzipName,
|
expected: gzipName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "fallback on non-zero compression type",
|
desc: "fallback on non-zero compression encoding",
|
||||||
values: []string{"compress;q=1.0, gzip, identity;q=0"},
|
acceptEncoding: []string{"compress;q=1.0, gzip, identity;q=0"},
|
||||||
expected: gzipName,
|
expected: gzipName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "not acceptable (identity)",
|
desc: "not acceptable (identity)",
|
||||||
values: []string{"compress;q=1.0, identity;q=0"},
|
acceptEncoding: []string{"compress;q=1.0, identity;q=0"},
|
||||||
expected: notAcceptable,
|
expected: notAcceptable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "not acceptable (wildcard)",
|
desc: "not acceptable (wildcard)",
|
||||||
values: []string{"compress;q=1.0, *;q=0"},
|
acceptEncoding: []string{"compress;q=1.0, *;q=0"},
|
||||||
expected: notAcceptable,
|
expected: notAcceptable,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "non-zero is higher than 0",
|
desc: "non-zero is higher than 0",
|
||||||
values: []string{"gzip, *;q=0"},
|
acceptEncoding: []string{"gzip, *;q=0"},
|
||||||
expected: gzipName,
|
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.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
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) {
|
func Test_parseAcceptEncoding(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
values []string
|
values []string
|
||||||
expected []Encoding
|
supportedEncodings []string
|
||||||
assertWeight assert.BoolAssertionFunc
|
expected []Encoding
|
||||||
|
assertWeight assert.BoolAssertionFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "weight",
|
desc: "weight",
|
||||||
|
@ -105,6 +123,17 @@ func Test_parseAcceptEncoding(t *testing.T) {
|
||||||
},
|
},
|
||||||
assertWeight: assert.True,
|
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",
|
desc: "mixed",
|
||||||
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
|
values: []string{"zstd,gzip, br;q=1.0, *;q=0"},
|
||||||
|
@ -116,6 +145,16 @@ func Test_parseAcceptEncoding(t *testing.T) {
|
||||||
},
|
},
|
||||||
assertWeight: assert.True,
|
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",
|
desc: "no weight",
|
||||||
values: []string{"zstd, gzip, br, *"},
|
values: []string{"zstd, gzip, br, *"},
|
||||||
|
@ -127,6 +166,16 @@ func Test_parseAcceptEncoding(t *testing.T) {
|
||||||
},
|
},
|
||||||
assertWeight: assert.False,
|
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",
|
desc: "weight and identity",
|
||||||
values: []string{"gzip;q=1.0, identity; q=0.5, *;q=0"},
|
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,
|
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 {
|
for _, test := range testCases {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
t.Parallel()
|
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)
|
assert.Equal(t, test.expected, aes)
|
||||||
test.assertWeight(t, hasWeight)
|
test.assertWeight(t, hasWeight)
|
||||||
|
|
|
@ -16,9 +16,11 @@ import (
|
||||||
|
|
||||||
const typeName = "Compress"
|
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.
|
// 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.
|
// Compress is a middleware that allows to compress the response.
|
||||||
type compress struct {
|
type compress struct {
|
||||||
|
@ -27,6 +29,7 @@ type compress struct {
|
||||||
excludes []string
|
excludes []string
|
||||||
includes []string
|
includes []string
|
||||||
minSize int
|
minSize int
|
||||||
|
encodings []string
|
||||||
defaultEncoding string
|
defaultEncoding string
|
||||||
|
|
||||||
brotliHandler http.Handler
|
brotliHandler http.Handler
|
||||||
|
@ -62,17 +65,30 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str
|
||||||
includes = append(includes, mediaType)
|
includes = append(includes, mediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
minSize := DefaultMinSize
|
minSize := defaultMinSize
|
||||||
if conf.MinResponseBodyBytes > 0 {
|
if conf.MinResponseBodyBytes > 0 {
|
||||||
minSize = conf.MinResponseBodyBytes
|
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{
|
c := &compress{
|
||||||
next: next,
|
next: next,
|
||||||
name: name,
|
name: name,
|
||||||
excludes: excludes,
|
excludes: excludes,
|
||||||
includes: includes,
|
includes: includes,
|
||||||
minSize: minSize,
|
minSize: minSize,
|
||||||
|
encodings: conf.Encodings,
|
||||||
defaultEncoding: conf.DefaultEncoding,
|
defaultEncoding: conf.DefaultEncoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +147,7 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
return
|
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) {
|
func (c *compress) chooseHandler(typ string, rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
@ -102,7 +102,11 @@ func TestNegotiation(t *testing.T) {
|
||||||
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
_, _ = rw.Write(generateBytes(10))
|
_, _ = 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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
@ -123,7 +127,7 @@ func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) {
|
||||||
_, err := rw.Write(baseBody)
|
_, err := rw.Write(baseBody)
|
||||||
assert.NoError(t, err)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
@ -153,7 +157,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
@ -175,7 +179,7 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
@ -202,7 +206,7 @@ func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
@ -229,7 +233,7 @@ func TestShouldNotCompressWhenEmptyAcceptEncodingHeader(t *testing.T) {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
@ -251,7 +255,7 @@ func TestShouldNotCompressHeadRequest(t *testing.T) {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
@ -274,6 +278,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "Exclude Request Content-Type",
|
desc: "Exclude Request Content-Type",
|
||||||
conf: dynamic.Compress{
|
conf: dynamic.Compress{
|
||||||
|
Encodings: defaultSupportedEncodings,
|
||||||
ExcludedContentTypes: []string{"text/event-stream"},
|
ExcludedContentTypes: []string{"text/event-stream"},
|
||||||
},
|
},
|
||||||
reqContentType: "text/event-stream",
|
reqContentType: "text/event-stream",
|
||||||
|
@ -281,6 +286,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "Exclude Response Content-Type",
|
desc: "Exclude Response Content-Type",
|
||||||
conf: dynamic.Compress{
|
conf: dynamic.Compress{
|
||||||
|
Encodings: defaultSupportedEncodings,
|
||||||
ExcludedContentTypes: []string{"text/event-stream"},
|
ExcludedContentTypes: []string{"text/event-stream"},
|
||||||
},
|
},
|
||||||
respContentType: "text/event-stream",
|
respContentType: "text/event-stream",
|
||||||
|
@ -288,6 +294,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "Include Response Content-Type",
|
desc: "Include Response Content-Type",
|
||||||
conf: dynamic.Compress{
|
conf: dynamic.Compress{
|
||||||
|
Encodings: defaultSupportedEncodings,
|
||||||
IncludedContentTypes: []string{"text/plain"},
|
IncludedContentTypes: []string{"text/plain"},
|
||||||
},
|
},
|
||||||
respContentType: "text/html",
|
respContentType: "text/html",
|
||||||
|
@ -295,6 +302,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "Ignoring application/grpc with exclude option",
|
desc: "Ignoring application/grpc with exclude option",
|
||||||
conf: dynamic.Compress{
|
conf: dynamic.Compress{
|
||||||
|
Encodings: defaultSupportedEncodings,
|
||||||
ExcludedContentTypes: []string{"application/json"},
|
ExcludedContentTypes: []string{"application/json"},
|
||||||
},
|
},
|
||||||
reqContentType: "application/grpc",
|
reqContentType: "application/grpc",
|
||||||
|
@ -302,13 +310,16 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "Ignoring application/grpc with include option",
|
desc: "Ignoring application/grpc with include option",
|
||||||
conf: dynamic.Compress{
|
conf: dynamic.Compress{
|
||||||
|
Encodings: defaultSupportedEncodings,
|
||||||
IncludedContentTypes: []string{"application/json"},
|
IncludedContentTypes: []string{"application/json"},
|
||||||
},
|
},
|
||||||
reqContentType: "application/grpc",
|
reqContentType: "application/grpc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Ignoring application/grpc with no option",
|
desc: "Ignoring application/grpc with no option",
|
||||||
conf: dynamic.Compress{},
|
conf: dynamic.Compress{
|
||||||
|
Encodings: defaultSupportedEncodings,
|
||||||
|
},
|
||||||
reqContentType: "application/grpc",
|
reqContentType: "application/grpc",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -358,6 +369,7 @@ func TestShouldCompressWhenSpecificContentType(t *testing.T) {
|
||||||
{
|
{
|
||||||
desc: "Include Response Content-Type",
|
desc: "Include Response Content-Type",
|
||||||
conf: dynamic.Compress{
|
conf: dynamic.Compress{
|
||||||
|
Encodings: defaultSupportedEncodings,
|
||||||
IncludedContentTypes: []string{"text/html"},
|
IncludedContentTypes: []string{"text/html"},
|
||||||
},
|
},
|
||||||
respContentType: "text/html",
|
respContentType: "text/html",
|
||||||
|
@ -429,7 +441,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ts := httptest.NewServer(compress)
|
ts := httptest.NewServer(compress)
|
||||||
|
@ -464,7 +476,7 @@ func TestShouldWriteHeaderWhenFlush(t *testing.T) {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ts := httptest.NewServer(handler)
|
ts := httptest.NewServer(handler)
|
||||||
|
@ -515,7 +527,7 @@ func TestIntegrationShouldCompress(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ts := httptest.NewServer(compress)
|
ts := httptest.NewServer(compress)
|
||||||
|
@ -571,8 +583,11 @@ func TestMinResponseBodyBytes(t *testing.T) {
|
||||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
cfg := dynamic.Compress{
|
||||||
handler, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: test.minResponseBodyBytes}, "testing")
|
MinResponseBodyBytes: test.minResponseBodyBytes,
|
||||||
|
Encodings: defaultSupportedEncodings,
|
||||||
|
}
|
||||||
|
handler, err := New(context.Background(), next, cfg, "testing")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
rw := httptest.NewRecorder()
|
rw := httptest.NewRecorder()
|
||||||
|
@ -607,8 +622,11 @@ func Test1xxResponses(t *testing.T) {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
cfg := dynamic.Compress{
|
||||||
compress, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: 1024}, "testing")
|
MinResponseBodyBytes: 1024,
|
||||||
|
Encodings: defaultSupportedEncodings,
|
||||||
|
}
|
||||||
|
compress, err := New(context.Background(), next, cfg, "testing")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
server := httptest.NewServer(compress)
|
server := httptest.NewServer(compress)
|
||||||
|
|
|
@ -304,7 +304,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||||
InFlightReq: middleware.Spec.InFlightReq,
|
InFlightReq: middleware.Spec.InFlightReq,
|
||||||
Buffering: middleware.Spec.Buffering,
|
Buffering: middleware.Spec.Buffering,
|
||||||
CircuitBreaker: circuitBreaker,
|
CircuitBreaker: circuitBreaker,
|
||||||
Compress: middleware.Spec.Compress,
|
Compress: createCompressMiddleware(middleware.Spec.Compress),
|
||||||
PassTLSClientCert: middleware.Spec.PassTLSClientCert,
|
PassTLSClientCert: middleware.Spec.PassTLSClientCert,
|
||||||
Retry: retry,
|
Retry: retry,
|
||||||
ContentType: middleware.Spec.ContentType,
|
ContentType: middleware.Spec.ContentType,
|
||||||
|
@ -655,14 +655,49 @@ func createCircuitBreakerMiddleware(circuitBreaker *traefikv1alpha1.CircuitBreak
|
||||||
return cb, nil
|
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) {
|
func createRateLimitMiddleware(rateLimit *traefikv1alpha1.RateLimit) (*dynamic.RateLimit, error) {
|
||||||
if rateLimit == nil {
|
if rateLimit == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rl := &dynamic.RateLimit{Average: rateLimit.Average}
|
rl := &dynamic.RateLimit{}
|
||||||
rl.SetDefaults()
|
rl.SetDefaults()
|
||||||
|
|
||||||
|
if rateLimit.Average != nil {
|
||||||
|
rl.Average = *rateLimit.Average
|
||||||
|
}
|
||||||
|
|
||||||
if rateLimit.Burst != nil {
|
if rateLimit.Burst != nil {
|
||||||
rl.Burst = *rateLimit.Burst
|
rl.Burst = *rateLimit.Burst
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ type MiddlewareSpec struct {
|
||||||
InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"`
|
InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"`
|
||||||
Buffering *dynamic.Buffering `json:"buffering,omitempty"`
|
Buffering *dynamic.Buffering `json:"buffering,omitempty"`
|
||||||
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
|
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
|
||||||
Compress *dynamic.Compress `json:"compress,omitempty"`
|
Compress *Compress `json:"compress,omitempty"`
|
||||||
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
|
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
|
||||||
Retry *Retry `json:"retry,omitempty"`
|
Retry *Retry `json:"retry,omitempty"`
|
||||||
ContentType *dynamic.ContentType `json:"contentType,omitempty"`
|
ContentType *dynamic.ContentType `json:"contentType,omitempty"`
|
||||||
|
@ -188,7 +188,7 @@ type RateLimit struct {
|
||||||
// It defaults to 0, which means no rate limiting.
|
// 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,
|
// 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.
|
// 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:
|
// Period, in combination with Average, defines the actual maximum rate, such as:
|
||||||
// r = Average / Period. It defaults to a second.
|
// r = Average / Period. It defaults to a second.
|
||||||
Period *intstr.IntOrString `json:"period,omitempty"`
|
Period *intstr.IntOrString `json:"period,omitempty"`
|
||||||
|
@ -203,6 +203,26 @@ type RateLimit struct {
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +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.
|
// 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.
|
// 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.
|
// As soon as the server answers, the middleware stops retrying, regardless of the response status.
|
||||||
|
|
|
@ -164,6 +164,47 @@ func (in *ClientTLS) DeepCopy() *ClientTLS {
|
||||||
return out
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *DigestAuth) DeepCopyInto(out *DigestAuth) {
|
func (in *DigestAuth) DeepCopyInto(out *DigestAuth) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@ -776,7 +817,7 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
|
||||||
}
|
}
|
||||||
if in.Compress != nil {
|
if in.Compress != nil {
|
||||||
in, out := &in.Compress, &out.Compress
|
in, out := &in.Compress, &out.Compress
|
||||||
*out = new(dynamic.Compress)
|
*out = new(Compress)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
if in.PassTLSClientCert != nil {
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *RateLimit) DeepCopyInto(out *RateLimit) {
|
func (in *RateLimit) DeepCopyInto(out *RateLimit) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.Average != nil {
|
||||||
|
in, out := &in.Average, &out.Average
|
||||||
|
*out = new(int64)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.Period != nil {
|
if in.Period != nil {
|
||||||
in, out := &in.Period, &out.Period
|
in, out := &in.Period, &out.Period
|
||||||
*out = new(intstr.IntOrString)
|
*out = new(intstr.IntOrString)
|
||||||
|
|
|
@ -207,6 +207,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
"traefik/http/middlewares/Middleware02/buffering/retryExpression": "foobar",
|
"traefik/http/middlewares/Middleware02/buffering/retryExpression": "foobar",
|
||||||
"traefik/http/middlewares/Middleware02/buffering/maxRequestBodyBytes": "42",
|
"traefik/http/middlewares/Middleware02/buffering/maxRequestBodyBytes": "42",
|
||||||
"traefik/http/middlewares/Middleware02/buffering/memRequestBodyBytes": "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/Middleware05/compress/minResponseBodyBytes": "42",
|
||||||
"traefik/http/middlewares/Middleware18/retry/attempts": "42",
|
"traefik/http/middlewares/Middleware18/retry/attempts": "42",
|
||||||
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0": "foobar",
|
"traefik/http/middlewares/Middleware19/stripPrefix/prefixes/0": "foobar",
|
||||||
|
@ -412,6 +413,10 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
"Middleware05": {
|
"Middleware05": {
|
||||||
Compress: &dynamic.Compress{
|
Compress: &dynamic.Compress{
|
||||||
MinResponseBodyBytes: 42,
|
MinResponseBodyBytes: 42,
|
||||||
|
Encodings: []string{
|
||||||
|
"foobar",
|
||||||
|
"foobar",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"Middleware08": {
|
"Middleware08": {
|
||||||
|
|
Loading…
Reference in a new issue