2018-11-14 10:18:03 +01:00
package customerrors
2018-04-11 13:54:03 +02:00
import (
"bufio"
"bytes"
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"
"github.com/sirupsen/logrus"
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 (
_ middlewares . Stateful = & responseRecorderWithCloseNotify { }
_ middlewares . Stateful = & codeCatcherWithCloseNotify { }
)
2018-04-11 13:54:03 +02:00
2018-11-14 10:18:03 +01:00
const (
typeName = "customError"
backendURL = "http://0.0.0.0"
)
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 ( )
2018-11-14 10:18:03 +01:00
for _ , block := range c . httpCodeRanges {
2019-09-12 16:20:05 +02:00
if code >= block [ 0 ] && code <= block [ 1 ] {
logger . Errorf ( "Caught HTTP Status Code %d, returning error page" , code )
2018-04-11 13:54:03 +02:00
var query string
2018-11-14 10:18:03 +01:00
if len ( c . backendQuery ) > 0 {
query = "/" + strings . TrimPrefix ( c . backendQuery , "/" )
2020-09-15 13:08:03 +02:00
query = strings . ReplaceAll ( query , "{status}" , strconv . Itoa ( code ) )
2018-04-11 13:54:03 +02:00
}
2018-11-14 10:18:03 +01:00
pageReq , err := newRequest ( backendURL + query )
2018-04-23 11:28:04 +02:00
if err != nil {
2018-11-14 10:18:03 +01:00
logger . Error ( err )
2019-09-12 16:20:05 +02:00
rw . WriteHeader ( code )
_ , err = fmt . Fprint ( rw , http . StatusText ( code ) )
2018-11-14 10:18:03 +01:00
if err != nil {
http . Error ( rw , err . Error ( ) , http . StatusInternalServerError )
}
2018-04-23 11:28:04 +02:00
return
2018-04-11 13:54:03 +02:00
}
2018-04-23 11:28:04 +02:00
2019-09-13 19:28:04 +02:00
recorderErrorPage := newResponseRecorder ( ctx , rw )
2018-04-23 11:28:04 +02:00
utils . CopyHeaders ( pageReq . Header , req . Header )
2018-05-18 16:38:03 +02:00
2018-11-14 10:18:03 +01:00
c . backendHandler . ServeHTTP ( recorderErrorPage , pageReq . WithContext ( req . Context ( ) ) )
2018-05-18 16:38:03 +02:00
2018-11-14 10:18:03 +01:00
utils . CopyHeaders ( rw . Header ( ) , recorderErrorPage . Header ( ) )
2019-09-12 16:20:05 +02:00
rw . WriteHeader ( code )
2018-08-06 20:00:03 +02:00
2018-11-14 10:18:03 +01:00
if _ , err = rw . Write ( recorderErrorPage . GetBody ( ) . Bytes ( ) ) ; err != nil {
logger . Error ( err )
2018-08-06 20:00:03 +02:00
}
2018-04-11 13:54:03 +02:00
return
}
}
}
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
firstWrite bool
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 ,
firstWrite : true ,
}
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 ! cc . firstWrite {
if cc . caughtFilteredCode {
// 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.
return len ( buf ) , nil
}
return cc . responseWriter . Write ( buf )
}
cc . firstWrite = false
// 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 {
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
break
}
}
// it will be up to the other response recorder to send the headers,
// so it is out of our hands now.
if cc . caughtFilteredCode {
return
}
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 ( )
}
}
2018-04-11 13:54:03 +02:00
type responseRecorder interface {
http . ResponseWriter
http . Flusher
GetCode ( ) int
GetBody ( ) * bytes . Buffer
IsStreamingResponseStarted ( ) bool
}
// newResponseRecorder returns an initialized responseRecorder.
2019-09-13 19:28:04 +02:00
func newResponseRecorder ( ctx context . Context , rw http . ResponseWriter ) responseRecorder {
2018-04-11 13:54:03 +02:00
recorder := & responseRecorderWithoutCloseNotify {
HeaderMap : make ( http . Header ) ,
Body : new ( bytes . Buffer ) ,
Code : http . StatusOK ,
responseWriter : rw ,
2019-09-13 19:28:04 +02:00
logger : log . FromContext ( ctx ) ,
2018-04-11 13:54:03 +02:00
}
if _ , ok := rw . ( http . CloseNotifier ) ; ok {
return & responseRecorderWithCloseNotify { recorder }
}
return recorder
}
// responseRecorderWithoutCloseNotify is an implementation of http.ResponseWriter that
// records its mutations for later inspection.
type responseRecorderWithoutCloseNotify struct {
Code int // the HTTP response code from WriteHeader
HeaderMap http . Header // the HTTP response headers
Body * bytes . Buffer // if non-nil, the bytes.Buffer to append written data to
responseWriter http . ResponseWriter
err error
streamingResponseStarted bool
2018-11-14 10:18:03 +01:00
logger logrus . FieldLogger
2018-04-11 13:54:03 +02:00
}
type responseRecorderWithCloseNotify struct {
* responseRecorderWithoutCloseNotify
}
// CloseNotify returns a channel that receives at most a
// single value (true) when the client connection has gone away.
2018-05-28 15:00:04 +02:00
func ( r * responseRecorderWithCloseNotify ) CloseNotify ( ) <- chan bool {
return r . responseWriter . ( http . CloseNotifier ) . CloseNotify ( )
2018-04-11 13:54:03 +02:00
}
// Header returns the response headers.
2018-05-28 15:00:04 +02:00
func ( r * responseRecorderWithoutCloseNotify ) 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
return r . HeaderMap
2018-04-11 13:54:03 +02:00
}
2018-05-28 15:00:04 +02:00
func ( r * responseRecorderWithoutCloseNotify ) GetCode ( ) int {
return r . Code
2018-04-11 13:54:03 +02:00
}
2018-05-28 15:00:04 +02:00
func ( r * responseRecorderWithoutCloseNotify ) GetBody ( ) * bytes . Buffer {
return r . Body
2018-04-11 13:54:03 +02:00
}
2018-05-28 15:00:04 +02:00
func ( r * responseRecorderWithoutCloseNotify ) IsStreamingResponseStarted ( ) bool {
return r . streamingResponseStarted
2018-04-11 13:54:03 +02:00
}
// Write always succeeds and writes to rw.Body, if not nil.
2018-05-28 15:00:04 +02:00
func ( r * responseRecorderWithoutCloseNotify ) Write ( buf [ ] byte ) ( int , error ) {
if r . err != nil {
return 0 , r . err
2018-04-11 13:54:03 +02:00
}
2018-05-28 15:00:04 +02:00
return r . Body . Write ( buf )
2018-04-11 13:54:03 +02:00
}
// WriteHeader sets rw.Code.
2018-05-28 15:00:04 +02:00
func ( r * responseRecorderWithoutCloseNotify ) WriteHeader ( code int ) {
r . Code = code
2018-04-11 13:54:03 +02:00
}
2020-05-11 12:06:07 +02:00
// Hijack hijacks the connection.
2018-05-28 15:00:04 +02:00
func ( r * responseRecorderWithoutCloseNotify ) Hijack ( ) ( net . Conn , * bufio . ReadWriter , error ) {
return r . responseWriter . ( http . Hijacker ) . Hijack ( )
2018-04-11 13:54:03 +02:00
}
// Flush sends any buffered data to the client.
2018-05-28 15:00:04 +02:00
func ( r * responseRecorderWithoutCloseNotify ) Flush ( ) {
if ! r . streamingResponseStarted {
utils . CopyHeaders ( r . responseWriter . Header ( ) , r . Header ( ) )
r . responseWriter . WriteHeader ( r . Code )
r . streamingResponseStarted = true
2018-04-11 13:54:03 +02:00
}
2018-05-28 15:00:04 +02:00
_ , err := r . responseWriter . Write ( r . Body . Bytes ( ) )
2018-04-11 13:54:03 +02:00
if err != nil {
2018-11-14 10:18:03 +01:00
r . logger . Errorf ( "Error writing response in responseRecorder: %v" , err )
2018-05-28 15:00:04 +02:00
r . err = err
2018-04-11 13:54:03 +02:00
}
2018-05-28 15:00:04 +02:00
r . Body . Reset ( )
2018-04-11 13:54:03 +02:00
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 ( )
}
}