Repository navigation
fix: derive HTTPClient2 from HTTPClient - #600
Merged
github-actions[bot] merged 1 commit intoOct 14, 2026
Merged
Conversation
When the HTTP/1.x transport fails with `malformed HTTP version "HTTP/2"` or `malformed HTTP response`, Do retries the request on HTTPClient2. NewClient always built that client from DefaultClient(), so the retry ignored a client passed through Options.HttpClient or NewWithHTTPClient. The retry skipped the caller's proxy, dialer, TLS config, cookie jar, and redirect policy, and could connect to the target directly. Build HTTPClient2 as a copy of HTTPClient instead. The copy gets its own clone of the transport, so the caller's transport keeps its own HTTP/2 settings. The copy is made before WrapTransport runs, because the wrapper hides the *http.Transport to clone. A RoundTripper that isn't an *http.Transport can't be cloned, so the copy shares it and the retry still goes through it. Set ForceAttemptHTTP2 on the clone. net/http doesn't enable HTTP/2 for a transport with a custom TLS config or dialer unless forced, and http2.ConfigureTransport in golang.org/x/net v0.55.0 is a no-op on Go 1.27 and later. Without it, cloning a transport that enables HTTP/2 implicitly gives a client that offers h2 during the TLS handshake and then speaks HTTP/1.1 on that connection. This also changes clients built without a custom http.Client. In single-host mode (KillIdleConn false), HTTPClient2 now keeps idle connections for reuse, as HTTPClient already does; before, it disabled keep-alives. Both clients also share one TLS session cache. Signed-off-by: Dwi Siswanto <git@dw1.io>
github-actions
Bot
deleted the
dwisiswant0/fix/derive-HTTPClient2-from-HTTPClient
branch
October 14, 2026 15:35
Member
Author
|
huh |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When the HTTP/1.x transport fails with
malformed HTTP version "HTTP/2"or
malformed HTTP response, Do retries the request on HTTPClient2.NewClient always built that client from DefaultClient(), so the retry
ignored a client passed through Options.HttpClient or
NewWithHTTPClient. The retry skipped the caller's proxy, dialer, TLS
config, cookie jar, and redirect policy, and could connect to the
target directly.
Build HTTPClient2 as a copy of HTTPClient instead. The copy gets its
own clone of the transport, so the caller's transport keeps its own
HTTP/2 settings. The copy is made before WrapTransport runs, because
the wrapper hides the *http.Transport to clone. A RoundTripper that
isn't an *http.Transport can't be cloned, so the copy shares it and
the retry still goes through it.
Set ForceAttemptHTTP2 on the clone. net/http doesn't enable HTTP/2 for
a transport with a custom TLS config or dialer unless forced, and
http2.ConfigureTransport in golang.org/x/net v0.55.0 is a no-op on
Go 1.27 and later. Without it, cloning a transport that enables HTTP/2
implicitly gives a client that offers h2 during the TLS handshake and
then speaks HTTP/1.1 on that connection.
This also changes clients built without a custom http.Client. In
single-host mode (KillIdleConn false), HTTPClient2 now keeps idle
connections for reuse, as HTTPClient already does; before, it disabled
keep-alives. Both clients also share one TLS session cache.
Fixes #599