| // 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. |
| |
| //go:build !nethttpomithttp2 && !nethttpomithttp2client |
| |
| package http |
| |
| import ( |
| "context" |
| "crypto/tls" |
| "errors" |
| "net" |
| "net/http/internal/http2" |
| "time" |
| |
| _ "unsafe" // for go:linkname |
| ) |
| |
| // This file connects the net/http Transport to the HTTP/2 client |
| // in the net/http/internal/http2 package. It is omitted when |
| // the nethttpomithttp2 or nethttpomithttp2client build tag is set. |
| |
| const omitHTTP2Client = false |
| |
| type http2Transport = http2.Transport |
| |
| // http2ExternalTransportConfig is an HTTP/2 configuration provided by x/net/http2. |
| // |
| // When a x/net/http2.Transport wraps a net/http.Transport, we need to support the user |
| // setting configuration settings on the x/net Transport: |
| // |
| // tr1 := &http.Transport{} |
| // tr2 := http2.ConfigureTransports(t1) |
| // |
| // // This setting needs to affect tr1: |
| // tr2.MaxHeaderListSize = 10000 |
| // |
| // We handle this by having http2.ConfigureTransports pass us an http2ExternalTransportConfig, |
| // which we can use to query the current state of the http2.Transport. |
| type http2ExternalTransportConfig interface { |
| // Various configuration settings: |
| HTTP2Config() HTTP2Config |
| DisableCompression() bool |
| MaxHeaderListSize() int64 |
| IdleConnTimeout() time.Duration |
| |
| // ConnFromContext is used to pass a net.Conn to Transport.NewClientConn |
| // via a context value. See Transport.http2NewClientConnFromContext. |
| ConnFromContext(context.Context) net.Conn |
| |
| // DialFromContext is used to dial new connections, overriding Transport.DialContext etc. |
| // This is used when the user calls x/net/http2.Transport.RoundTrip directly, |
| // in which case the historical behavior is to use the http2.Transport's dial functions. |
| DialFromContext(ctx context.Context, network, addr string) (net.Conn, error) |
| |
| // ExternalRoundTrip reports whether Transport.RoundTrip should call the |
| // external transport's RoundTrip. This is used when x/net/http2.Transport.ConnPool |
| // is set, in which case the user-provided ClientConnPool has taken responsibility |
| // for picking a connection to use. |
| ExternalRoundTrip() bool |
| |
| // RoundTrip performs a round trip. |
| // It should only be used when ExternalRoundTrip requests it. |
| RoundTrip(*Request) (*Response, error) |
| |
| // Registered is called to report successful registration of the config. |
| Registered(*Transport) |
| } |
| |
| func (t *Transport) configureHTTP2(protocols Protocols) { |
| if t.TLSClientConfig == nil { |
| t.TLSClientConfig = &tls.Config{} |
| } |
| if t.HTTP2 == nil { |
| t.HTTP2 = &HTTP2Config{} |
| } |
| t2 := http2.NewTransport(transportConfig{t}) |
| t.h2Transport = t2 |
| |
| t.registerProtocol("https", http2RoundTripper{t2, true}) |
| if t.TLSNextProto == nil { |
| t.TLSNextProto = make(map[string]func(authority string, c *tls.Conn) RoundTripper) |
| } |
| // Historically, the presence of a TLSNextProto["h2"] key has been the signal to |
| // enable/disable HTTP/2 support. Set a value in the map, but we'll never use it. |
| t.TLSNextProto["h2"] = func(authority string, c *tls.Conn) RoundTripper { |
| return http2ErringRoundTripper{ |
| errors.New("unexpected use of stub RoundTripper"), |
| } |
| } |
| |
| // Server.ServeTLS clones the tls.Config before modifying it. |
| // Transport doesn't. We may want to make the two consistent some day. |
| // |
| // http2configureTransport will have already set NextProtos, but adjust it again |
| // here to remove HTTP/1.1 if the user has disabled it. |
| t.TLSClientConfig.NextProtos = adjustNextProtos(t.TLSClientConfig.NextProtos, protocols) |
| } |
| |
| type http2ErringRoundTripper struct{ err error } |
| |
| func (rt http2ErringRoundTripper) RoundTripErr() error { return rt.err } |
| func (rt http2ErringRoundTripper) RoundTrip(*Request) (*Response, error) { return nil, rt.err } |
| |
| func http2RoundTrip(req *Request, rt func(*http2.ClientRequest) (*http2.ClientResponse, error)) (*Response, error) { |
| resp := &Response{} |
| cresp, err := rt(&http2.ClientRequest{ |
| Context: req.Context(), |
| Method: req.Method, |
| URL: req.URL, |
| Header: http2.Header(req.Header), |
| Trailer: http2.Header(req.Trailer), |
| Body: req.Body, |
| Host: req.Host, |
| GetBody: req.GetBody, |
| ContentLength: req.ContentLength, |
| Cancel: req.Cancel, |
| Close: req.Close, |
| ResTrailer: (*http2.Header)(&resp.Trailer), |
| }) |
| if err != nil { |
| return nil, err |
| } |
| resp.Status = cresp.Status + " " + StatusText(cresp.StatusCode) |
| resp.StatusCode = cresp.StatusCode |
| resp.Proto = "HTTP/2.0" |
| resp.ProtoMajor = 2 |
| resp.ProtoMinor = 0 |
| resp.ContentLength = cresp.ContentLength |
| resp.Uncompressed = cresp.Uncompressed |
| resp.Header = Header(cresp.Header) |
| resp.Trailer = Header(cresp.Trailer) |
| resp.Body = cresp.Body |
| resp.TLS = cresp.TLS |
| resp.Request = req |
| return resp, nil |
| } |
| |
| // http2AddConn adds nc to the HTTP/2 connection pool. |
| func (t *Transport) http2AddConn(scheme, authority string, nc net.Conn) (RoundTripper, error) { |
| if t.h2Transport == nil { |
| return nil, errors.ErrUnsupported |
| } |
| err := t.h2Transport.AddConn(scheme, authority, nc) |
| if err != nil { |
| return nil, err |
| } |
| return http2RoundTripper{t.h2Transport, false}, nil |
| } |
| |
| // http2NewClientConn creates an HTTP/2 genericClientConn (used to implement ClientConn) from nc. |
| // The connection is not added to the HTTP/2 connection pool. |
| func (t *Transport) http2NewClientConn(nc net.Conn, internalStateHook func()) (genericClientConn, error) { |
| if t.h2Transport == nil { |
| return nil, errors.ErrUnsupported |
| } |
| cc, err := t.h2Transport.NewClientConn(nc, internalStateHook) |
| if err != nil { |
| return nil, err |
| } |
| return http2ClientConn{cc}, nil |
| } |
| |
| // http2NewClientConnFromContext creates a *ClientConn from a net.Conn. |
| // |
| // Transport.NewClientConn takes an address and dials a new net.Conn. |
| // We don't currently provide a simple way for the user to provide a net.Conn and get a |
| // *ClientConn out of it (although we do let the user provide their own Transport.DialContext, |
| // which can be used to effectively do this). |
| // |
| // x/net/http2.Transport.NewClientConn, in contrast, requires the user to provide a net.Conn. |
| // To support implementing the x/net/http2 NewClientConn in terms of a net/http.Transport, |
| // we permit x/net/http2 to pass us a net.Conn via a context key. |
| // |
| // http2NewClientConnFromContext handles extracting the net.Conn from the Context |
| // (when present) and creating a *ClientConn from it. |
| func (t *Transport) http2NewClientConnFromContext(ctx context.Context) (*ClientConn, error) { |
| if t.h2Config == nil { |
| return nil, errors.ErrUnsupported |
| } |
| nc := t.h2Config.ConnFromContext(ctx) |
| if nc == nil { |
| return nil, errors.ErrUnsupported |
| } |
| if t.h2Transport == nil { |
| return nil, errors.New("http: Transport does not support HTTP/2") |
| } |
| cc := &ClientConn{} |
| gc, err := t.http2NewClientConn(nc, cc.maybeRunStateHook) |
| if err != nil { |
| return nil, err |
| } |
| cc.stateHookMu.Lock() |
| defer cc.stateHookMu.Unlock() |
| cc.cc = gc |
| cc.lastAvailable = gc.Available() |
| return cc, nil |
| } |
| |
| // http2ExternalDial creates a new HTTP/2 connection, |
| // using the x/net/http2.Transport's dial functions. |
| // |
| // This is used when the user has called x/net/http2.Transport.RoundTrip. |
| // If the RoundTrip needs to create a new connection, |
| // the historical behavior is for it to use the http2.Transport's DialTLS or DialTLSContext |
| // functions, and not any dial functions on the http.Transport. |
| func (t *Transport) http2ExternalDial(ctx context.Context, cm connectMethod) (RoundTripper, error) { |
| if t.h2Config == nil { |
| return nil, errors.ErrUnsupported |
| } |
| nc, err := t.h2Config.DialFromContext(ctx, "tcp", cm.targetAddr) |
| if err != nil { |
| return nil, err |
| } |
| return t.http2AddConn(cm.targetScheme, cm.targetAddr, nc) |
| } |
| |
| type http2RoundTripper struct { |
| t *http2.Transport |
| mapCachedConnErr bool |
| } |
| |
| func (rt http2RoundTripper) RoundTrip(req *Request) (*Response, error) { |
| resp, err := http2RoundTrip(req, rt.t.RoundTrip) |
| if err != nil { |
| if rt.mapCachedConnErr && http2isNoCachedConnError(err) { |
| err = ErrSkipAltProtocol |
| } |
| return nil, err |
| } |
| return resp, nil |
| } |
| |
| type http2ClientConn struct { |
| http2.NetHTTPClientConn |
| } |
| |
| func (cc http2ClientConn) RoundTrip(req *Request) (*Response, error) { |
| return http2RoundTrip(req, cc.NetHTTPClientConn.RoundTrip) |
| } |
| |
| // transportConfig implements the http2.TransportConfig interface, |
| // providing the net/http Transport's configuration to the HTTP/2 implementation. |
| // |
| // When an x/net/http2 Transport has provided a configuration (see http2ExternalTransportConfig), |
| // the transportConfig merges the x/net/http2 and net/http Transport configurations. |
| type transportConfig struct { |
| t *Transport |
| } |
| |
| func (t transportConfig) MaxResponseHeaderBytes() int64 { return t.t.MaxResponseHeaderBytes } |
| func (t transportConfig) DisableKeepAlives() bool { return t.t.DisableKeepAlives } |
| func (t transportConfig) ExpectContinueTimeout() time.Duration { return t.t.ExpectContinueTimeout } |
| func (t transportConfig) ResponseHeaderTimeout() time.Duration { return t.t.ResponseHeaderTimeout } |
| |
| func (t transportConfig) MaxHeaderListSize() int64 { |
| if t.t.h2Config != nil { |
| return t.t.h2Config.MaxHeaderListSize() |
| } |
| return 0 |
| } |
| |
| func (t transportConfig) DisableCompression() bool { |
| if t.t.h2Config != nil && t.t.h2Config.DisableCompression() { |
| return true |
| } |
| return t.t.DisableCompression |
| } |
| |
| func (t transportConfig) IdleConnTimeout() time.Duration { |
| // Unlike most config settings, historically IdleConnTimeout prefers the |
| // http2.Transport's setting over the http.Transport. |
| if t.t.h2Config != nil { |
| if timeout := t.t.h2Config.IdleConnTimeout(); timeout != 0 { |
| return timeout |
| } |
| } |
| return t.t.IdleConnTimeout |
| } |
| |
| func (t transportConfig) HTTP2Config() http2.Config { |
| return mergeHTTP2Config(t.t.HTTP2, t.t.h2Config) |
| } |
| |
| // transportFromH1Transport provides a way for HTTP/2 tests to extract |
| // the http2.Transport from an http.Transport. |
| // |
| //go:linkname transportFromH1Transport net/http/internal/http2_test.transportFromH1Transport |
| func transportFromH1Transport(t *Transport) any { |
| t.nextProtoOnce.Do(t.onceSetNextProtoDefaults) |
| return t.h2Transport |
| } |