http2: enable HTTP/2 in wrap configureTransport The go1.27 wrap implementation of ConfigureTransport is a no-op, and ConfigureTransports only registers the protocol with net/http; neither enables HTTP/2 on the transport or initializes TLSClientConfig. The pre-wrapping transport.go did both. As a result, callers that enable HTTP/2 on a transport with a custom TLSClientConfig or dialer (where net/http does not enable HTTP/2 automatically) send requests over HTTP/1 to an HTTP/2 server. Without "h2" in NextProtos this is a silent downgrade to HTTP/1.1; with it, the connection negotiates h2 at the TLS layer but is read by the HTTP/1 transport, failing with "net/http: HTTP/1.x transport connection broken: malformed HTTP response". This is the client-side counterpart of the server-side fix in CL 782940. configureTransport now delegates to configureTransports, which ensures a non-nil TLSClientConfig and enables net/http's native HTTP/2 via Transport.Protocols. TestConfigureTransport is gated to the legacy build (!(go1.27 && !http2legacy)) and does not compile into the wrapper build, which is why this regressed silently. The new transport_configure_test.go is untagged and exercises both ConfigureTransport and ConfigureTransports under the legacy and wrap builds. Fixes golang/go#79778 Change-Id: Ibc27bced8679fcb3baa7953bad12315da07535fb Reviewed-on: https://go-review.googlesource.com/c/net/+/785900 Reviewed-by: Nicholas Husin <husin@google.com> Reviewed-by: Damien Neil <dneil@google.com> Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Nicholas Husin <nsh@golang.org>
diff --git a/http2/transport_configure_test.go b/http2/transport_configure_test.go new file mode 100644 index 0000000..83993c6 --- /dev/null +++ b/http2/transport_configure_test.go
@@ -0,0 +1,63 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http2_test + +import ( + "crypto/tls" + "io" + "net/http" + "net/http/httptest" + "testing" + + . "golang.org/x/net/http2" +) + +// TestConfigureTransportEnablesHTTP2 verifies that ConfigureTransport and +// ConfigureTransports enable HTTP/2 on a transport with a custom TLSClientConfig. +// net/http does not auto-enable HTTP/2 for such a transport, so without +// ConfigureTransport doing so the request is sent over HTTP/1 to an HTTP/2 +// server. The test is intentionally not build-tagged: the behavior must hold for +// the legacy implementation (transport.go) and the go1.27 net/http wrapper +// (transport_wrap.go) alike. +func TestConfigureTransportEnablesHTTP2(t *testing.T) { + for _, tc := range []struct { + name string + configure func(*http.Transport) error + }{ + {"ConfigureTransport", ConfigureTransport}, + {"ConfigureTransports", func(t1 *http.Transport) error { + _, err := ConfigureTransports(t1) + return err + }}, + } { + t.Run(tc.name, func(t *testing.T) { + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, r.Proto) + })) + if err := ConfigureServer(ts.Config, &Server{}); err != nil { + t.Fatal(err) + } + ts.TLS = ts.Config.TLSConfig + ts.StartTLS() + defer ts.Close() + + // A custom TLSClientConfig disables net/http's automatic HTTP/2; + // ConfigureTransport is responsible for re-enabling it. + tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + if err := tc.configure(tr); err != nil { + t.Fatal(err) + } + res, err := (&http.Client{Transport: tr}).Get(ts.URL) + if err != nil { + t.Fatalf("Get: %v", err) + } + defer res.Body.Close() + body, _ := io.ReadAll(res.Body) + if res.ProtoMajor != 2 { + t.Errorf("request negotiated %s (server saw %q); want HTTP/2", res.Proto, body) + } + }) + } +}
diff --git a/http2/transport_wrap.go b/http2/transport_wrap.go index d25d99b..eab2e6b 100644 --- a/http2/transport_wrap.go +++ b/http2/transport_wrap.go
@@ -22,8 +22,8 @@ ) func configureTransport(t1 *http.Transport) error { - // ConfigureTransport is a no-op: The http.Transport already supports HTTP/2. - return nil + _, err := configureTransports(t1) + return err } func configureTransports(t1 *http.Transport) (*Transport, error) { @@ -31,6 +31,17 @@ // linked to the http.Transport's. tr2 := &Transport{} tr2.configure(t1) + // Enable HTTP/2 on the transport, as the pre-wrapping implementation did: + // net/http does not auto-enable it for a transport with a custom + // TLSClientConfig or dialer. + if t1.TLSClientConfig == nil { + t1.TLSClientConfig = &tls.Config{} + } + if t1.Protocols == nil { + t1.Protocols = new(http.Protocols) + t1.Protocols.SetHTTP1(true) + } + t1.Protocols.SetHTTP2(true) return tr2, nil }