mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +03:00
reverseproxy: Fix double headers in response handlers (#4847)
This commit is contained in:
parent
25f10511e7
commit
98468af8b6
5 changed files with 34 additions and 138 deletions
|
@ -63,32 +63,6 @@ app.example.com {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"exclude": [
|
|
||||||
"Connection",
|
|
||||||
"Keep-Alive",
|
|
||||||
"Te",
|
|
||||||
"Trailers",
|
|
||||||
"Transfer-Encoding",
|
|
||||||
"Upgrade"
|
|
||||||
],
|
|
||||||
"handler": "copy_response_headers"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "copy_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handler": "reverse_proxy",
|
"handler": "reverse_proxy",
|
||||||
|
|
|
@ -55,32 +55,6 @@ forward_auth localhost:9000 {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"exclude": [
|
|
||||||
"Connection",
|
|
||||||
"Keep-Alive",
|
|
||||||
"Te",
|
|
||||||
"Trailers",
|
|
||||||
"Transfer-Encoding",
|
|
||||||
"Upgrade"
|
|
||||||
],
|
|
||||||
"handler": "copy_response_headers"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"handle": [
|
|
||||||
{
|
|
||||||
"handler": "copy_response"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"handler": "reverse_proxy",
|
"handler": "reverse_proxy",
|
||||||
|
|
|
@ -80,7 +80,7 @@ func (h CopyResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request
|
||||||
hrc.isFinalized = true
|
hrc.isFinalized = true
|
||||||
|
|
||||||
// write the response
|
// write the response
|
||||||
return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger, false)
|
return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyResponseHeadersHandler is a special HTTP handler which may
|
// CopyResponseHeadersHandler is a special HTTP handler which may
|
||||||
|
|
|
@ -59,13 +59,6 @@ func init() {
|
||||||
// Remote-Email {http.reverse_proxy.header.Remote-Email}
|
// Remote-Email {http.reverse_proxy.header.Remote-Email}
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
// handle_response {
|
|
||||||
// copy_response_headers {
|
|
||||||
// exclude Connection Keep-Alive Te Trailers Transfer-Encoding Upgrade
|
|
||||||
// }
|
|
||||||
// copy_response
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
|
func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
|
||||||
|
@ -217,41 +210,13 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
rpHandler.HandleResponse = append(rpHandler.HandleResponse, goodResponseHandler)
|
|
||||||
|
|
||||||
// set up handler for denial responses; when a response
|
// note that when a response has any other status than 2xx, then we
|
||||||
// has any other status than 2xx, then we copy the response
|
// use the reverse proxy's default behaviour of copying the response
|
||||||
// back to the client, and terminate handling.
|
// back to the client, so we don't need to explicitly add a response
|
||||||
denialResponseHandler := caddyhttp.ResponseHandler{
|
// handler specifically for that behaviour; we do need the 2xx handler
|
||||||
Routes: []caddyhttp.Route{
|
// though, to make handling fall through to handlers deeper in the chain.
|
||||||
{
|
rpHandler.HandleResponse = append(rpHandler.HandleResponse, goodResponseHandler)
|
||||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
|
|
||||||
&reverseproxy.CopyResponseHeadersHandler{
|
|
||||||
Exclude: []string{
|
|
||||||
"Connection",
|
|
||||||
"Keep-Alive",
|
|
||||||
"Te",
|
|
||||||
"Trailers",
|
|
||||||
"Transfer-Encoding",
|
|
||||||
"Upgrade",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"handler",
|
|
||||||
"copy_response_headers",
|
|
||||||
nil,
|
|
||||||
)},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
|
|
||||||
&reverseproxy.CopyResponseHandler{},
|
|
||||||
"handler",
|
|
||||||
"copy_response",
|
|
||||||
nil,
|
|
||||||
)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
rpHandler.HandleResponse = append(rpHandler.HandleResponse, denialResponseHandler)
|
|
||||||
|
|
||||||
// the rest of the config is specified by the user
|
// the rest of the config is specified by the user
|
||||||
// using the reverse_proxy directive syntax
|
// using the reverse_proxy directive syntax
|
||||||
|
|
|
@ -784,18 +784,14 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
|
||||||
res.Body = h.bufferedBody(res.Body)
|
res.Body = h.bufferedBody(res.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the response body may get closed by a response handler,
|
|
||||||
// and we need to keep track to make sure we don't try to copy
|
|
||||||
// the response if it was already closed
|
|
||||||
bodyClosed := false
|
|
||||||
|
|
||||||
// see if any response handler is configured for this response from the backend
|
// see if any response handler is configured for this response from the backend
|
||||||
for i, rh := range h.HandleResponse {
|
for i, rh := range h.HandleResponse {
|
||||||
if rh.Match != nil && !rh.Match.Match(res.StatusCode, res.Header) {
|
if rh.Match != nil && !rh.Match.Match(res.StatusCode, res.Header) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, ""))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -840,33 +836,29 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
|
||||||
// pass the request through the response handler routes
|
// pass the request through the response handler routes
|
||||||
routeErr := rh.Routes.Compile(next).ServeHTTP(rw, origReq.WithContext(ctx))
|
routeErr := rh.Routes.Compile(next).ServeHTTP(rw, origReq.WithContext(ctx))
|
||||||
|
|
||||||
// if the response handler routes already finalized the response,
|
// close the response body afterwards, since we don't need it anymore;
|
||||||
// we can return early. It should be finalized if the routes executed
|
// either a route had 'copy_response' which already consumed the body,
|
||||||
// included a copy_response handler. If a fresh response was written
|
// or some other terminal handler ran which doesn't need the response
|
||||||
// by the routes instead, then we still need to finalize the response
|
// body after that point (e.g. 'file_server' for X-Accel-Redirect flow),
|
||||||
// without copying the body.
|
// or we fell through to subsequent handlers past this proxy
|
||||||
if routeErr == nil && hrc.isFinalized {
|
// (e.g. forward auth's 2xx response flow).
|
||||||
return nil
|
if !hrc.isFinalized {
|
||||||
|
res.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// always close the response body afterwards, since it's expected
|
// wrap any route error in roundtripSucceeded so caller knows that
|
||||||
// that the response handler routes will have written to the
|
// the roundtrip was successful and to not retry
|
||||||
// response writer with a new body, if it wasn't already finalized.
|
|
||||||
res.Body.Close()
|
|
||||||
bodyClosed = true
|
|
||||||
|
|
||||||
if routeErr != nil {
|
if routeErr != nil {
|
||||||
// wrap error in roundtripSucceeded so caller knows that
|
|
||||||
// the roundtrip was successful and to not retry
|
|
||||||
return roundtripSucceeded{routeErr}
|
return roundtripSucceeded{routeErr}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we've already closed the body, so there's no use allowing
|
// we're done handling the response, and we don't want to
|
||||||
// another response handler to run as well
|
// fall through to the default finalize/copy behaviour
|
||||||
break
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.finalizeResponse(rw, req, res, repl, start, logger, bodyClosed)
|
// copy the response body and headers back to the upstream client
|
||||||
|
return h.finalizeResponse(rw, req, res, repl, start, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// finalizeResponse prepares and copies the response.
|
// finalizeResponse prepares and copies the response.
|
||||||
|
@ -877,7 +869,6 @@ func (h Handler) finalizeResponse(
|
||||||
repl *caddy.Replacer,
|
repl *caddy.Replacer,
|
||||||
start time.Time,
|
start time.Time,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
bodyClosed bool,
|
|
||||||
) error {
|
) error {
|
||||||
// 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 {
|
||||||
|
@ -891,13 +882,6 @@ func (h Handler) finalizeResponse(
|
||||||
res.Header.Del(h)
|
res.Header.Del(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the content length if we're not going to be copying
|
|
||||||
// from the response, because otherwise there'll be a mismatch
|
|
||||||
// between bytes written and the advertised length
|
|
||||||
if bodyClosed {
|
|
||||||
res.Header.Del("Content-Length")
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply any response header operations
|
// apply any response header operations
|
||||||
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 ||
|
||||||
|
@ -920,17 +904,16 @@ func (h Handler) finalizeResponse(
|
||||||
}
|
}
|
||||||
|
|
||||||
rw.WriteHeader(res.StatusCode)
|
rw.WriteHeader(res.StatusCode)
|
||||||
if !bodyClosed {
|
|
||||||
err := h.copyResponse(rw, res.Body, h.flushInterval(req, res))
|
err := h.copyResponse(rw, res.Body, h.flushInterval(req, res))
|
||||||
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we're streaming the response and we've already written headers, so
|
// we're streaming the response and we've already written headers, so
|
||||||
// there's nothing an error handler can do to recover at this point;
|
// there's nothing an error handler can do to recover at this point;
|
||||||
// the standard lib's proxy panics at this point, but we'll just log
|
// the standard lib's proxy panics at this point, but we'll just log
|
||||||
// the error and abort the stream here
|
// the error and abort the stream here
|
||||||
h.logger.Error("aborting with incomplete response", zap.Error(err))
|
h.logger.Error("aborting with incomplete response", zap.Error(err))
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(res.Trailer) > 0 {
|
if len(res.Trailer) > 0 {
|
||||||
|
|
Loading…
Reference in a new issue