Disable Content-Type auto-detection by default
This commit is contained in:
parent
4d86668af3
commit
db287c4d31
20 changed files with 193 additions and 168 deletions
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
title: "Traefik ContentType Documentation"
|
||||
description: "Traefik Proxy's HTTP middleware can automatically specify the content-type header if it has not been defined by the backend. Read the technical documentation."
|
||||
description: "Traefik Proxy's HTTP middleware automatically sets the `Content-Type` header value when it is not set by the backend. Read the technical documentation."
|
||||
---
|
||||
|
||||
# ContentType
|
||||
|
@ -8,84 +8,59 @@ description: "Traefik Proxy's HTTP middleware can automatically specify the cont
|
|||
Handling Content-Type auto-detection
|
||||
{: .subtitle }
|
||||
|
||||
The Content-Type middleware - or rather its `autoDetect` option -
|
||||
specifies whether to let the `Content-Type` header,
|
||||
if it has not been defined by the backend,
|
||||
be automatically set to a value derived from the contents of the response.
|
||||
|
||||
As a proxy, the default behavior should be to leave the header alone,
|
||||
regardless of what the backend did with it.
|
||||
However, the historic default was to always auto-detect and set the header if it was not already defined,
|
||||
and altering this behavior would be a breaking change which would impact many users.
|
||||
|
||||
This middleware exists to enable the correct behavior until at least the default one can be changed in a future version.
|
||||
The Content-Type middleware sets the `Content-Type` header value to the media type detected from the response content,
|
||||
when it is not set by the backend.
|
||||
|
||||
!!! info
|
||||
|
||||
As explained above, for compatibility reasons the default behavior on a router (without this middleware),
|
||||
is still to automatically set the `Content-Type` header.
|
||||
Therefore, given the default value of the `autoDetect` option (false),
|
||||
simply enabling this middleware for a router switches the router's behavior.
|
||||
|
||||
The scope of the Content-Type middleware is the MIME type detection done by the core of Traefik (the server part).
|
||||
Therefore, it has no effect against any other `Content-Type` header modifications (e.g.: in another middleware such as compress).
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
```yaml tab="Docker"
|
||||
# Disable auto-detection
|
||||
# Enable auto-detection
|
||||
labels:
|
||||
- "traefik.http.middlewares.autodetect.contenttype.autodetect=false"
|
||||
- "traefik.http.middlewares.autodetect.contenttype=true"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
# Disable auto-detection
|
||||
# Enable auto-detection
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: autodetect
|
||||
spec:
|
||||
contentType:
|
||||
autoDetect: false
|
||||
contentType: {}
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
# Disable auto-detection
|
||||
- "traefik.http.middlewares.autodetect.contenttype.autodetect=false"
|
||||
# Enable auto-detection
|
||||
- "traefik.http.middlewares.autodetect.contenttype=true"
|
||||
```
|
||||
|
||||
```json tab="Marathon"
|
||||
"labels": {
|
||||
"traefik.http.middlewares.autodetect.contenttype.autodetect": "false"
|
||||
"traefik.http.middlewares.autodetect.contenttype": "true"
|
||||
}
|
||||
```
|
||||
|
||||
```yaml tab="Rancher"
|
||||
# Disable auto-detection
|
||||
# Enable auto-detection
|
||||
labels:
|
||||
- "traefik.http.middlewares.autodetect.contenttype.autodetect=false"
|
||||
- "traefik.http.middlewares.autodetect.contenttype=true"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
# Disable auto-detection
|
||||
# Enable auto-detection
|
||||
http:
|
||||
middlewares:
|
||||
autodetect:
|
||||
contentType:
|
||||
autoDetect: false
|
||||
contentType: {}
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
# Disable auto-detection
|
||||
# Enable auto-detection
|
||||
[http.middlewares]
|
||||
[http.middlewares.autodetect.contentType]
|
||||
autoDetect=false
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### `autoDetect`
|
||||
|
||||
`autoDetect` specifies whether to let the `Content-Type` header,
|
||||
if it has not been set by the backend,
|
||||
be automatically set to a value derived from the contents of the response.
|
||||
```
|
|
@ -45,3 +45,8 @@ and should be explicitly combined using logical operators to mimic previous beha
|
|||
`Query` can take a single value to match is the query value that has no value (e.g. `/search?mobile`).
|
||||
|
||||
`HostHeader` has been removed, use `Host` instead.
|
||||
|
||||
## Content-Type Auto-Detection
|
||||
|
||||
In v3, the `Content-Type` header is not auto-detected anymore when it is not set by the backend.
|
||||
One should use the `ContentType` middleware to enable the `Content-Type` header value auto-detection.
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
- "traefik.http.middlewares.middleware05.compress=true"
|
||||
- "traefik.http.middlewares.middleware05.compress.excludedcontenttypes=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware05.compress.minresponsebodybytes=42"
|
||||
- "traefik.http.middlewares.middleware06.contenttype.autodetect=true"
|
||||
- "traefik.http.middlewares.middleware06.contenttype=true"
|
||||
- "traefik.http.middlewares.middleware07.digestauth.headerfield=foobar"
|
||||
- "traefik.http.middlewares.middleware07.digestauth.realm=foobar"
|
||||
- "traefik.http.middlewares.middleware07.digestauth.removeheader=true"
|
||||
|
|
|
@ -137,7 +137,6 @@
|
|||
minResponseBodyBytes = 42
|
||||
[http.middlewares.Middleware06]
|
||||
[http.middlewares.Middleware06.contentType]
|
||||
autoDetect = true
|
||||
[http.middlewares.Middleware07]
|
||||
[http.middlewares.Middleware07.digestAuth]
|
||||
users = ["foobar", "foobar"]
|
||||
|
|
|
@ -141,8 +141,7 @@ http:
|
|||
- foobar
|
||||
minResponseBodyBytes: 42
|
||||
Middleware06:
|
||||
contentType:
|
||||
autoDetect: true
|
||||
contentType: {}
|
||||
Middleware07:
|
||||
digestAuth:
|
||||
users:
|
||||
|
|
|
@ -762,19 +762,9 @@ spec:
|
|||
type: object
|
||||
contentType:
|
||||
description: ContentType holds the content-type middleware configuration.
|
||||
This middleware exists to enable the correct behavior until at least
|
||||
the default one can be changed in a future version.
|
||||
properties:
|
||||
autoDetect:
|
||||
description: AutoDetect specifies whether to let the `Content-Type`
|
||||
header, if it has not been set by the backend, be automatically
|
||||
set to a value derived from the contents of the response. As
|
||||
a proxy, the default behavior should be to leave the header
|
||||
alone, regardless of what the backend did with it. However,
|
||||
the historic default was to always auto-detect and set the header
|
||||
if it was nil, and it is going to be kept that way in order
|
||||
to support users currently relying on it.
|
||||
type: boolean
|
||||
This middleware sets the `Content-Type` header value to the media
|
||||
type detected from the response content, when it is not set by the
|
||||
backend.
|
||||
type: object
|
||||
digestAuth:
|
||||
description: 'DigestAuth holds the digest auth middleware configuration.
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware05/compress/minResponseBodyBytes` | `42` |
|
||||
| `traefik/http/middlewares/Middleware06/contentType/autoDetect` | `true` |
|
||||
| `traefik/http/middlewares/Middleware06/contentType` | `` |
|
||||
| `traefik/http/middlewares/Middleware07/digestAuth/headerField` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware07/digestAuth/realm` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware07/digestAuth/removeHeader` | `true` |
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
"traefik.http.middlewares.middleware05.compress": "true",
|
||||
"traefik.http.middlewares.middleware05.compress.excludedcontenttypes": "foobar, foobar",
|
||||
"traefik.http.middlewares.middleware05.compress.minresponsebodybytes": "42",
|
||||
"traefik.http.middlewares.middleware06.contenttype.autodetect": "true",
|
||||
"traefik.http.middlewares.middleware06.contenttype": "true",
|
||||
"traefik.http.middlewares.middleware07.digestauth.headerfield": "foobar",
|
||||
"traefik.http.middlewares.middleware07.digestauth.realm": "foobar",
|
||||
"traefik.http.middlewares.middleware07.digestauth.removeheader": "true",
|
||||
|
|
|
@ -185,19 +185,9 @@ spec:
|
|||
type: object
|
||||
contentType:
|
||||
description: ContentType holds the content-type middleware configuration.
|
||||
This middleware exists to enable the correct behavior until at least
|
||||
the default one can be changed in a future version.
|
||||
properties:
|
||||
autoDetect:
|
||||
description: AutoDetect specifies whether to let the `Content-Type`
|
||||
header, if it has not been set by the backend, be automatically
|
||||
set to a value derived from the contents of the response. As
|
||||
a proxy, the default behavior should be to leave the header
|
||||
alone, regardless of what the backend did with it. However,
|
||||
the historic default was to always auto-detect and set the header
|
||||
if it was nil, and it is going to be kept that way in order
|
||||
to support users currently relying on it.
|
||||
type: boolean
|
||||
This middleware sets the `Content-Type` header value to the media
|
||||
type detected from the response content, when it is not set by the
|
||||
backend.
|
||||
type: object
|
||||
digestAuth:
|
||||
description: 'DigestAuth holds the digest auth middleware configuration.
|
||||
|
|
|
@ -762,19 +762,9 @@ spec:
|
|||
type: object
|
||||
contentType:
|
||||
description: ContentType holds the content-type middleware configuration.
|
||||
This middleware exists to enable the correct behavior until at least
|
||||
the default one can be changed in a future version.
|
||||
properties:
|
||||
autoDetect:
|
||||
description: AutoDetect specifies whether to let the `Content-Type`
|
||||
header, if it has not been set by the backend, be automatically
|
||||
set to a value derived from the contents of the response. As
|
||||
a proxy, the default behavior should be to leave the header
|
||||
alone, regardless of what the backend did with it. However,
|
||||
the historic default was to always auto-detect and set the header
|
||||
if it was nil, and it is going to be kept that way in order
|
||||
to support users currently relying on it.
|
||||
type: boolean
|
||||
This middleware sets the `Content-Type` header value to the media
|
||||
type detected from the response content, when it is not set by the
|
||||
backend.
|
||||
type: object
|
||||
digestAuth:
|
||||
description: 'DigestAuth holds the digest auth middleware configuration.
|
||||
|
|
|
@ -21,32 +21,12 @@
|
|||
[http.routers]
|
||||
[http.routers.router1]
|
||||
service = "service1"
|
||||
rule = "PathPrefix(`/css/ct/nomiddleware`) || PathPrefix(`/pdf/ct/nomiddleware`)"
|
||||
rule = "PathPrefix(`/`)"
|
||||
|
||||
[http.routers.router2]
|
||||
service = "service1"
|
||||
middlewares = ["autodetect"]
|
||||
rule = "PathPrefix(`/css/ct/middlewareauto`) || PathPrefix(`/pdf/ct/middlewareauto`)"
|
||||
|
||||
[http.routers.router3]
|
||||
service = "service1"
|
||||
middlewares = ["noautodetect"]
|
||||
rule = "PathPrefix(`/css/ct/middlewarenoauto`) || PathPrefix(`/pdf/ct/middlewarenoauto`)"
|
||||
|
||||
[http.routers.router4]
|
||||
service = "service1"
|
||||
rule = "PathPrefix(`/css/noct/nomiddleware`) || PathPrefix(`/pdf/noct/nomiddleware`)"
|
||||
|
||||
[http.routers.router5]
|
||||
service = "service1"
|
||||
middlewares = ["autodetect"]
|
||||
rule = "PathPrefix(`/css/noct/middlewareauto`) || PathPrefix(`/pdf/noct/middlewareauto`)"
|
||||
|
||||
[http.routers.router6]
|
||||
service = "service1"
|
||||
middlewares = ["noautodetect"]
|
||||
rule = "PathPrefix(`/css/noct/middlewarenoauto`) || PathPrefix(`/pdf/noct/middlewarenoauto`)"
|
||||
|
||||
rule = "PathPrefix(`/autodetect`)"
|
||||
|
||||
[http.services]
|
||||
[http.services.service1]
|
||||
|
@ -56,7 +36,3 @@
|
|||
url = "{{ .Server }}"
|
||||
|
||||
[http.middlewares.autodetect.contentType]
|
||||
autoDetect=true
|
||||
|
||||
[http.middlewares.noautodetect.contentType]
|
||||
autoDetect=false
|
||||
|
|
|
@ -1166,9 +1166,10 @@ func (s *SimpleSuite) TestSecureAPI(c *check.C) {
|
|||
func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
|
||||
srv1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header()["Content-Type"] = nil
|
||||
switch req.URL.Path[:4] {
|
||||
path := strings.TrimPrefix(req.URL.Path, "/autodetect")
|
||||
switch path[:4] {
|
||||
case "/css":
|
||||
if !strings.Contains(req.URL.Path, "noct") {
|
||||
if strings.Contains(req.URL.Path, "/ct") {
|
||||
rw.Header().Set("Content-Type", "text/css")
|
||||
}
|
||||
|
||||
|
@ -1177,7 +1178,7 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
|
|||
_, err := rw.Write([]byte(".testcss { }"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
case "/pdf":
|
||||
if !strings.Contains(req.URL.Path, "noct") {
|
||||
if strings.Contains(req.URL.Path, "/ct") {
|
||||
rw.Header().Set("Content-Type", "application/pdf")
|
||||
}
|
||||
|
||||
|
@ -1211,37 +1212,13 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
|
|||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/css/ct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
|
||||
err = try.GetRequest("http://127.0.0.1:8000/css/ct", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/pdf/ct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
|
||||
err = try.GetRequest("http://127.0.0.1:8000/pdf/ct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/css/ct/middlewareauto", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/pdf/ct/nomiddlewareauto", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/css/ct/middlewarenoauto", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/pdf/ct/nomiddlewarenoauto", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/css/noct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/pdf/noct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/css/noct/middlewareauto", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/pdf/noct/nomiddlewareauto", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/css/noct/middlewarenoauto", time.Second, func(res *http.Response) error {
|
||||
err = try.GetRequest("http://127.0.0.1:8000/css/noct", time.Second, func(res *http.Response) error {
|
||||
if ct, ok := res.Header["Content-Type"]; ok {
|
||||
return fmt.Errorf("should have no content type and %s is present", ct)
|
||||
}
|
||||
|
@ -1249,13 +1226,25 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
|
|||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/pdf/noct/middlewarenoauto", time.Second, func(res *http.Response) error {
|
||||
err = try.GetRequest("http://127.0.0.1:8000/pdf/noct", time.Second, func(res *http.Response) error {
|
||||
if ct, ok := res.Header["Content-Type"]; ok {
|
||||
return fmt.Errorf("should have no content type and %s is present", ct)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/autodetect/css/ct", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/autodetect/pdf/ct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/autodetect/css/noct", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8000/autodetect/pdf/noct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestMuxer(c *check.C) {
|
||||
|
|
|
@ -33,7 +33,7 @@ type Middleware struct {
|
|||
Compress *Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
PassTLSClientCert *PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty" export:"true"`
|
||||
Retry *Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty" export:"true"`
|
||||
ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty" export:"true"`
|
||||
ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
GrpcWeb *GrpcWeb `json:"grpcWeb,omitempty" toml:"grpcWeb,omitempty" yaml:"grpcWeb,omitempty" export:"true"`
|
||||
|
||||
Plugin map[string]PluginConf `json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty" export:"true"`
|
||||
|
@ -52,15 +52,9 @@ type GrpcWeb struct {
|
|||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// ContentType holds the content-type middleware configuration.
|
||||
// This middleware exists to enable the correct behavior until at least the default one can be changed in a future version.
|
||||
type ContentType struct {
|
||||
// AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend,
|
||||
// be automatically set to a value derived from the contents of the response.
|
||||
// As a proxy, the default behavior should be to leave the header alone, regardless of what the backend did with it.
|
||||
// However, the historic default was to always auto-detect and set the header if it was nil,
|
||||
// and it is going to be kept that way in order to support users currently relying on it.
|
||||
AutoDetect bool `json:"autoDetect,omitempty" toml:"autoDetect,omitempty" yaml:"autoDetect,omitempty" export:"true"`
|
||||
}
|
||||
// This middleware sets the `Content-Type` header value to the media type detected from the response content,
|
||||
// when it is not set by the backend.
|
||||
type ContentType struct{}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
|
|
46
pkg/middlewares/contenttype/content_type.go
Normal file
46
pkg/middlewares/contenttype/content_type.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package contenttype
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
)
|
||||
|
||||
const (
|
||||
typeName = "ContentType"
|
||||
)
|
||||
|
||||
// ContentType is a middleware used to activate Content-Type auto-detection.
|
||||
type contentType struct {
|
||||
next http.Handler
|
||||
name string
|
||||
}
|
||||
|
||||
// New creates a new handler.
|
||||
func New(ctx context.Context, next http.Handler, name string) (http.Handler, error) {
|
||||
middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware")
|
||||
return &contentType{next: next, name: name}, nil
|
||||
}
|
||||
|
||||
func (c *contentType) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
// Re-enable auto-detection.
|
||||
if ct, ok := rw.Header()["Content-Type"]; ok && ct == nil {
|
||||
middlewares.GetLogger(req.Context(), c.name, typeName).
|
||||
Debug().Msg("Enable Content-Type auto-detection.")
|
||||
delete(rw.Header(), "Content-Type")
|
||||
}
|
||||
|
||||
c.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func DisableAutoDetection(next http.Handler) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
// Prevent Content-Type auto-detection.
|
||||
if _, ok := rw.Header()["Content-Type"]; !ok {
|
||||
rw.Header()["Content-Type"] = nil
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
79
pkg/middlewares/contenttype/content_type_test.go
Normal file
79
pkg/middlewares/contenttype/content_type_test.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package contenttype
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
)
|
||||
|
||||
func TestAutoDetection(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
autoDetect bool
|
||||
contentType string
|
||||
wantContentType string
|
||||
}{
|
||||
{
|
||||
desc: "Keep the Content-Type returned by the server",
|
||||
autoDetect: false,
|
||||
contentType: "application/json",
|
||||
wantContentType: "application/json",
|
||||
},
|
||||
{
|
||||
desc: "Don't auto-detect Content-Type header by default when not set by the server",
|
||||
autoDetect: false,
|
||||
contentType: "",
|
||||
wantContentType: "",
|
||||
},
|
||||
{
|
||||
desc: "Keep the Content-Type returned by the server with auto-detection middleware",
|
||||
autoDetect: true,
|
||||
contentType: "application/json",
|
||||
wantContentType: "application/json",
|
||||
},
|
||||
{
|
||||
desc: "Auto-detect when Content-Type header is not already set by the server with auto-detection middleware",
|
||||
autoDetect: true,
|
||||
contentType: "",
|
||||
wantContentType: "text/plain; charset=utf-8",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var next http.Handler
|
||||
next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if test.contentType != "" {
|
||||
w.Header().Set("Content-Type", test.contentType)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("Test"))
|
||||
})
|
||||
|
||||
if test.autoDetect {
|
||||
var err error
|
||||
next, err = New(context.Background(), next, "foo-content-type")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
server := httptest.NewServer(
|
||||
DisableAutoDetection(next),
|
||||
)
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, server.URL, nil)
|
||||
res, err := server.Client().Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.wantContentType, res.Header.Get("Content-Type"))
|
||||
})
|
||||
}
|
||||
}
|
|
@ -337,9 +337,7 @@ func init() {
|
|||
Attempts: 42,
|
||||
InitialInterval: 42,
|
||||
},
|
||||
ContentType: &dynamic.ContentType{
|
||||
AutoDetect: true,
|
||||
},
|
||||
ContentType: &dynamic.ContentType{},
|
||||
Plugin: map[string]dynamic.PluginConf{
|
||||
"foo": {
|
||||
"answer": struct{ Answer int }{
|
||||
|
|
|
@ -302,9 +302,7 @@
|
|||
"attempts": 42,
|
||||
"initialInterval": "42ns"
|
||||
},
|
||||
"contentType": {
|
||||
"autoDetect": true
|
||||
},
|
||||
"contentType": {},
|
||||
"plugin": {
|
||||
"foo": {
|
||||
"answer": {}
|
||||
|
|
|
@ -305,9 +305,7 @@
|
|||
"attempts": 42,
|
||||
"initialInterval": "42ns"
|
||||
},
|
||||
"contentType": {
|
||||
"autoDetect": true
|
||||
},
|
||||
"contentType": {},
|
||||
"plugin": {
|
||||
"foo": {
|
||||
"answer": {}
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/traefik/traefik/v2/pkg/middlewares/chain"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/circuitbreaker"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/compress"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/contenttype"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/customerrors"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/grpcweb"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/headers"
|
||||
|
@ -181,12 +182,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) (
|
|||
return nil, badConf
|
||||
}
|
||||
middleware = func(next http.Handler) (http.Handler, error) {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if !config.ContentType.AutoDetect {
|
||||
rw.Header()["Content-Type"] = nil
|
||||
}
|
||||
next.ServeHTTP(rw, req)
|
||||
}), nil
|
||||
return contenttype.New(ctx, next, middlewareName)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/traefik/traefik/v2/pkg/ip"
|
||||
"github.com/traefik/traefik/v2/pkg/logs"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/contenttype"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/forwardedheaders"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
|
@ -537,6 +538,8 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||
|
||||
handler = http.AllowQuerySemicolons(handler)
|
||||
|
||||
handler = contenttype.DisableAutoDetection(handler)
|
||||
|
||||
if withH2c {
|
||||
handler = h2c.NewHandler(handler, &http2.Server{
|
||||
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
|
||||
|
|
Loading…
Reference in a new issue