@ -43,7 +43,7 @@ func init() {
// Handler implements a highly configurable and production-ready reverse proxy.
// Handler implements a highly configurable and production-ready reverse proxy.
//
//
// Upon proxying, this module sets the following placeholders (which can be used
// Upon proxying, this module sets the following placeholders (which can be used
// both within and after this handler):
// both within and after this handler; for example, in response headers ):
//
//
// Placeholder | Description
// Placeholder | Description
// ------------|-------------
// ------------|-------------
@ -54,6 +54,9 @@ func init() {
// `{http.reverse_proxy.upstream.requests}` | The approximate current number of requests to the upstream
// `{http.reverse_proxy.upstream.requests}` | The approximate current number of requests to the upstream
// `{http.reverse_proxy.upstream.max_requests}` | The maximum approximate number of requests allowed to the upstream
// `{http.reverse_proxy.upstream.max_requests}` | The maximum approximate number of requests allowed to the upstream
// `{http.reverse_proxy.upstream.fails}` | The number of recent failed requests to the upstream
// `{http.reverse_proxy.upstream.fails}` | The number of recent failed requests to the upstream
// `{http.reverse_proxy.upstream.latency}` | How long it took the proxy upstream to write the response header.
// `{http.reverse_proxy.upstream.duration}` | Time spent proxying to the upstream, including writing response body to client.
// `{http.reverse_proxy.duration}` | Total time spent proxying, including selecting an upstream, retries, and writing response.
type Handler struct {
type Handler struct {
// Configures the method of transport for the proxy. A transport
// Configures the method of transport for the proxy. A transport
// is what performs the actual "round trip" to the backend.
// is what performs the actual "round trip" to the backend.
@ -370,6 +373,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
} ( )
} ( )
start := time . Now ( )
start := time . Now ( )
defer func ( ) {
// total proxying duration, including time spent on LB and retries
repl . Set ( "http.reverse_proxy.duration" , time . Since ( start ) )
} ( )
var proxyErr error
var proxyErr error
for {
for {
@ -419,7 +426,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
}
}
// proxy the request to that upstream
// proxy the request to that upstream
proxyErr = h . reverseProxy ( w , r , dialInfo , next )
proxyErr = h . reverseProxy ( w , r , repl , dialInfo , next )
if proxyErr == nil || proxyErr == context . Canceled {
if proxyErr == nil || proxyErr == context . Canceled {
// context.Canceled happens when the downstream client
// context.Canceled happens when the downstream client
// cancels the request, which is not our failure
// cancels the request, which is not our failure
@ -522,7 +529,7 @@ func (h Handler) prepareRequest(req *http.Request) error {
// reverseProxy performs a round-trip to the given backend and processes the response with the client.
// reverseProxy performs a round-trip to the given backend and processes the response with the client.
// (This method is mostly the beginning of what was borrowed from the net/http/httputil package in the
// (This method is mostly the beginning of what was borrowed from the net/http/httputil package in the
// Go standard library which was used as the foundation.)
// Go standard library which was used as the foundation.)
func ( h * Handler ) reverseProxy ( rw http . ResponseWriter , req * http . Request , di DialInfo , next caddyhttp . Handler ) error {
func ( h * Handler ) reverseProxy ( rw http . ResponseWriter , req * http . Request , repl * caddy . Replacer , di DialInfo , next caddyhttp . Handler ) error {
_ = di . Upstream . Host . CountRequest ( 1 )
_ = di . Upstream . Host . CountRequest ( 1 )
//nolint:errcheck
//nolint:errcheck
defer di . Upstream . Host . CountRequest ( - 1 )
defer di . Upstream . Host . CountRequest ( - 1 )
@ -547,6 +554,9 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
zap . Object ( "headers" , caddyhttp . LoggableHTTPHeader ( res . Header ) ) ,
zap . Object ( "headers" , caddyhttp . LoggableHTTPHeader ( res . Header ) ) ,
zap . Int ( "status" , res . StatusCode ) )
zap . Int ( "status" , res . StatusCode ) )
// duration until upstream wrote response headers (roundtrip duration)
repl . Set ( "http.reverse_proxy.upstream.latency" , duration )
// update circuit breaker on current conditions
// update circuit breaker on current conditions
if di . Upstream . cb != nil {
if di . Upstream . cb != nil {
di . Upstream . cb . RecordMetric ( res . StatusCode , duration )
di . Upstream . cb . RecordMetric ( res . StatusCode , duration )
@ -579,8 +589,6 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
continue
continue
}
}
repl := req . Context ( ) . Value ( caddy . ReplacerCtxKey ) . ( * caddy . Replacer )
// if configured to only change the status code, do that then continue regular proxy response
// if configured to only change the status code, do that then continue regular proxy response
if statusCodeStr := rh . StatusCode . String ( ) ; statusCodeStr != "" {
if statusCodeStr := rh . StatusCode . String ( ) ; statusCodeStr != "" {
statusCode , err := strconv . Atoi ( repl . ReplaceAll ( statusCodeStr , "" ) )
statusCode , err := strconv . Atoi ( repl . ReplaceAll ( statusCodeStr , "" ) )
@ -625,7 +633,6 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
if h . Headers != nil && h . Headers . Response != nil {
if h . Headers != nil && h . Headers . Response != nil {
if h . Headers . Response . Require == nil ||
if h . Headers . Response . Require == nil ||
h . Headers . Response . Require . Match ( res . StatusCode , res . Header ) {
h . Headers . Response . Require . Match ( res . StatusCode , res . Header ) {
repl := req . Context ( ) . Value ( caddy . ReplacerCtxKey ) . ( * caddy . Replacer )
h . Headers . Response . ApplyTo ( res . Header , repl )
h . Headers . Response . ApplyTo ( res . Header , repl )
}
}
}
}
@ -673,6 +680,9 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia
}
}
}
}
// total duration spent proxying, including writing response body
repl . Set ( "http.reverse_proxy.upstream.duration" , duration )
if len ( res . Trailer ) == announcedTrailers {
if len ( res . Trailer ) == announcedTrailers {
copyHeader ( rw . Header ( ) , res . Trailer )
copyHeader ( rw . Header ( ) , res . Trailer )
return nil
return nil