diff --git a/common/httpx/httpx.go b/common/httpx/httpx.go index 039f4c4c..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{} @@ -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..d7509b33 --- /dev/null +++ b/common/httpx/httpx_protocol_test.go @@ -0,0 +1,51 @@ +package httpx + +import ( + "os" + "testing" + + "golang.org/x/net/http2" + + "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 + + 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) + 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) +} + +// 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 + + 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) +}