Support rewriting status codes in error page middleware

This commit is contained in:
Daniel Peinhopf 2025-03-03 11:54:04 +01:00 committed by GitHub
parent f0849e8ee6
commit fa76ed57d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 195 additions and 8 deletions

View file

@ -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"`
}

View file

@ -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
}

View file

@ -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()))
}

View file

@ -9,6 +9,8 @@ spec:
status:
- "404"
- "500"
statusRewrites:
404: 200
query: query
service:
name: whoami

View file

@ -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{

View file

@ -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",
},

View file

@ -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"`
}

View file

@ -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
}