|  | @ -94,6 +94,20 @@ type Handler struct { | 
			
		
	
		
		
			
				
					|  |  | 	// be avoided if at all possible for performance reasons.
 |  |  | 	// be avoided if at all possible for performance reasons.
 | 
			
		
	
		
		
			
				
					|  |  | 	BufferRequests bool `json:"buffer_requests,omitempty"` |  |  | 	BufferRequests bool `json:"buffer_requests,omitempty"` | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// List of handlers and their associated matchers to evaluate
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// after successful roundtrips. The first handler that matches
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// the response from a backend will be invoked. The response
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// body from the backend will not be written to the client;
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// it is up to the handler to finish handling the response.
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// If passive health checks are enabled, any errors from the
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// handler chain will not affect the health status of the
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// backend.
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	//
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// Two new placeholders are available in this handler chain:
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// - `{http.reverse_proxy.status_code}` The status code
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// - `{http.reverse_proxy.status_text}` The status text
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	HandleResponse []caddyhttp.ResponseHandler `json:"handle_response,omitempty"` | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  | 	Transport http.RoundTripper `json:"-"` |  |  | 	Transport http.RoundTripper `json:"-"` | 
			
		
	
		
		
			
				
					|  |  | 	CB        CircuitBreaker    `json:"-"` |  |  | 	CB        CircuitBreaker    `json:"-"` | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  | @ -252,6 +266,14 @@ func (h *Handler) Provision(ctx caddy.Context) error { | 
			
		
	
		
		
			
				
					|  |  | 		go h.activeHealthChecker() |  |  | 		go h.activeHealthChecker() | 
			
		
	
		
		
			
				
					|  |  | 	} |  |  | 	} | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	// set up any response routes
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	for i, rh := range h.HandleResponse { | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		err := rh.Provision(ctx) | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		if err != nil { | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 			return fmt.Errorf("provisioning response handler %d: %v", i, err) | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		} | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	} | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  | 	return nil |  |  | 	return nil | 
			
		
	
		
		
			
				
					|  |  | } |  |  | } | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  | @ -361,13 +383,21 @@ 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) |  |  | 		proxyErr = h.reverseProxy(w, r, 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
 | 
			
		
	
		
		
			
				
					|  |  | 			return nil |  |  | 			return nil | 
			
		
	
		
		
			
				
					|  |  | 		} |  |  | 		} | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		// if the roundtrip was successful, don't retry the request or
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		// ding the health status of the upstream (an error can still
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		// occur after the roundtrip if, for example, a response handler
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		// after the roundtrip returns an error)
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		if succ, ok := proxyErr.(roundtripSucceeded); ok { | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 			return succ.error | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		} | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  | 		// remember this failure (if enabled)
 |  |  | 		// remember this failure (if enabled)
 | 
			
		
	
		
		
			
				
					|  |  | 		h.countFailure(upstream) |  |  | 		h.countFailure(upstream) | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  | @ -456,7 +486,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) error { |  |  | func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di DialInfo, next caddyhttp.Handler) error { | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					|  |  | 	di.Upstream.Host.CountRequest(1) |  |  | 	di.Upstream.Host.CountRequest(1) | 
			
		
	
		
		
			
				
					|  |  | 	defer di.Upstream.Host.CountRequest(-1) |  |  | 	defer di.Upstream.Host.CountRequest(-1) | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  | @ -471,16 +501,14 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia | 
			
		
	
		
		
			
				
					|  |  | 	logger := h.logger.With( |  |  | 	logger := h.logger.With( | 
			
		
	
		
		
			
				
					|  |  | 		zap.String("upstream", di.Upstream.String()), |  |  | 		zap.String("upstream", di.Upstream.String()), | 
			
		
	
		
		
			
				
					|  |  | 		zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}), |  |  | 		zap.Object("request", caddyhttp.LoggableHTTPRequest{Request: req}), | 
			
		
	
		
		
			
				
					
					|  |  | 		zap.Duration("duration", duration), |  |  | 		zap.Duration("duration", duration)) | 
			
				
				
			
		
	
		
		
			
				
					|  |  | 	) |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					|  |  | 	if err != nil { |  |  | 	if err != nil { | 
			
		
	
		
		
			
				
					|  |  | 		logger.Debug("upstream roundtrip", zap.Error(err)) |  |  | 		logger.Debug("upstream roundtrip", zap.Error(err)) | 
			
		
	
		
		
			
				
					|  |  | 		return err |  |  | 		return err | 
			
		
	
		
		
			
				
					|  |  | 	} |  |  | 	} | 
			
		
	
		
		
			
				
					|  |  | 	logger.Debug("upstream roundtrip", |  |  | 	logger.Debug("upstream roundtrip", | 
			
		
	
		
		
			
				
					|  |  | 		zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)), |  |  | 		zap.Object("headers", caddyhttp.LoggableHTTPHeader(res.Header)), | 
			
		
	
		
		
			
				
					
					|  |  | 		zap.Int("status", res.StatusCode), |  |  | 		zap.Int("status", res.StatusCode)) | 
			
				
				
			
		
	
		
		
			
				
					|  |  | 	) |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  | 	// update circuit breaker on current conditions
 |  |  | 	// update circuit breaker on current conditions
 | 
			
		
	
		
		
			
				
					|  |  | 	if di.Upstream.cb != nil { |  |  | 	if di.Upstream.cb != nil { | 
			
		
	
	
		
		
			
				
					|  | @ -503,6 +531,25 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia | 
			
		
	
		
		
			
				
					|  |  | 		} |  |  | 		} | 
			
		
	
		
		
			
				
					|  |  | 	} |  |  | 	} | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	for i, rh := range h.HandleResponse { | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		if len(rh.Routes) == 0 { | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 			continue | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		} | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		if rh.Match != nil && !rh.Match.Match(res.StatusCode, res.Header) { | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 			continue | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		} | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		res.Body.Close() | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		repl := req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		repl.Set("http.reverse_proxy.status_code", res.StatusCode) | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		repl.Set("http.reverse_proxy.status_text", res.Status) | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		h.logger.Debug("handling response", zap.Int("handler", i)) | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		if routeErr := rh.Routes.Compile(next).ServeHTTP(rw, req); routeErr != nil { | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 			// wrap error in roundtripSucceeded so caller knows that
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 			// the roundtrip was successful and to not retry
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 			return roundtripSucceeded{routeErr} | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 		} | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 	} | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  | 	// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
 |  |  | 	// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
 | 
			
		
	
		
		
			
				
					|  |  | 	if res.StatusCode == http.StatusSwitchingProtocols { |  |  | 	if res.StatusCode == http.StatusSwitchingProtocols { | 
			
		
	
		
		
			
				
					|  |  | 		h.handleUpgradeResponse(rw, req, res) |  |  | 		h.handleUpgradeResponse(rw, req, res) | 
			
		
	
	
		
		
			
				
					|  | @ -537,15 +584,6 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, di Dia | 
			
		
	
		
		
			
				
					|  |  | 		rw.Header().Add("Trailer", strings.Join(trailerKeys, ", ")) |  |  | 		rw.Header().Add("Trailer", strings.Join(trailerKeys, ", ")) | 
			
		
	
		
		
			
				
					|  |  | 	} |  |  | 	} | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  | 	// TODO: there should be an option to return an error if the response
 |  |  |  | 
			
		
	
		
		
			
				
					|  |  | 	// matches some criteria; would solve https://github.com/caddyserver/caddy/issues/1447
 |  |  |  | 
			
		
	
		
		
			
				
					|  |  | 	// by allowing the backend to determine whether this server should treat
 |  |  |  | 
			
		
	
		
		
			
				
					|  |  | 	// a 400+ status code as an error -- but we might need to be careful that
 |  |  |  | 
			
		
	
		
		
			
				
					|  |  | 	// we do not affect the health status of the backend... still looking into
 |  |  |  | 
			
		
	
		
		
			
				
					|  |  | 	// that; if we need to avoid that, we should return a particular error type
 |  |  |  | 
			
		
	
		
		
			
				
					|  |  | 	// that the caller of this function checks for and only applies health
 |  |  |  | 
			
		
	
		
		
			
				
					|  |  | 	// status changes if the error is not this special type
 |  |  |  | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  |  | 
			
		
	
		
		
			
				
					|  |  | 	rw.WriteHeader(res.StatusCode) |  |  | 	rw.WriteHeader(res.StatusCode) | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  | 	err = h.copyResponse(rw, res.Body, h.flushInterval(req, res)) |  |  | 	err = h.copyResponse(rw, res.Body, h.flushInterval(req, res)) | 
			
		
	
	
		
		
			
				
					|  | @ -782,6 +820,10 @@ type TLSTransport interface { | 
			
		
	
		
		
			
				
					|  |  | 	EnableTLS(base *TLSConfig) error |  |  | 	EnableTLS(base *TLSConfig) error | 
			
		
	
		
		
			
				
					|  |  | } |  |  | } | 
			
		
	
		
		
			
				
					|  |  | 
 |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | // roundtripSucceeded is an error type that is returned if the
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | // roundtrip succeeded, but an error occurred after-the-fact.
 | 
			
		
	
		
		
			
				
					|  |  |  |  |  | type roundtripSucceeded struct{ error } | 
			
		
	
		
		
			
				
					|  |  |  |  |  | 
 | 
			
		
	
		
		
			
				
					|  |  | var bufPool = sync.Pool{ |  |  | var bufPool = sync.Pool{ | 
			
		
	
		
		
			
				
					|  |  | 	New: func() interface{} { |  |  | 	New: func() interface{} { | 
			
		
	
		
		
			
				
					|  |  | 		return new(bytes.Buffer) |  |  | 		return new(bytes.Buffer) | 
			
		
	
	
		
		
			
				
					|  | 
 |