2016-06-15 19:07:33 +02:00
package middlewares
import (
"bufio"
2017-05-03 10:20:33 +02:00
"context"
2017-02-02 17:09:47 +01:00
"io/ioutil"
2016-06-15 19:07:33 +02:00
"net"
"net/http"
2016-12-30 09:21:13 +01:00
"github.com/containous/traefik/log"
2016-06-15 19:07:33 +02:00
)
2018-01-26 18:22:03 +01:00
// Compile time validation that the response writer implements http interfaces correctly.
var _ Stateful = & retryResponseWriterWithCloseNotify { }
2016-08-03 14:50:52 +02:00
2016-06-15 19:07:33 +02:00
// Retry is a middleware that retries requests
type Retry struct {
attempts int
next http . Handler
2017-04-18 08:22:06 +02:00
listener RetryListener
2016-06-15 19:07:33 +02:00
}
// NewRetry returns a new Retry instance
2017-04-18 08:22:06 +02:00
func NewRetry ( attempts int , next http . Handler , listener RetryListener ) * Retry {
2016-06-15 19:07:33 +02:00
return & Retry {
attempts : attempts ,
next : next ,
2017-04-18 08:22:06 +02:00
listener : listener ,
2016-06-15 19:07:33 +02:00
}
}
func ( retry * Retry ) ServeHTTP ( rw http . ResponseWriter , r * http . Request ) {
2017-02-02 17:09:47 +01:00
// if we might make multiple attempts, swap the body for an ioutil.NopCloser
// cf https://github.com/containous/traefik/issues/1008
if retry . attempts > 1 {
body := r . Body
defer body . Close ( )
r . Body = ioutil . NopCloser ( body )
}
2018-01-26 18:22:03 +01:00
2016-06-15 19:07:33 +02:00
attempts := 1
for {
2017-05-03 10:20:33 +02:00
netErrorOccurred := false
// We pass in a pointer to netErrorOccurred so that we can set it to true on network errors
// when proxying the HTTP requests to the backends. This happens in the custom RecordingErrorHandler.
newCtx := context . WithValue ( r . Context ( ) , defaultNetErrCtxKey , & netErrorOccurred )
2018-01-26 18:22:03 +01:00
retryResponseWriter := newRetryResponseWriter ( rw , attempts >= retry . attempts , & netErrorOccurred )
2017-05-03 10:20:33 +02:00
2018-01-26 18:22:03 +01:00
retry . next . ServeHTTP ( retryResponseWriter , r . WithContext ( newCtx ) )
if ! retryResponseWriter . ShouldRetry ( ) {
2017-09-20 18:40:03 +02:00
break
}
2016-06-15 19:07:33 +02:00
attempts ++
log . Debugf ( "New attempt %d for request: %v" , attempts , r . URL )
2017-08-28 12:50:02 +02:00
retry . listener . Retried ( r , attempts )
2016-06-15 19:07:33 +02:00
}
}
2017-05-03 10:20:33 +02:00
// netErrorCtxKey is a custom type that is used as key for the context.
type netErrorCtxKey string
// defaultNetErrCtxKey is the actual key which value is used to record network errors.
var defaultNetErrCtxKey netErrorCtxKey = "NetErrCtxKey"
// NetErrorRecorder is an interface to record net errors.
type NetErrorRecorder interface {
// Record can be used to signal the retry middleware that an network error happened
// and therefore the request should be retried.
Record ( ctx context . Context )
}
// DefaultNetErrorRecorder is the default NetErrorRecorder implementation.
type DefaultNetErrorRecorder struct { }
// Record is recording network errors by setting the context value for the defaultNetErrCtxKey to true.
func ( DefaultNetErrorRecorder ) Record ( ctx context . Context ) {
val := ctx . Value ( defaultNetErrCtxKey )
if netErrorOccurred , isBoolPointer := val . ( * bool ) ; isBoolPointer {
* netErrorOccurred = true
}
2016-06-15 19:07:33 +02:00
}
2017-04-18 08:22:06 +02:00
// RetryListener is used to inform about retry attempts.
type RetryListener interface {
// Retried will be called when a retry happens, with the request attempt passed to it.
// For the first retry this will be attempt 2.
2017-08-28 12:50:02 +02:00
Retried ( req * http . Request , attempt int )
}
// RetryListeners is a convenience type to construct a list of RetryListener and notify
// each of them about a retry attempt.
type RetryListeners [ ] RetryListener
// Retried exists to implement the RetryListener interface. It calls Retried on each of its slice entries.
func ( l RetryListeners ) Retried ( req * http . Request , attempt int ) {
for _ , retryListener := range l {
retryListener . Retried ( req , attempt )
}
2017-04-18 08:22:06 +02:00
}
2018-01-26 18:22:03 +01:00
type retryResponseWriter interface {
2018-01-04 11:18:03 +01:00
http . ResponseWriter
http . Flusher
2018-01-26 18:22:03 +01:00
ShouldRetry ( ) bool
}
func newRetryResponseWriter ( rw http . ResponseWriter , attemptsExhausted bool , netErrorOccured * bool ) retryResponseWriter {
responseWriter := & retryResponseWriterWithoutCloseNotify {
responseWriter : rw ,
attemptsExhausted : attemptsExhausted ,
netErrorOccured : netErrorOccured ,
2018-01-04 11:18:03 +01:00
}
if _ , ok := rw . ( http . CloseNotifier ) ; ok {
2018-01-26 18:22:03 +01:00
return & retryResponseWriterWithCloseNotify { responseWriter }
2018-01-04 11:18:03 +01:00
}
2018-01-26 18:22:03 +01:00
return responseWriter
2016-06-15 19:07:33 +02:00
}
2018-01-26 18:22:03 +01:00
type retryResponseWriterWithoutCloseNotify struct {
responseWriter http . ResponseWriter
attemptsExhausted bool
netErrorOccured * bool
2018-01-04 11:18:03 +01:00
}
2018-01-26 18:22:03 +01:00
func ( rr * retryResponseWriterWithoutCloseNotify ) ShouldRetry ( ) bool {
2018-02-19 01:04:45 +01:00
return * rr . netErrorOccured && ! rr . attemptsExhausted
2016-06-15 19:07:33 +02:00
}
2018-01-26 18:22:03 +01:00
func ( rr * retryResponseWriterWithoutCloseNotify ) Header ( ) http . Header {
if rr . ShouldRetry ( ) {
return make ( http . Header )
2016-06-15 19:07:33 +02:00
}
2018-01-26 18:22:03 +01:00
return rr . responseWriter . Header ( )
2016-06-15 19:07:33 +02:00
}
2018-01-26 18:22:03 +01:00
func ( rr * retryResponseWriterWithoutCloseNotify ) Write ( buf [ ] byte ) ( int , error ) {
if rr . ShouldRetry ( ) {
return 0 , nil
}
return rr . responseWriter . Write ( buf )
2018-01-04 11:18:03 +01:00
}
2018-01-26 18:22:03 +01:00
func ( rr * retryResponseWriterWithoutCloseNotify ) WriteHeader ( code int ) {
if rr . ShouldRetry ( ) {
return
}
rr . responseWriter . WriteHeader ( code )
2018-01-04 11:18:03 +01:00
}
2018-01-26 18:22:03 +01:00
func ( rr * retryResponseWriterWithoutCloseNotify ) Hijack ( ) ( net . Conn , * bufio . ReadWriter , error ) {
return rr . responseWriter . ( http . Hijacker ) . Hijack ( )
2018-01-04 11:18:03 +01:00
}
2018-01-26 18:22:03 +01:00
func ( rr * retryResponseWriterWithoutCloseNotify ) Flush ( ) {
if flusher , ok := rr . responseWriter . ( http . Flusher ) ; ok {
flusher . Flush ( )
2016-06-15 19:07:33 +02:00
}
}
2018-01-26 18:22:03 +01:00
type retryResponseWriterWithCloseNotify struct {
* retryResponseWriterWithoutCloseNotify
2016-06-15 19:07:33 +02:00
}
2016-08-03 14:50:52 +02:00
2018-01-26 18:22:03 +01:00
func ( rr * retryResponseWriterWithCloseNotify ) CloseNotify ( ) <- chan bool {
return rr . responseWriter . ( http . CloseNotifier ) . CloseNotify ( )
2016-08-03 14:50:52 +02:00
}