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
}