2018-11-14 10:18:03 +01:00
package customerrors
2018-04-11 13:54:03 +02:00
import (
"bufio"
2018-11-14 10:18:03 +01:00
"context"
2018-04-23 11:28:04 +02:00
"fmt"
2018-04-11 13:54:03 +02:00
"net"
"net/http"
2018-04-23 11:28:04 +02:00
"net/url"
2018-04-11 13:54:03 +02:00
"strconv"
"strings"
2018-11-14 10:18:03 +01:00
"github.com/opentracing/opentracing-go/ext"
2020-09-16 15:46:04 +02:00
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/middlewares"
"github.com/traefik/traefik/v2/pkg/tracing"
"github.com/traefik/traefik/v2/pkg/types"
2018-04-11 13:54:03 +02:00
"github.com/vulcand/oxy/utils"
)
// Compile time validation that the response recorder implements http interfaces correctly.
2019-09-12 16:20:05 +02:00
var (
2022-04-20 16:42:09 +02:00
// TODO: maybe remove at least for codeModifierWithCloseNotify.
_ middlewares . Stateful = & codeModifierWithCloseNotify { }
2019-09-12 16:20:05 +02:00
_ middlewares . Stateful = & codeCatcherWithCloseNotify { }
)
2018-04-11 13:54:03 +02:00
2021-09-27 17:40:13 +02:00
const typeName = "customError"
2018-11-14 10:18:03 +01:00
type serviceBuilder interface {
2020-09-01 18:16:04 +02:00
BuildHTTP ( ctx context . Context , serviceName string ) ( http . Handler , error )
2018-11-14 10:18:03 +01:00
}
// customErrors is a middleware that provides the custom error pages..
type customErrors struct {
name string
next http . Handler
2018-04-11 13:54:03 +02:00
backendHandler http . Handler
httpCodeRanges types . HTTPCodeRanges
backendQuery string
}
2018-11-14 10:18:03 +01:00
// New creates a new custom error pages middleware.
2019-07-10 09:26:04 +02:00
func New ( ctx context . Context , next http . Handler , config dynamic . ErrorPage , serviceBuilder serviceBuilder , name string ) ( http . Handler , error ) {
2019-09-13 19:28:04 +02:00
log . FromContext ( middlewares . GetLoggerCtx ( ctx , name , typeName ) ) . Debug ( "Creating middleware" )
2018-11-14 10:18:03 +01:00
httpCodeRanges , err := types . NewHTTPCodeRanges ( config . Status )
if err != nil {
return nil , err
2018-04-11 13:54:03 +02:00
}
2020-09-01 18:16:04 +02:00
backend , err := serviceBuilder . BuildHTTP ( ctx , config . Service )
2018-04-11 13:54:03 +02:00
if err != nil {
return nil , err
}
2018-11-14 10:18:03 +01:00
return & customErrors {
name : name ,
next : next ,
backendHandler : backend ,
2018-04-11 13:54:03 +02:00
httpCodeRanges : httpCodeRanges ,
2018-11-14 10:18:03 +01:00
backendQuery : config . Query ,
2018-04-11 13:54:03 +02:00
} , nil
}
2018-11-14 10:18:03 +01:00
func ( c * customErrors ) GetTracingInformation ( ) ( string , ext . SpanKindEnum ) {
return c . name , tracing . SpanKindNoneEnum
2018-04-11 13:54:03 +02:00
}
2018-11-14 10:18:03 +01:00
func ( c * customErrors ) ServeHTTP ( rw http . ResponseWriter , req * http . Request ) {
2019-09-13 19:28:04 +02:00
ctx := middlewares . GetLoggerCtx ( req . Context ( ) , c . name , typeName )
logger := log . FromContext ( ctx )
2018-11-14 10:18:03 +01:00
if c . backendHandler == nil {
logger . Error ( "Error pages: no backend handler." )
tracing . SetErrorWithEvent ( req , "Error pages: no backend handler." )
c . next . ServeHTTP ( rw , req )
2018-04-11 13:54:03 +02:00
return
}
2019-09-12 16:20:05 +02:00
catcher := newCodeCatcher ( rw , c . httpCodeRanges )
c . next . ServeHTTP ( catcher , req )
if ! catcher . isFilteredCode ( ) {
return
}
2018-04-11 13:54:03 +02:00
// check the recorder code against the configured http status code ranges
2019-09-12 16:20:05 +02:00
code := catcher . getCode ( )
2022-04-20 16:42:09 +02:00
logger . Debugf ( "Caught HTTP Status Code %d, returning error page" , code )
2018-05-18 16:38:03 +02:00
2022-04-20 16:42:09 +02:00
var query string
if len ( c . backendQuery ) > 0 {
query = "/" + strings . TrimPrefix ( c . backendQuery , "/" )
query = strings . ReplaceAll ( query , "{status}" , strconv . Itoa ( code ) )
}
2021-03-04 09:02:03 +01:00
2022-04-20 16:42:09 +02:00
pageReq , err := newRequest ( "http://" + req . Host + query )
if err != nil {
logger . Error ( err )
http . Error ( rw , http . StatusText ( code ) , code )
2021-03-04 09:02:03 +01:00
return
2018-04-11 13:54:03 +02:00
}
2022-04-20 16:42:09 +02:00
utils . CopyHeaders ( pageReq . Header , req . Header )
c . backendHandler . ServeHTTP ( newCodeModifier ( rw , code ) ,
pageReq . WithContext ( req . Context ( ) ) )
2018-04-11 13:54:03 +02:00
}
2018-04-23 11:28:04 +02:00
func newRequest ( baseURL string ) ( * http . Request , error ) {
u , err := url . Parse ( baseURL )
if err != nil {
2020-05-11 12:06:07 +02:00
return nil , fmt . Errorf ( "error pages: error when parse URL: %w" , err )
2018-04-23 11:28:04 +02:00
}
2018-11-14 10:18:03 +01:00
req , err := http . NewRequest ( http . MethodGet , u . String ( ) , nil )
2018-04-23 11:28:04 +02:00
if err != nil {
2020-05-11 12:06:07 +02:00
return nil , fmt . Errorf ( "error pages: error when create query: %w" , err )
2018-04-23 11:28:04 +02:00
}
req . RequestURI = u . RequestURI ( )
return req , nil
}
2019-09-12 16:20:05 +02:00
type responseInterceptor interface {
http . ResponseWriter
http . Flusher
getCode ( ) int
isFilteredCode ( ) bool
}
// codeCatcher is a response writer that detects as soon as possible whether the
// response is a code within the ranges of codes it watches for. If it is, it
// simply drops the data from the response. Otherwise, it forwards it directly to
// the original client (its responseWriter) without any buffering.
type codeCatcher struct {
headerMap http . Header
code int
httpCodeRanges types . HTTPCodeRanges
caughtFilteredCode bool
responseWriter http . ResponseWriter
headersSent bool
}
type codeCatcherWithCloseNotify struct {
* codeCatcher
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone away.
func ( cc * codeCatcherWithCloseNotify ) CloseNotify ( ) <- chan bool {
return cc . responseWriter . ( http . CloseNotifier ) . CloseNotify ( )
}
func newCodeCatcher ( rw http . ResponseWriter , httpCodeRanges types . HTTPCodeRanges ) responseInterceptor {
catcher := & codeCatcher {
headerMap : make ( http . Header ) ,
code : http . StatusOK , // If backend does not call WriteHeader on us, we consider it's a 200.
responseWriter : rw ,
httpCodeRanges : httpCodeRanges ,
}
if _ , ok := rw . ( http . CloseNotifier ) ; ok {
return & codeCatcherWithCloseNotify { catcher }
}
return catcher
}
func ( cc * codeCatcher ) Header ( ) http . Header {
if cc . headerMap == nil {
cc . headerMap = make ( http . Header )
}
return cc . headerMap
}
func ( cc * codeCatcher ) getCode ( ) int {
return cc . code
}
// isFilteredCode returns whether the codeCatcher received a response code among the ones it is watching,
// and for which the response should be deferred to the error handler.
func ( cc * codeCatcher ) isFilteredCode ( ) bool {
return cc . caughtFilteredCode
}
func ( cc * codeCatcher ) Write ( buf [ ] byte ) ( int , error ) {
// If WriteHeader was already called from the caller, this is a NOOP.
// Otherwise, cc.code is actually a 200 here.
cc . WriteHeader ( cc . code )
if cc . caughtFilteredCode {
2022-04-21 10:42:08 +02:00
// We don't care about the contents of the response,
// since we want to serve the ones from the error page,
// so we just drop them.
2019-09-12 16:20:05 +02:00
return len ( buf ) , nil
}
return cc . responseWriter . Write ( buf )
}
func ( cc * codeCatcher ) WriteHeader ( code int ) {
if cc . headersSent || cc . caughtFilteredCode {
return
}
cc . code = code
for _ , block := range cc . httpCodeRanges {
if cc . code >= block [ 0 ] && cc . code <= block [ 1 ] {
cc . caughtFilteredCode = true
2022-04-21 10:42:08 +02:00
// it will be up to the caller to send the headers,
// so it is out of our hands now.
return
2019-09-12 16:20:05 +02:00
}
}
2022-04-21 10:42:08 +02:00
2019-09-12 16:20:05 +02:00
utils . CopyHeaders ( cc . responseWriter . Header ( ) , cc . Header ( ) )
cc . responseWriter . WriteHeader ( cc . code )
cc . headersSent = true
}
2020-05-11 12:06:07 +02:00
// Hijack hijacks the connection.
2019-09-12 16:20:05 +02:00
func ( cc * codeCatcher ) Hijack ( ) ( net . Conn , * bufio . ReadWriter , error ) {
if hj , ok := cc . responseWriter . ( http . Hijacker ) ; ok {
return hj . Hijack ( )
}
return nil , nil , fmt . Errorf ( "%T is not a http.Hijacker" , cc . responseWriter )
}
// Flush sends any buffered data to the client.
func ( cc * codeCatcher ) Flush ( ) {
// If WriteHeader was already called from the caller, this is a NOOP.
// Otherwise, cc.code is actually a 200 here.
cc . WriteHeader ( cc . code )
if flusher , ok := cc . responseWriter . ( http . Flusher ) ; ok {
flusher . Flush ( )
}
}
2022-04-20 16:42:09 +02:00
// codeModifier forwards a response back to the client,
// while enforcing a given response code.
type codeModifier interface {
2018-04-11 13:54:03 +02:00
http . ResponseWriter
}
2022-04-20 16:42:09 +02:00
// newCodeModifier returns a codeModifier that enforces the given code.
func newCodeModifier ( rw http . ResponseWriter , code int ) codeModifier {
codeMod := & codeModifierWithoutCloseNotify {
headerMap : make ( http . Header ) ,
code : code ,
2018-04-11 13:54:03 +02:00
responseWriter : rw ,
}
if _ , ok := rw . ( http . CloseNotifier ) ; ok {
2022-04-20 16:42:09 +02:00
return & codeModifierWithCloseNotify { codeMod }
2018-04-11 13:54:03 +02:00
}
2022-04-20 16:42:09 +02:00
return codeMod
2018-04-11 13:54:03 +02:00
}
2022-04-20 16:42:09 +02:00
type codeModifierWithoutCloseNotify struct {
code int // the code enforced in the response.
// headerSent is whether the headers have already been sent,
// either through Write or WriteHeader.
headerSent bool
headerMap http . Header // the HTTP response headers from the backend.
responseWriter http . ResponseWriter
2018-04-11 13:54:03 +02:00
}
2022-04-20 16:42:09 +02:00
type codeModifierWithCloseNotify struct {
* codeModifierWithoutCloseNotify
2018-04-11 13:54:03 +02:00
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone away.
2022-04-20 16:42:09 +02:00
func ( r * codeModifierWithCloseNotify ) CloseNotify ( ) <- chan bool {
2018-05-28 15:00:04 +02:00
return r . responseWriter . ( http . CloseNotifier ) . CloseNotify ( )
2018-04-11 13:54:03 +02:00
}
// Header returns the response headers.
2022-04-20 16:42:09 +02:00
func ( r * codeModifierWithoutCloseNotify ) Header ( ) http . Header {
if r . headerMap == nil {
r . headerMap = make ( http . Header )
2018-04-11 13:54:03 +02:00
}
2018-05-28 15:00:04 +02:00
2022-04-20 16:42:09 +02:00
return r . headerMap
2018-04-11 13:54:03 +02:00
}
2022-04-20 16:42:09 +02:00
// Write calls WriteHeader to send the enforced code,
// then writes the data directly to r.responseWriter.
func ( r * codeModifierWithoutCloseNotify ) Write ( buf [ ] byte ) ( int , error ) {
r . WriteHeader ( r . code )
return r . responseWriter . Write ( buf )
2018-04-11 13:54:03 +02:00
}
2022-04-20 16:42:09 +02:00
// WriteHeader sends the headers, with the enforced code (the code in argument
// is always ignored), if it hasn't already been done.
func ( r * codeModifierWithoutCloseNotify ) WriteHeader ( _ int ) {
if r . headerSent {
return
2018-04-11 13:54:03 +02:00
}
2022-04-20 16:42:09 +02:00
utils . CopyHeaders ( r . responseWriter . Header ( ) , r . Header ( ) )
r . responseWriter . WriteHeader ( r . code )
r . headerSent = true
2018-04-11 13:54:03 +02:00
}
2020-05-11 12:06:07 +02:00
// Hijack hijacks the connection.
2022-04-20 16:42:09 +02:00
func ( r * codeModifierWithoutCloseNotify ) Hijack ( ) ( net . Conn , * bufio . ReadWriter , error ) {
hijacker , ok := r . responseWriter . ( http . Hijacker )
if ! ok {
return nil , nil , fmt . Errorf ( "%T is not a http.Hijacker" , r . responseWriter )
}
return hijacker . Hijack ( )
2018-04-11 13:54:03 +02:00
}
// Flush sends any buffered data to the client.
2022-04-20 16:42:09 +02:00
func ( r * codeModifierWithoutCloseNotify ) Flush ( ) {
2022-04-21 10:42:08 +02:00
r . WriteHeader ( r . code )
2018-05-28 15:00:04 +02:00
if flusher , ok := r . responseWriter . ( http . Flusher ) ; ok {
2018-04-11 13:54:03 +02:00
flusher . Flush ( )
}
}