@marcel91200 / caddy Mirror · read-only / commits / f5e10494
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, 21:45 UTC

reverseproxy: Make shallow-ish clone of the request (#4551)

* reverseproxy: Make shallow-ish clone of the request * Refactor request cloning into separate function Co-authored-by: Matthew Holt <mholt@users.noreply.github.com>

Francis Lavoie committed Mar 3, 2022 at 11:54 UTC f5e104944ea48e776580a0a9ebbd02fca33d659f
1 file changed +76 -61
modules/caddyhttp/reverseproxy/reverseproxy.go
+76 -61
@@ -358,54 +358,20 @@ func (h *Handler) Cleanup() error {
358 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
359 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
360
361 - // if enabled, buffer client request;
362 - // this should only be enabled if the
363 - // upstream requires it and does not
364 - // work with "slow clients" (gunicorn,
365 - // etc.) - this obviously has a perf
366 - // overhead and makes the proxy at
367 - // risk of exhausting memory and more
368 - // susceptible to slowloris attacks,
369 - // so it is strongly recommended to
370 - // only use this feature if absolutely
371 - // required, if read timeouts are set,
372 - // and if body size is limited
373 - if h.BufferRequests {
374 - r.Body = h.bufferedBody(r.Body)
375 - }
376 -
361 // prepare the request for proxying; this is needed only once
378 - err := h.prepareRequest(r)
362 + clonedReq, err := h.prepareRequest(r)
363 if err != nil {
364 return caddyhttp.Error(http.StatusInternalServerError,
365 fmt.Errorf("preparing request for upstream round-trip: %v", err))
366 }
367
368 // we will need the original headers and Host value if
385 - // header operations are configured; and we should
386 - // restore them after we're done if they are changed
387 - // (for example, changing the outbound Host header
388 - // should not permanently change r.Host; issue #3509)
389 - reqHost := r.Host
390 - reqHeader := r.Header
391 -
392 - // sanitize the request URL; we expect it to not contain the scheme and host
393 - // since those should be determined by r.TLS and r.Host respectively, but
394 - // some clients may include it in the request-line, which is technically
395 - // valid in HTTP, but breaks reverseproxy behaviour, overriding how the
396 - // dialer will behave. See #4237 for context.
397 - origURLScheme := r.URL.Scheme
398 - origURLHost := r.URL.Host
399 - r.URL.Scheme = ""
400 - r.URL.Host = ""
401 -
402 - // restore modifications to the request after we're done proxying
403 - defer func() {
404 - r.Host = reqHost // TODO: data race, see #4038
405 - r.Header = reqHeader // TODO: data race, see #4038
406 - r.URL.Scheme = origURLScheme
407 - r.URL.Host = origURLHost
408 - }()
369 + // header operations are configured; this is so that each
370 + // retry can apply the modifications, because placeholders
371 + // may be used which depend on the selected upstream for
372 + // their values
373 + reqHost := clonedReq.Host
374 + reqHeader := clonedReq.Header
375
376 start := time.Now()
377 defer func() {
@@ -416,12 +382,12 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
382 var proxyErr error
383 for {
384 // choose an available upstream
419 - upstream := h.LoadBalancing.SelectionPolicy.Select(h.Upstreams, r, w)
385 + upstream := h.LoadBalancing.SelectionPolicy.Select(h.Upstreams, clonedReq, w)
386 if upstream == nil {
387 if proxyErr == nil {
388 proxyErr = fmt.Errorf("no upstreams available")
389 }
424 - if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, r) {
390 + if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, clonedReq) {
391 break
392 }
393 continue
@@ -430,7 +396,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
396 // the dial address may vary per-request if placeholders are
397 // used, so perform those replacements here; the resulting
398 // DialInfo struct should have valid network address syntax
433 - dialInfo, err := upstream.fillDialInfo(r)
399 + dialInfo, err := upstream.fillDialInfo(clonedReq)
400 if err != nil {
401 return statusError(fmt.Errorf("making dial info: %v", err))
402 }
@@ -451,17 +417,17 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
417
418 // mutate request headers according to this upstream;
419 // because we're in a retry loop, we have to copy
454 - // headers (and the r.Host value) from the original
420 + // headers (and the Host value) from the original
421 // so that each retry is identical to the first
422 if h.Headers != nil && h.Headers.Request != nil {
457 - r.Header = make(http.Header)
458 - copyHeader(r.Header, reqHeader)
459 - r.Host = reqHost
460 - h.Headers.Request.ApplyToRequest(r)
423 + clonedReq.Header = make(http.Header)
424 + copyHeader(clonedReq.Header, reqHeader)
425 + clonedReq.Host = reqHost
426 + h.Headers.Request.ApplyToRequest(clonedReq)
427 }
428
429 // proxy the request to that upstream
464 - proxyErr = h.reverseProxy(w, r, repl, dialInfo, next)
430 + proxyErr = h.reverseProxy(w, clonedReq, repl, dialInfo, next)
431 if proxyErr == nil || proxyErr == context.Canceled {
432 // context.Canceled happens when the downstream client
433 // cancels the request, which is not our failure
@@ -480,7 +446,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
446 h.countFailure(upstream)
447
448 // if we've tried long enough, break
483 - if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, r) {
449 + if !h.LoadBalancing.tryAgain(h.ctx, start, proxyErr, clonedReq) {
450 break
451 }
452 }
@@ -488,14 +454,27 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
454 return statusError(proxyErr)
455 }
456
491 -// prepareRequest modifies req so that it is ready to be proxied,
492 -// except for directing to a specific upstream. This method mutates
493 -// headers and other necessary properties of the request and should
494 -// be done just once (before proxying) regardless of proxy retries.
495 -// This assumes that no mutations of the request are performed
496 -// by h during or after proxying.
497 -func (h Handler) prepareRequest(req *http.Request) error {
498 - // most of this is borrowed from the Go std lib reverse proxy
457 +// prepareRequest clones req so that it can be safely modified without
458 +// changing the original request or introducing data races. It then
459 +// modifies it so that it is ready to be proxied, except for directing
460 +// to a specific upstream. This method adjusts headers and other relevant
461 +// properties of the cloned request and should be done just once (before
462 +// proxying) regardless of proxy retries. This assumes that no mutations
463 +// of the cloned request are performed by h during or after proxying.
464 +func (h Handler) prepareRequest(req *http.Request) (*http.Request, error) {
465 + req = cloneRequest(req)
466 +
467 + // if enabled, buffer client request; this should only be
468 + // enabled if the upstream requires it and does not work
469 + // with "slow clients" (gunicorn, etc.) - this obviously
470 + // has a perf overhead and makes the proxy at risk of
471 + // exhausting memory and more susceptible to slowloris
472 + // attacks, so it is strongly recommended to only use this
473 + // feature if absolutely required, if read timeouts are
474 + // set, and if body size is limited
475 + if h.BufferRequests {
476 + req.Body = h.bufferedBody(req.Body)
477 + }
478
479 if req.ContentLength == 0 {
480 req.Body = nil // Issue golang/go#16036: nil Body for http.Transport retries
@@ -560,7 +539,7 @@ func (h Handler) prepareRequest(req *http.Request) error {
539 req.Header.Set("X-Forwarded-Proto", proto)
540 }
541
563 - return nil
542 + return req, nil
543 }
544
545 // reverseProxy performs a round-trip to the given backend and processes the response with the client.
@@ -807,7 +786,7 @@ func (lb LoadBalancing) tryAgain(ctx caddy.Context, start time.Time, proxyErr er
786
787 // directRequest modifies only req.URL so that it points to the upstream
788 // in the given DialInfo. It must modify ONLY the request URL.
810 -func (h Handler) directRequest(req *http.Request, di DialInfo) {
789 +func (Handler) directRequest(req *http.Request, di DialInfo) {
790 // we need a host, so set the upstream's host address
791 reqHost := di.Address
792
@@ -845,6 +824,42 @@ func (h Handler) bufferedBody(originalBody io.ReadCloser) io.ReadCloser {
824 }
825 }
826
827 +// cloneRequest makes a semi-deep clone of origReq.
828 +//
829 +// Most of this code is borrowed from the Go stdlib reverse proxy,
830 +// but we make a shallow-ish clone the request (deep clone only
831 +// the headers and URL) so we can avoid manipulating the original
832 +// request when using it to proxy upstream. This prevents request
833 +// corruption and data races.
834 +func cloneRequest(origReq *http.Request) *http.Request {
835 + req := new(http.Request)
836 + *req = *origReq
837 + if origReq.URL != nil {
838 + newURL := new(url.URL)
839 + *newURL = *origReq.URL
840 + if origReq.URL.User != nil {
841 + newURL.User = new(url.Userinfo)
842 + *newURL.User = *origReq.URL.User
843 + }
844 + // sanitize the request URL; we expect it to not contain the
845 + // scheme and host since those should be determined by r.TLS
846 + // and r.Host respectively, but some clients may include it
847 + // in the request-line, which is technically valid in HTTP,
848 + // but breaks reverseproxy behaviour, overriding how the
849 + // dialer will behave. See #4237 for context.
850 + newURL.Scheme = ""
851 + newURL.Host = ""
852 + req.URL = newURL
853 + }
854 + if origReq.Header != nil {
855 + req.Header = origReq.Header.Clone()
856 + }
857 + if origReq.Trailer != nil {
858 + req.Trailer = origReq.Trailer.Clone()
859 + }
860 + return req
861 +}
862 +
863 func copyHeader(dst, src http.Header) {
864 for k, vv := range src {
865 for _, v := range vv {