mirror of
https://github.com/caddyserver/caddy.git
synced 2025-01-23 00:56:45 +01:00
It's a raw, low-level implementation for now, but it's very flexible. More sugar-coating can be added after error handling is more developed.
This commit is contained in:
parent
69b5643130
commit
538ddb8587
2 changed files with 87 additions and 15 deletions
|
@ -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)
|
||||||
|
|
|
@ -80,6 +80,36 @@ func (sr *Subroute) ServeHTTP(w http.ResponseWriter, r *http.Request, next Handl
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResponseHandler pairs a response matcher with a route list.
|
||||||
|
// It is useful for executing handler routes based on the
|
||||||
|
// properties of an HTTP response that has not been written
|
||||||
|
// out to the client yet.
|
||||||
|
//
|
||||||
|
// To use this type, provision it at module load time, then
|
||||||
|
// when ready to use, match the response against its matcher;
|
||||||
|
// if it matches (or doesn't have a matcher), invoke the routes
|
||||||
|
// by calling `rh.Routes.Compile(next).ServeHTTP(rw, req)` (or
|
||||||
|
// similar).
|
||||||
|
type ResponseHandler struct {
|
||||||
|
// The response matcher for this handler. If empty/nil,
|
||||||
|
// it always matches.
|
||||||
|
Match *ResponseMatcher `json:"match,omitempty"`
|
||||||
|
|
||||||
|
// The list of HTTP routes to execute.
|
||||||
|
Routes RouteList `json:"routes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision sets up the routse in rh.
|
||||||
|
func (rh *ResponseHandler) Provision(ctx caddy.Context) error {
|
||||||
|
if rh.Routes != nil {
|
||||||
|
err := rh.Routes.Provision(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Interface guards
|
// Interface guards
|
||||||
var (
|
var (
|
||||||
_ caddy.Provisioner = (*Subroute)(nil)
|
_ caddy.Provisioner = (*Subroute)(nil)
|
||||||
|
|
Loading…
Reference in a new issue