mirror of
https://github.com/traefik/traefik.git
synced 2025-05-05 15:33:01 +00:00
Support rewriting status codes in error page middleware
This commit is contained in:
parent
f0849e8ee6
commit
fa76ed57d3
@ -102,6 +102,19 @@ The status code ranges are inclusive (`505-599` will trigger with every code bet
|
||||
The comma-separated syntax is only available for label-based providers.
|
||||
The examples above demonstrate which syntax is appropriate for each provider.
|
||||
|
||||
### `statusRewrites`
|
||||
|
||||
An optional mapping of status codes to be rewritten. For example, if a service returns a 418, you might want to rewrite it to a 404.
|
||||
You can map individual status codes or even ranges to a different status code. The syntax for ranges follows the same rules as the `status` option.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```yml
|
||||
statusRewrites:
|
||||
"500-503": 500
|
||||
"418": 404
|
||||
```
|
||||
|
||||
### `service`
|
||||
|
||||
The service that will serve the new requested error page.
|
||||
@ -123,7 +136,8 @@ There are multiple variables that can be placed in the `query` option to insert
|
||||
|
||||
The table below lists all the available variables and their associated values.
|
||||
|
||||
| Variable | Value |
|
||||
|------------|--------------------------------------------------------------------|
|
||||
| `{status}` | The response status code. |
|
||||
| `{url}` | The [escaped](https://pkg.go.dev/net/url#QueryEscape) request URL. |
|
||||
| Variable | Value |
|
||||
|--------------------|--------------------------------------------------------------------------------------------|
|
||||
| `{status}` | The response status code. It may be rewritten when using the `statusRewrites` option. |
|
||||
| `{originalStatus}` | The original response status code, if it has been modified by the `statusRewrites` option. |
|
||||
| `{url}` | The [escaped](https://pkg.go.dev/net/url#QueryEscape) request URL. |
|
||||
|
@ -33,6 +33,8 @@
|
||||
- "traefik.http.middlewares.middleware09.errors.query=foobar"
|
||||
- "traefik.http.middlewares.middleware09.errors.service=foobar"
|
||||
- "traefik.http.middlewares.middleware09.errors.status=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware09.errors.statusrewrites.name0=42"
|
||||
- "traefik.http.middlewares.middleware09.errors.statusrewrites.name1=42"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.addauthcookiestoresponse=foobar, foobar"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.address=foobar"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.authrequestheaders=foobar, foobar"
|
||||
|
@ -173,6 +173,9 @@
|
||||
status = ["foobar", "foobar"]
|
||||
service = "foobar"
|
||||
query = "foobar"
|
||||
[http.middlewares.Middleware09.errors.statusRewrites]
|
||||
name0 = 42
|
||||
name1 = 42
|
||||
[http.middlewares.Middleware10]
|
||||
[http.middlewares.Middleware10.forwardAuth]
|
||||
address = "foobar"
|
||||
|
@ -186,6 +186,9 @@ http:
|
||||
status:
|
||||
- foobar
|
||||
- foobar
|
||||
statusRewrites:
|
||||
name0: 42
|
||||
name1: 42
|
||||
service: foobar
|
||||
query: foobar
|
||||
Middleware10:
|
||||
|
@ -1003,6 +1003,8 @@ spec:
|
||||
description: |-
|
||||
Query defines the URL for the error page (hosted by service).
|
||||
The {status} variable can be used in order to insert the status code in the URL.
|
||||
The {originalStatus} variable can be used in order to insert the upstream status code in the URL.
|
||||
The {url} variable can be used in order to insert the escaped request URL.
|
||||
type: string
|
||||
service:
|
||||
description: |-
|
||||
@ -1199,6 +1201,13 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
statusRewrites:
|
||||
additionalProperties:
|
||||
type: integer
|
||||
description: |-
|
||||
StatusRewrites defines a mapping of status codes that should be returned instead of the original error status codes.
|
||||
For example: "418": 404 or "410-418": 404
|
||||
type: object
|
||||
type: object
|
||||
forwardAuth:
|
||||
description: |-
|
||||
|
@ -40,6 +40,8 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
||||
| `traefik/http/middlewares/Middleware09/errors/service` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware09/errors/status/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware09/errors/status/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware09/errors/statusRewrites/name0` | `42` |
|
||||
| `traefik/http/middlewares/Middleware09/errors/statusRewrites/name1` | `42` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/addAuthCookiesToResponse/0` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/addAuthCookiesToResponse/1` | `foobar` |
|
||||
| `traefik/http/middlewares/Middleware10/forwardAuth/address` | `foobar` |
|
||||
|
@ -261,6 +261,8 @@ spec:
|
||||
description: |-
|
||||
Query defines the URL for the error page (hosted by service).
|
||||
The {status} variable can be used in order to insert the status code in the URL.
|
||||
The {originalStatus} variable can be used in order to insert the upstream status code in the URL.
|
||||
The {url} variable can be used in order to insert the escaped request URL.
|
||||
type: string
|
||||
service:
|
||||
description: |-
|
||||
@ -457,6 +459,13 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
statusRewrites:
|
||||
additionalProperties:
|
||||
type: integer
|
||||
description: |-
|
||||
StatusRewrites defines a mapping of status codes that should be returned instead of the original error status codes.
|
||||
For example: "418": 404 or "410-418": 404
|
||||
type: object
|
||||
type: object
|
||||
forwardAuth:
|
||||
description: |-
|
||||
|
@ -69,6 +69,30 @@ func (s *ErrorPagesSuite) TestErrorPage() {
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *ErrorPagesSuite) TestStatusRewrites() {
|
||||
// The `statusRewrites.toml` file contains a misconfigured backend host and some status code rewrites.
|
||||
file := s.adaptFile("fixtures/error_pages/statusRewrites.toml", struct {
|
||||
Server1 string
|
||||
Server2 string
|
||||
}{s.BackendIP, s.ErrorPageIP})
|
||||
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil)
|
||||
require.NoError(s.T(), err)
|
||||
frontendReq.Host = "test502.local"
|
||||
|
||||
err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred."), try.StatusCodeIs(404))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
frontendReq, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil)
|
||||
require.NoError(s.T(), err)
|
||||
frontendReq.Host = "test418.local"
|
||||
|
||||
err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred."), try.StatusCodeIs(400))
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *ErrorPagesSuite) TestErrorPageFlush() {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Add("Transfer-Encoding", "chunked")
|
||||
|
45
integration/fixtures/error_pages/statusRewrites.toml
Normal file
45
integration/fixtures/error_pages/statusRewrites.toml
Normal file
@ -0,0 +1,45 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
noColor = true
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8080"
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.router1]
|
||||
rule = "Host(`test502.local`)"
|
||||
service = "service1"
|
||||
middlewares = ["error"]
|
||||
[http.routers.router2]
|
||||
rule = "Host(`test418.local`)"
|
||||
service = "noop@internal"
|
||||
middlewares = ["error"]
|
||||
|
||||
[http.middlewares]
|
||||
[http.middlewares.error.errors]
|
||||
status = ["500-502", "503-599", "418"]
|
||||
service = "error"
|
||||
query = "/50x.html"
|
||||
[http.middlewares.error.errors.statusRewrites]
|
||||
"418" = 400
|
||||
"500-502" = 404
|
||||
|
||||
[http.services]
|
||||
[http.services.service1.loadBalancer]
|
||||
passHostHeader = true
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "http://{{.Server1}}:8989474"
|
||||
|
||||
[http.services.error.loadBalancer]
|
||||
[[http.services.error.loadBalancer.servers]]
|
||||
url = "http://{{.Server2}}:80"
|
@ -1003,6 +1003,8 @@ spec:
|
||||
description: |-
|
||||
Query defines the URL for the error page (hosted by service).
|
||||
The {status} variable can be used in order to insert the status code in the URL.
|
||||
The {originalStatus} variable can be used in order to insert the upstream status code in the URL.
|
||||
The {url} variable can be used in order to insert the escaped request URL.
|
||||
type: string
|
||||
service:
|
||||
description: |-
|
||||
@ -1199,6 +1201,13 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
statusRewrites:
|
||||
additionalProperties:
|
||||
type: integer
|
||||
description: |-
|
||||
StatusRewrites defines a mapping of status codes that should be returned instead of the original error status codes.
|
||||
For example: "418": 404 or "410-418": 404
|
||||
type: object
|
||||
type: object
|
||||
forwardAuth:
|
||||
description: |-
|
||||
|
@ -222,10 +222,15 @@ type ErrorPage struct {
|
||||
// as ranges by separating two codes with a dash (500-599),
|
||||
// or a combination of the two (404,418,500-599).
|
||||
Status []string `json:"status,omitempty" toml:"status,omitempty" yaml:"status,omitempty" export:"true"`
|
||||
// StatusRewrites defines a mapping of status codes that should be returned instead of the original error status codes.
|
||||
// For example: "418": 404 or "410-418": 404
|
||||
StatusRewrites map[string]int `json:"statusRewrites,omitempty" toml:"statusRewrites,omitempty" yaml:"statusRewrites,omitempty" export:"true"`
|
||||
// Service defines the name of the service that will serve the error page.
|
||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||
// Query defines the URL for the error page (hosted by service).
|
||||
// The {status} variable can be used in order to insert the status code in the URL.
|
||||
// The {originalStatus} variable can be used in order to insert the upstream status code in the URL.
|
||||
// The {url} variable can be used in order to insert the escaped request URL.
|
||||
Query string `json:"query,omitempty" toml:"query,omitempty" yaml:"query,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
|
@ -313,6 +313,13 @@ func (in *ErrorPage) DeepCopyInto(out *ErrorPage) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.StatusRewrites != nil {
|
||||
in, out := &in.StatusRewrites, &out.StatusRewrites
|
||||
*out = make(map[string]int, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,12 @@ type customErrors struct {
|
||||
backendHandler http.Handler
|
||||
httpCodeRanges types.HTTPCodeRanges
|
||||
backendQuery string
|
||||
statusRewrites []statusRewrite
|
||||
}
|
||||
|
||||
type statusRewrite struct {
|
||||
fromCodes types.HTTPCodeRanges
|
||||
toCode int
|
||||
}
|
||||
|
||||
// New creates a new custom error pages middleware.
|
||||
@ -53,12 +59,27 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse StatusRewrites
|
||||
statusRewrites := make([]statusRewrite, 0, len(config.StatusRewrites))
|
||||
for k, v := range config.StatusRewrites {
|
||||
ranges, err := types.NewHTTPCodeRanges([]string{k})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statusRewrites = append(statusRewrites, statusRewrite{
|
||||
fromCodes: ranges,
|
||||
toCode: v,
|
||||
})
|
||||
}
|
||||
|
||||
return &customErrors{
|
||||
name: name,
|
||||
next: next,
|
||||
backendHandler: backend,
|
||||
httpCodeRanges: httpCodeRanges,
|
||||
backendQuery: config.Query,
|
||||
statusRewrites: statusRewrites,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -84,12 +105,28 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
// check the recorder code against the configured http status code ranges
|
||||
code := catcher.getCode()
|
||||
logger.Debug().Msgf("Caught HTTP Status Code %d, returning error page", code)
|
||||
|
||||
originalCode := code
|
||||
|
||||
// Check if we need to rewrite the status code
|
||||
for _, rsc := range c.statusRewrites {
|
||||
if rsc.fromCodes.Contains(code) {
|
||||
code = rsc.toCode
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if code != originalCode {
|
||||
logger.Debug().Msgf("Caught HTTP Status Code %d (rewritten to %d), returning error page", originalCode, code)
|
||||
} else {
|
||||
logger.Debug().Msgf("Caught HTTP Status Code %d, returning error page", code)
|
||||
}
|
||||
|
||||
var query string
|
||||
if len(c.backendQuery) > 0 {
|
||||
query = "/" + strings.TrimPrefix(c.backendQuery, "/")
|
||||
query = strings.ReplaceAll(query, "{status}", strconv.Itoa(code))
|
||||
query = strings.ReplaceAll(query, "{originalStatus}", strconv.Itoa(originalCode))
|
||||
query = strings.ReplaceAll(query, "{url}", url.QueryEscape(req.URL.String()))
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,8 @@ spec:
|
||||
status:
|
||||
- "404"
|
||||
- "500"
|
||||
statusRewrites:
|
||||
404: 200
|
||||
query: query
|
||||
service:
|
||||
name: whoami
|
||||
|
@ -737,8 +737,9 @@ func (p *Provider) createErrorPageMiddleware(client Client, namespace string, er
|
||||
}
|
||||
|
||||
errorPageMiddleware := &dynamic.ErrorPage{
|
||||
Status: errorPage.Status,
|
||||
Query: errorPage.Query,
|
||||
Status: errorPage.Status,
|
||||
StatusRewrites: errorPage.StatusRewrites,
|
||||
Query: errorPage.Query,
|
||||
}
|
||||
|
||||
cb := configBuilder{
|
||||
|
@ -4160,7 +4160,10 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-errorpage": {
|
||||
Errors: &dynamic.ErrorPage{
|
||||
Status: []string{"404", "500"},
|
||||
Status: []string{"404", "500"},
|
||||
StatusRewrites: map[string]int{
|
||||
"404": 200,
|
||||
},
|
||||
Service: "default-errorpage-errorpage-service",
|
||||
Query: "query",
|
||||
},
|
||||
|
@ -68,11 +68,16 @@ type ErrorPage struct {
|
||||
// as ranges by separating two codes with a dash (500-599),
|
||||
// or a combination of the two (404,418,500-599).
|
||||
Status []string `json:"status,omitempty"`
|
||||
// StatusRewrites defines a mapping of status codes that should be returned instead of the original error status codes.
|
||||
// For example: "418": 404 or "410-418": 404
|
||||
StatusRewrites map[string]int `json:"statusRewrites,omitempty"`
|
||||
// Service defines the reference to a Kubernetes Service that will serve the error page.
|
||||
// More info: https://doc.traefik.io/traefik/v3.3/middlewares/http/errorpages/#service
|
||||
Service Service `json:"service,omitempty"`
|
||||
// Query defines the URL for the error page (hosted by service).
|
||||
// The {status} variable can be used in order to insert the status code in the URL.
|
||||
// The {originalStatus} variable can be used in order to insert the upstream status code in the URL.
|
||||
// The {url} variable can be used in order to insert the escaped request URL.
|
||||
Query string `json:"query,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -229,6 +229,13 @@ func (in *ErrorPage) DeepCopyInto(out *ErrorPage) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.StatusRewrites != nil {
|
||||
in, out := &in.StatusRewrites, &out.StatusRewrites
|
||||
*out = make(map[string]int, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
in.Service.DeepCopyInto(&out.Service)
|
||||
return
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user