@marcel91200 / caddy Mirror · read-only / commits / 67a9e065
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 11, 18:30 UTC

reverseproxy: Fix retries for requests with bodies (#7360)

* capture the buffered body once, then reset clonedReq.Body before each retry * no copy * keep receiver name * set the buf to nil after extraction and only return it to pool if not nil --------- Co-authored-by: WeidiDeng <weidi_deng@icloud.com>

Petr committed Nov 24, 2025 at 23:03 UTC 67a9e0657e60df8c78510065e8977d86ee17d01c
1 file changed +22 -3
modules/caddyhttp/reverseproxy/reverseproxy.go
+22 -3
@@ -437,6 +437,20 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
437 reqHost := clonedReq.Host
438 reqHeader := clonedReq.Header
439
440 + // If the cloned request body was fully buffered, keep a reference to its
441 + // buffer so we can reuse it across retries and return it to the pool
442 + // once we’re done.
443 + var bufferedReqBody *bytes.Buffer
444 + if reqBodyBuf, ok := clonedReq.Body.(bodyReadCloser); ok && reqBodyBuf.body == nil && reqBodyBuf.buf != nil {
445 + bufferedReqBody = reqBodyBuf.buf
446 + reqBodyBuf.buf = nil
447 +
448 + defer func() {
449 + bufferedReqBody.Reset()
450 + bufPool.Put(bufferedReqBody)
451 + }()
452 + }
453 +
454 start := time.Now()
455 defer func() {
456 // total proxying duration, including time spent on LB and retries
@@ -455,8 +469,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
469 // and reusable, so if a backend partially or fully reads the body but then
470 // produces an error, the request can be repeated to the next backend with
471 // the full body (retries should only happen for idempotent requests) (see #6259)
458 - if reqBodyBuf, ok := r.Body.(bodyReadCloser); ok && reqBodyBuf.body == nil {
459 - r.Body = io.NopCloser(bytes.NewReader(reqBodyBuf.buf.Bytes()))
472 + if bufferedReqBody != nil {
473 + clonedReq.Body = io.NopCloser(bytes.NewReader(bufferedReqBody.Bytes()))
474 }
475
476 var done bool
@@ -1538,7 +1552,12 @@ type bodyReadCloser struct {
1552 }
1553
1554 func (brc bodyReadCloser) Close() error {
1541 - bufPool.Put(brc.buf)
1555 + // Inside this package this will be set to nil for fully-buffered
1556 + // requests due to the possibility of retrial.
1557 + if brc.buf != nil {
1558 + bufPool.Put(brc.buf)
1559 + }
1560 + // For fully-buffered bodies, body is nil, so Close is a no-op.
1561 if brc.body != nil {
1562 return brc.body.Close()
1563 }