blob: 78466cb9a0e37e9b68755d046351b6e1b1d0a64e [file]
// 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
}