From a4701aded0963ed1e0621c6b1eb02ea4c0833bbf Mon Sep 17 00:00:00 2001 From: whoami Date: Sat, 21 Feb 2026 02:29:56 +0800 Subject: [PATCH 1/3] fix(httpx): honor http11 across retry and h2 probe clients --- common/httpx/httpx.go | 33 ++++++++++++++---------- common/httpx/httpx_protocol_test.go | 39 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 common/httpx/httpx_protocol_test.go diff --git a/common/httpx/httpx.go b/common/httpx/httpx.go index 039f4c4c..dbd33805 100644 --- a/common/httpx/httpx.go +++ b/common/httpx/httpx.go @@ -183,19 +183,26 @@ func New(options *Options) (*HTTPX, error) { CheckRedirect: redirectFunc, }, retryablehttpOptions) - transport2 := &http2.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - MinVersion: tls.VersionTLS10, - }, - AllowHTTP: true, - } - if httpx.Options.SniName != "" { - transport2.TLSClientConfig.ServerName = httpx.Options.SniName - } - httpx.client2 = &http.Client{ - Transport: transport2, - Timeout: httpx.Options.Timeout, + // honor explicit HTTP/1.1 mode by disabling retryablehttp-go's internal + // HTTP/2 fallback client and HTTPX's own HTTP/2 probing client + if httpx.Options.Protocol == "http11" { + httpx.client.HTTPClient2 = httpx.client.HTTPClient + httpx.client2 = httpx.client.HTTPClient + } else { + transport2 := &http2.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + MinVersion: tls.VersionTLS10, + }, + AllowHTTP: true, + } + if httpx.Options.SniName != "" { + transport2.TLSClientConfig.ServerName = httpx.Options.SniName + } + httpx.client2 = &http.Client{ + Transport: transport2, + Timeout: httpx.Options.Timeout, + } } httpx.htmlPolicy = bluemonday.NewPolicy() diff --git a/common/httpx/httpx_protocol_test.go b/common/httpx/httpx_protocol_test.go new file mode 100644 index 00000000..9c22ddee --- /dev/null +++ b/common/httpx/httpx_protocol_test.go @@ -0,0 +1,39 @@ +package httpx + +import ( + "testing" + + "golang.org/x/net/http2" + + "github.com/stretchr/testify/require" +) + +func TestNew_HTTP11DisablesRetryableHTTP2Fallback(t *testing.T) { + opts := DefaultOptions + opts.Protocol = HTTP11 + + ht, err := New(&opts) + require.NoError(t, err) + require.NotNil(t, ht) + t.Cleanup(func() { ht.Dialer.Close() }) + require.NotNil(t, ht.client) + require.Same(t, ht.client.HTTPClient, ht.client.HTTPClient2) + require.Same(t, ht.client.HTTPClient, ht.client2) + _, isHTTP2 := ht.client2.Transport.(*http2.Transport) + require.False(t, isHTTP2) +} + +func TestNew_NonHTTP11KeepsRetryableHTTP2FallbackClient(t *testing.T) { + opts := DefaultOptions + opts.Protocol = HTTP2 + + ht, err := New(&opts) + require.NoError(t, err) + require.NotNil(t, ht) + t.Cleanup(func() { ht.Dialer.Close() }) + require.NotNil(t, ht.client) + require.NotSame(t, ht.client.HTTPClient, ht.client.HTTPClient2) + require.NotSame(t, ht.client.HTTPClient, ht.client2) + _, isHTTP2 := ht.client2.Transport.(*http2.Transport) + require.True(t, isHTTP2) +} From 58e14d4b57e4a491b192098c7464695aea39cb61 Mon Sep 17 00:00:00 2001 From: whoami Date: Sat, 21 Feb 2026 04:50:17 +0800 Subject: [PATCH 2/3] chore(httpx): use HTTP11 constant and restore GODEBUG in test --- common/httpx/httpx.go | 4 ++-- common/httpx/httpx_protocol_test.go | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/httpx/httpx.go b/common/httpx/httpx.go index dbd33805..df516149 100644 --- a/common/httpx/httpx.go +++ b/common/httpx/httpx.go @@ -153,7 +153,7 @@ func New(options *Options) (*HTTPX, error) { DisableKeepAlives: true, } - if httpx.Options.Protocol == "http11" { + if httpx.Options.Protocol == HTTP11 { // disable http2 _ = os.Setenv("GODEBUG", "http2client=0") transport.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{} @@ -185,7 +185,7 @@ func New(options *Options) (*HTTPX, error) { // honor explicit HTTP/1.1 mode by disabling retryablehttp-go's internal // HTTP/2 fallback client and HTTPX's own HTTP/2 probing client - if httpx.Options.Protocol == "http11" { + if httpx.Options.Protocol == HTTP11 { httpx.client.HTTPClient2 = httpx.client.HTTPClient httpx.client2 = httpx.client.HTTPClient } else { diff --git a/common/httpx/httpx_protocol_test.go b/common/httpx/httpx_protocol_test.go index 9c22ddee..23445d8e 100644 --- a/common/httpx/httpx_protocol_test.go +++ b/common/httpx/httpx_protocol_test.go @@ -1,6 +1,7 @@ package httpx import ( + "os" "testing" "golang.org/x/net/http2" @@ -12,6 +13,15 @@ func TestNew_HTTP11DisablesRetryableHTTP2Fallback(t *testing.T) { opts := DefaultOptions opts.Protocol = HTTP11 + originalGODEBUG, hadGODEBUG := os.LookupEnv("GODEBUG") + t.Cleanup(func() { + if hadGODEBUG { + _ = os.Setenv("GODEBUG", originalGODEBUG) + } else { + _ = os.Unsetenv("GODEBUG") + } + }) + ht, err := New(&opts) require.NoError(t, err) require.NotNil(t, ht) From 137a590337541873f2578b043aeaf63e80fef5c7 Mon Sep 17 00:00:00 2001 From: whoami Date: Sun, 22 Feb 2026 09:26:03 +0800 Subject: [PATCH 3/3] docs(test): add doc comments to protocol tests --- common/httpx/httpx_protocol_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/httpx/httpx_protocol_test.go b/common/httpx/httpx_protocol_test.go index 23445d8e..d7509b33 100644 --- a/common/httpx/httpx_protocol_test.go +++ b/common/httpx/httpx_protocol_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" ) +// TestNew_HTTP11DisablesRetryableHTTP2Fallback verifies that forcing HTTP/1.1 disables retryablehttp-go's HTTP/2 fallback and prevents HTTP/2 probing client creation. func TestNew_HTTP11DisablesRetryableHTTP2Fallback(t *testing.T) { opts := DefaultOptions opts.Protocol = HTTP11 @@ -33,6 +34,7 @@ func TestNew_HTTP11DisablesRetryableHTTP2Fallback(t *testing.T) { require.False(t, isHTTP2) } +// TestNew_NonHTTP11KeepsRetryableHTTP2FallbackClient verifies that non-HTTP/1.1 mode keeps a dedicated HTTP/2 client2 transport and allows retryable fallback behavior. func TestNew_NonHTTP11KeepsRetryableHTTP2FallbackClient(t *testing.T) { opts := DefaultOptions opts.Protocol = HTTP2