Skip to content

fix: derive HTTPClient2 from HTTPClient - #600

Merged
github-actions[bot] merged 1 commit into
mainfrom
dwisiswant0/fix/derive-HTTPClient2-from-HTTPClient
Oct 14, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
dwisiswant0/fix/derive-HTTPClient2-from-HTTPClient

Conversation

@dwisiswant0

Copy link
Copy Markdown
Member

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

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>
@dwisiswant0
dwisiswant0 requested a review from Mzack9999 October 14, 2026 15:32

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automatically approved using projectdiscovery/actions/pr/approve action.

@github-actions
github-actions Bot merged commit 08d5f64 into main Oct 14, 2026
5 of 6 checks passed
@github-actions
github-actions Bot deleted the dwisiswant0/fix/derive-HTTPClient2-from-HTTPClient branch October 14, 2026 15:35
@dwisiswant0

Copy link
Copy Markdown
Member Author

huh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTP/2 fallback ignores the client passed to NewWithHTTPClient

1 participant