6 Commits
Author SHA1 Message Date
Matthew Holt 7a58576d34 reverseproxy: keep empty unknown-length request bodies retryable
The consumed-stream retry guard treated any read of a streaming request
body as consumption. Go's transport probes unknown-length bodies of GET,
HEAD, and similar requests before sending them, and HTTP/3 reports
bodyless GET and HEAD requests with an unknown length, so every such
request lost its retries once an upstream connection was established.

A first read that returns io.EOF without data no longer counts as
consumption. Later attempts send http.NoBody, while bodies that yield
data, fail, or are interrupted mid-read remain non-retryable.

Follow-up to de1bcf6d-457c-4348-9d4e-5eeb41cc120b.

Code written by GitHub Copilot (GPT-6.1 Sol, Claude Opus 5.5); reviewed by mholt.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-11 11:06:41 -07:00
Adarsh KumarandClaude Opus 5.5 f355b8687d reverseproxy: retry dial errors wrapped by a forward proxy (#8264)
When a forward proxy is configured (network_proxy, forward_proxy_url or
HTTP_PROXY), net/http wraps dial errors in a *net.OpError with Op
"proxyconnect". tryAgain used a type assertion to detect DialError, so it
missed the wrapped error and treated it as a failure after the request
was sent. Non-GET requests were then not retried, even though no
connection to the proxy was ever established.

Use errors.As so a DialError is recognized anywhere in the error chain.
Errors that do not contain a DialError are unaffected, so requests that
may have reached the proxy or upstream are still not retried by default.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 11:49:32 +00:00
Jay Hemnani 7c7456353e reverseproxy: Only retry responses for expressions that use response data (#8168)
* reverseproxy: Only retry responses for expressions that use response data

Since #7569 every lb_retry_match set with an expression is evaluated against
upstream responses. An expression that only tests the request, such as
{http.request.method} == "POST", then matched every successful response:
the request was sent again to the next upstreams and the client got a 502
once retries ran out. Only treat expressions referencing {rp.*} placeholders
as response conditions.

* reverseproxy: Only count placeholders set from the response as response data

* reverseproxy: Read response placeholders from the checked expression

The expression matcher records the placeholder names its checked CEL
expression reads, so retry_match finds direct ph() calls and ignores
escaped placeholder text in string literals. A placeholder name only
known at evaluation time counts as response data.

* reverseproxy: Match response placeholder names exactly

Only the header placeholders are matched by prefix now, and both
upstream latency placeholders are listed. MatchExpression.Placeholders
returns a copy, so callers cannot change the provisioned matcher.
2026-10-10 07:22:32 +10:00
James Hartig 77e9ce7404 reverseproxy: further prevent body closes from dial errors (#7715) 2026-05-12 12:05:50 -06:00
Daniil Sivak aed1af5976 reverseproxy: add lb_retry_match condition on response status (#7569) 2026-04-21 14:59:31 -04:00
Tom Paulus a5e7c6e232 reverseproxy: prevent body close on dial-error retries (#7547) 2026-03-04 15:17:02 -05:00