@marcel91200 / caddy Mirror · read-only / commits / 98468af8
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 13, 16:45 UTC

reverseproxy: Fix double headers in response handlers (#4847)

Francis Lavoie committed Jun 22, 2022 at 15:10 UTC 98468af8b6224d29431576fe30a7d92a8676030d
5 files changed +34 -138
caddytest/integration/caddyfile_adapt/forward_auth_authelia.txt
-26
@@ -63,32 +63,6 @@ app.example.com {
63 ]
64 }
65 ]
66 - },
67 - {
68 - "routes": [
69 - {
70 - "handle": [
71 - {
72 - "exclude": [
73 - "Connection",
74 - "Keep-Alive",
75 - "Te",
76 - "Trailers",
77 - "Transfer-Encoding",
78 - "Upgrade"
79 - ],
80 - "handler": "copy_response_headers"
81 - }
82 - ]
83 - },
84 - {
85 - "handle": [
86 - {
87 - "handler": "copy_response"
88 - }
89 - ]
90 - }
91 - ]
66 }
67 ],
68 "handler": "reverse_proxy",
caddytest/integration/caddyfile_adapt/forward_auth_rename_headers.txt
-26
@@ -55,32 +55,6 @@ forward_auth localhost:9000 {
55 ]
56 }
57 ]
58 - },
59 - {
60 - "routes": [
61 - {
62 - "handle": [
63 - {
64 - "exclude": [
65 - "Connection",
66 - "Keep-Alive",
67 - "Te",
68 - "Trailers",
69 - "Transfer-Encoding",
70 - "Upgrade"
71 - ],
72 - "handler": "copy_response_headers"
73 - }
74 - ]
75 - },
76 - {
77 - "handle": [
78 - {
79 - "handler": "copy_response"
80 - }
81 - ]
82 - }
83 - ]
58 }
59 ],
60 "handler": "reverse_proxy",
modules/caddyhttp/reverseproxy/copyresponse.go
+1 -1
@@ -80,7 +80,7 @@ func (h CopyResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request
80 hrc.isFinalized = true
81
82 // write the response
83 - return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger, false)
83 + return hrc.handler.finalizeResponse(rw, req, hrc.response, repl, hrc.start, hrc.logger)
84 }
85
86 // CopyResponseHeadersHandler is a special HTTP handler which may
modules/caddyhttp/reverseproxy/forwardauth/caddyfile.go
+6 -41
@@ -59,13 +59,6 @@ func init() {
59 // Remote-Email {http.reverse_proxy.header.Remote-Email}
60 // }
61 // }
62 -//
63 -// handle_response {
64 -// copy_response_headers {
65 -// exclude Connection Keep-Alive Te Trailers Transfer-Encoding Upgrade
66 -// }
67 -// copy_response
68 -// }
62 // }
63 //
64 func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) {
@@ -217,41 +210,13 @@ func parseCaddyfile(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
210 },
211 )
212 }
220 - rpHandler.HandleResponse = append(rpHandler.HandleResponse, goodResponseHandler)
213
222 - // set up handler for denial responses; when a response
223 - // has any other status than 2xx, then we copy the response
224 - // back to the client, and terminate handling.
225 - denialResponseHandler := caddyhttp.ResponseHandler{
226 - Routes: []caddyhttp.Route{
227 - {
228 - HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
229 - &reverseproxy.CopyResponseHeadersHandler{
230 - Exclude: []string{
231 - "Connection",
232 - "Keep-Alive",
233 - "Te",
234 - "Trailers",
235 - "Transfer-Encoding",
236 - "Upgrade",
237 - },
238 - },
239 - "handler",
240 - "copy_response_headers",
241 - nil,
242 - )},
243 - },
244 - {
245 - HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
246 - &reverseproxy.CopyResponseHandler{},
247 - "handler",
248 - "copy_response",
249 - nil,
250 - )},
251 - },
252 - },
253 - }
254 - rpHandler.HandleResponse = append(rpHandler.HandleResponse, denialResponseHandler)
214 + // note that when a response has any other status than 2xx, then we
215 + // use the reverse proxy's default behaviour of copying the response
216 + // back to the client, so we don't need to explicitly add a response
217 + // handler specifically for that behaviour; we do need the 2xx handler
218 + // though, to make handling fall through to handlers deeper in the chain.
219 + rpHandler.HandleResponse = append(rpHandler.HandleResponse, goodResponseHandler)
220
221 // the rest of the config is specified by the user
222 // using the reverse_proxy directive syntax
modules/caddyhttp/reverseproxy/reverseproxy.go
+27 -44
@@ -784,18 +784,14 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
784 res.Body = h.bufferedBody(res.Body)
785 }
786
787 - // the response body may get closed by a response handler,
788 - // and we need to keep track to make sure we don't try to copy
789 - // the response if it was already closed
790 - bodyClosed := false
791 -
787 // see if any response handler is configured for this response from the backend
788 for i, rh := range h.HandleResponse {
789 if rh.Match != nil && !rh.Match.Match(res.StatusCode, res.Header) {
790 continue
791 }
792
798 - // if configured to only change the status code, do that then continue regular proxy response
793 + // if configured to only change the status code,
794 + // do that then continue regular proxy response
795 if statusCodeStr := rh.StatusCode.String(); statusCodeStr != "" {
796 statusCode, err := strconv.Atoi(repl.ReplaceAll(statusCodeStr, ""))
797 if err != nil {
@@ -840,33 +836,29 @@ func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origRe
836 // pass the request through the response handler routes
837 routeErr := rh.Routes.Compile(next).ServeHTTP(rw, origReq.WithContext(ctx))
838
843 - // if the response handler routes already finalized the response,
844 - // we can return early. It should be finalized if the routes executed
845 - // included a copy_response handler. If a fresh response was written
846 - // by the routes instead, then we still need to finalize the response
847 - // without copying the body.
848 - if routeErr == nil && hrc.isFinalized {
849 - return nil
839 + // close the response body afterwards, since we don't need it anymore;
840 + // either a route had 'copy_response' which already consumed the body,
841 + // or some other terminal handler ran which doesn't need the response
842 + // body after that point (e.g. 'file_server' for X-Accel-Redirect flow),
843 + // or we fell through to subsequent handlers past this proxy
844 + // (e.g. forward auth's 2xx response flow).
845 + if !hrc.isFinalized {
846 + res.Body.Close()
847 }
848
852 - // always close the response body afterwards, since it's expected
853 - // that the response handler routes will have written to the
854 - // response writer with a new body, if it wasn't already finalized.
855 - res.Body.Close()
856 - bodyClosed = true
857 -
849 + // wrap any route error in roundtripSucceeded so caller knows that
850 + // the roundtrip was successful and to not retry
851 if routeErr != nil {
859 - // wrap error in roundtripSucceeded so caller knows that
860 - // the roundtrip was successful and to not retry
852 return roundtripSucceeded{routeErr}
853 }
854
864 - // we've already closed the body, so there's no use allowing
865 - // another response handler to run as well
866 - break
855 + // we're done handling the response, and we don't want to
856 + // fall through to the default finalize/copy behaviour
857 + return nil
858 }
859
869 - return h.finalizeResponse(rw, req, res, repl, start, logger, bodyClosed)
860 + // copy the response body and headers back to the upstream client
861 + return h.finalizeResponse(rw, req, res, repl, start, logger)
862 }
863
864 // finalizeResponse prepares and copies the response.
@@ -877,7 +869,6 @@ func (h Handler) finalizeResponse(
869 repl *caddy.Replacer,
870 start time.Time,
871 logger *zap.Logger,
880 - bodyClosed bool,
872 ) error {
873 // deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
874 if res.StatusCode == http.StatusSwitchingProtocols {
@@ -891,13 +882,6 @@ func (h Handler) finalizeResponse(
882 res.Header.Del(h)
883 }
884
894 - // remove the content length if we're not going to be copying
895 - // from the response, because otherwise there'll be a mismatch
896 - // between bytes written and the advertised length
897 - if bodyClosed {
898 - res.Header.Del("Content-Length")
899 - }
900 -
885 // apply any response header operations
886 if h.Headers != nil && h.Headers.Response != nil {
887 if h.Headers.Response.Require == nil ||
@@ -920,17 +904,16 @@ func (h Handler) finalizeResponse(
904 }
905
906 rw.WriteHeader(res.StatusCode)
923 - if !bodyClosed {
924 - err := h.copyResponse(rw, res.Body, h.flushInterval(req, res))
925 - res.Body.Close() // close now, instead of defer, to populate res.Trailer
926 - if err != nil {
927 - // we're streaming the response and we've already written headers, so
928 - // there's nothing an error handler can do to recover at this point;
929 - // the standard lib's proxy panics at this point, but we'll just log
930 - // the error and abort the stream here
931 - h.logger.Error("aborting with incomplete response", zap.Error(err))
932 - return nil
933 - }
907 +
908 + err := h.copyResponse(rw, res.Body, h.flushInterval(req, res))
909 + res.Body.Close() // close now, instead of defer, to populate res.Trailer
910 + if err != nil {
911 + // we're streaming the response and we've already written headers, so
912 + // there's nothing an error handler can do to recover at this point;
913 + // the standard lib's proxy panics at this point, but we'll just log
914 + // the error and abort the stream here
915 + h.logger.Error("aborting with incomplete response", zap.Error(err))
916 + return nil
917 }
918
919 if len(res.Trailer) > 0 {