| // 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 |
| |
| import ( |
| "context" |
| "crypto/tls" |
| "errors" |
| "fmt" |
| "log" |
| mathrand "math/rand" |
| "net" |
| "net/http" |
| "strings" |
| "sync/atomic" |
| "time" |
| |
| "golang.org/x/net/idna" |
| ) |
| |
| // ConfigureTransport configures a net/http HTTP/1 Transport to use HTTP/2. |
| // It returns an error if t1 has already been HTTP/2-enabled. |
| // |
| // Use ConfigureTransports instead to configure the HTTP/2 Transport. |
| func ConfigureTransport(t1 *http.Transport) error { |
| return configureTransport(t1) |
| } |
| |
| // ConfigureTransports configures a net/http HTTP/1 Transport to use HTTP/2. |
| // It returns a new HTTP/2 Transport for further configuration. |
| // It returns an error if t1 has already been HTTP/2-enabled. |
| func ConfigureTransports(t1 *http.Transport) (*Transport, error) { |
| return configureTransports(t1) |
| } |
| |
| // Transport is an HTTP/2 Transport. |
| // |
| // A Transport internally caches connections to servers. It is safe |
| // for concurrent use by multiple goroutines. |
| type Transport struct { |
| // DialTLSContext specifies an optional dial function with context for |
| // creating TLS connections for requests. |
| // |
| // If DialTLSContext and DialTLS is nil, tls.Dial is used. |
| // |
| // If the returned net.Conn has a ConnectionState method like tls.Conn, |
| // it will be used to set http.Response.TLS. |
| DialTLSContext func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) |
| |
| // DialTLS specifies an optional dial function for creating |
| // TLS connections for requests. |
| // |
| // If DialTLSContext and DialTLS is nil, tls.Dial is used. |
| // |
| // Deprecated: Use DialTLSContext instead, which allows the transport |
| // to cancel dials as soon as they are no longer needed. |
| // If both are set, DialTLSContext takes priority. |
| DialTLS func(network, addr string, cfg *tls.Config) (net.Conn, error) |
| |
| // TLSClientConfig specifies the TLS configuration to use with |
| // tls.Client. If nil, the default configuration is used. |
| TLSClientConfig *tls.Config |
| |
| // ConnPool optionally specifies an alternate connection pool to use. |
| // If nil, the default is used. |
| ConnPool ClientConnPool |
| |
| // DisableCompression, if true, prevents the Transport from |
| // requesting compression with an "Accept-Encoding: gzip" |
| // request header when the Request contains no existing |
| // Accept-Encoding value. If the Transport requests gzip on |
| // its own and gets a gzipped response, it's transparently |
| // decoded in the Response.Body. However, if the user |
| // explicitly requested gzip it is not automatically |
| // uncompressed. |
| DisableCompression bool |
| |
| // AllowHTTP, if true, permits HTTP/2 requests using the insecure, |
| // plain-text "http" scheme. Note that this does not enable h2c support. |
| AllowHTTP bool |
| |
| // MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to |
| // send in the initial settings frame. It is how many bytes |
| // of response headers are allowed. Unlike the http2 spec, zero here |
| // means to use a default limit (currently 10MB). If you actually |
| // want to advertise an unlimited value to the peer, Transport |
| // interprets the highest possible value here (0xffffffff or 1<<32-1) |
| // to mean no limit. |
| MaxHeaderListSize uint32 |
| |
| // MaxReadFrameSize is the http2 SETTINGS_MAX_FRAME_SIZE to send in the |
| // initial settings frame. It is the size in bytes of the largest frame |
| // payload that the sender is willing to receive. If 0, no setting is |
| // sent, and the value is provided by the peer, which should be 16384 |
| // according to the spec: |
| // https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2. |
| // Values are bounded in the range 16k to 16M. |
| MaxReadFrameSize uint32 |
| |
| // MaxDecoderHeaderTableSize optionally specifies the http2 |
| // SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It |
| // informs the remote endpoint of the maximum size of the header compression |
| // table used to decode header blocks, in octets. If zero, the default value |
| // of 4096 is used. |
| MaxDecoderHeaderTableSize uint32 |
| |
| // MaxEncoderHeaderTableSize optionally specifies an upper limit for the |
| // header compression table used for encoding request headers. Received |
| // SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero, |
| // the default value of 4096 is used. |
| MaxEncoderHeaderTableSize uint32 |
| |
| // StrictMaxConcurrentStreams controls whether the server's |
| // SETTINGS_MAX_CONCURRENT_STREAMS should be respected |
| // globally. If false, new TCP connections are created to the |
| // server as needed to keep each under the per-connection |
| // SETTINGS_MAX_CONCURRENT_STREAMS limit. If true, the |
| // server's SETTINGS_MAX_CONCURRENT_STREAMS is interpreted as |
| // a global limit and callers of RoundTrip block when needed, |
| // waiting for their turn. |
| StrictMaxConcurrentStreams bool |
| |
| // IdleConnTimeout is the maximum amount of time an idle |
| // (keep-alive) connection will remain idle before closing |
| // itself. |
| // Zero means no limit. |
| IdleConnTimeout time.Duration |
| |
| // ReadIdleTimeout is the timeout after which a health check using ping |
| // frame will be carried out if no frame is received on the connection. |
| // Note that a ping response will is considered a received frame, so if |
| // there is no other traffic on the connection, the health check will |
| // be performed every ReadIdleTimeout interval. |
| // If zero, no health check is performed. |
| ReadIdleTimeout time.Duration |
| |
| // PingTimeout is the timeout after which the connection will be closed |
| // if a response to Ping is not received. |
| // Defaults to 15s. |
| PingTimeout time.Duration |
| |
| // WriteByteTimeout is the timeout after which the connection will be |
| // closed no data can be written to it. The timeout begins when data is |
| // available to write, and is extended whenever any bytes are written. |
| WriteByteTimeout time.Duration |
| |
| // CountError, if non-nil, is called on HTTP/2 transport errors. |
| // It's intended to increment a metric for monitoring, such |
| // as an expvar or Prometheus metric. |
| // The errType consists of only ASCII word characters. |
| CountError func(errType string) |
| |
| // Internal state, differs between wrapped and non-wrapped implementations. |
| transportInternal |
| } |
| |
| var ( |
| errClientConnClosed = errors.New("http2: client conn is closed") |
| errClientConnNotEstablished = errors.New("http2: client conn could not be established") |
| errClientConnGotGoAway = errors.New("http2: Transport received Server's graceful shutdown GOAWAY") |
| errClientConnForceClosed = errors.New("http2: client connection force closed via ClientConn.Close") |
| errClientConnUnusable = errors.New("http2: client conn not usable") |
| ) |
| |
| // ClientConnPool manages a pool of HTTP/2 client connections. |
| type ClientConnPool interface { |
| // GetClientConn returns a specific HTTP/2 connection (usually |
| // a TLS-TCP connection) to an HTTP/2 server. On success, the |
| // returned ClientConn accounts for the upcoming RoundTrip |
| // call, so the caller should not omit it. If the caller needs |
| // to, ClientConn.RoundTrip can be called with a bogus |
| // new(http.Request) to release the stream reservation. |
| GetClientConn(req *http.Request, addr string) (*ClientConn, error) |
| MarkDead(*ClientConn) |
| } |
| |
| // ClientConnState describes the state of a ClientConn. |
| type ClientConnState struct { |
| // Closed is whether the connection is closed. |
| Closed bool |
| |
| // Closing is whether the connection is in the process of |
| // closing. It may be closing due to shutdown, being a |
| // single-use connection, being marked as DoNotReuse, or |
| // having received a GOAWAY frame. |
| Closing bool |
| |
| // StreamsActive is how many streams are active. |
| StreamsActive int |
| |
| // StreamsReserved is how many streams have been reserved via |
| // ClientConn.ReserveNewRequest. |
| StreamsReserved int |
| |
| // StreamsPending is how many requests have been sent in excess |
| // of the peer's advertised MaxConcurrentStreams setting and |
| // are waiting for other streams to complete. |
| StreamsPending int |
| |
| // MaxConcurrentStreams is how many concurrent streams the |
| // peer advertised as acceptable. Zero means no SETTINGS |
| // frame has been received yet. |
| MaxConcurrentStreams uint32 |
| |
| // LastIdle, if non-zero, is when the connection last |
| // transitioned to idle state. |
| LastIdle time.Time |
| } |
| |
| // RoundTripOpt are options for the Transport.RoundTripOpt method. |
| type RoundTripOpt struct { |
| // OnlyCachedConn controls whether RoundTripOpt may |
| // create a new TCP connection. If set true and |
| // no cached connection is available, RoundTripOpt |
| // will return ErrNoCachedConn. |
| |
| // OnlyCachedConn was broken in https://go.dev/cl/16699. |
| OnlyCachedConn bool |
| |
| allowHTTP bool // allow http:// URLs |
| } |
| |
| func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { |
| return t.RoundTripOpt(req, RoundTripOpt{}) |
| } |
| |
| // RoundTripOpt is like RoundTrip, but takes options. |
| func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { |
| return t.roundTripOpt(req, opt) |
| } |
| |
| // CloseIdleConnections closes any connections which were previously |
| // connected from previous requests but are now sitting idle. |
| // It does not interrupt any connections currently in use. |
| func (t *Transport) CloseIdleConnections() { |
| t.closeIdleConnections() |
| } |
| |
| func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) { |
| return t.newUserClientConn(c) |
| } |
| |
| // authorityAddr returns a given authority (a host/IP, or host:port / ip:port) |
| // and returns a host:port. The port 443 is added if needed. |
| func authorityAddr(scheme string, authority string) (addr string) { |
| host, port, err := net.SplitHostPort(authority) |
| if err != nil { // authority didn't have a port |
| host = authority |
| port = "" |
| } |
| if port == "" { // authority's port was empty |
| port = "443" |
| if scheme == "http" { |
| port = "80" |
| } |
| } |
| if a, err := idna.ToASCII(host); err == nil { |
| host = a |
| } |
| // IPv6 address literal, without a port: |
| if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { |
| return host + ":" + port |
| } |
| return net.JoinHostPort(host, port) |
| } |
| |
| func (t *Transport) roundTripViaPool(req *http.Request, opt RoundTripOpt, pool ClientConnPool) (*http.Response, error) { |
| addr := authorityAddr(req.URL.Scheme, req.URL.Host) |
| for retry := 0; ; retry++ { |
| cc, err := pool.GetClientConn(req, addr) |
| if err != nil { |
| t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err) |
| return nil, err |
| } |
| reused := !atomic.CompareAndSwapUint32(&cc.atomicReused, 0, 1) |
| traceGotConn(req, cc, reused) |
| res, err := cc.RoundTrip(req) |
| if err != nil && retry <= 6 { |
| roundTripErr := err |
| if req, err = shouldRetryRequest(req, err); err == nil { |
| // After the first retry, do exponential backoff with 10% jitter. |
| if retry == 0 { |
| t.vlogf("RoundTrip retrying after failure: %v", roundTripErr) |
| continue |
| } |
| backoff := float64(uint(1) << (uint(retry) - 1)) |
| backoff += backoff * (0.1 * mathrand.Float64()) |
| d := time.Second * time.Duration(backoff) |
| tm := time.NewTimer(d) |
| select { |
| case <-tm.C: |
| t.vlogf("RoundTrip retrying after failure: %v", roundTripErr) |
| continue |
| case <-req.Context().Done(): |
| tm.Stop() |
| err = req.Context().Err() |
| } |
| } |
| } |
| if err == errClientConnNotEstablished { |
| // This ClientConn was created recently, |
| // this is the first request to use it, |
| // and the connection is closed and not usable. |
| // |
| // In this state, cc.idleTimer will remove the conn from the pool |
| // when it fires. Stop the timer and remove it here so future requests |
| // won't try to use this connection. |
| // |
| // If the timer has already fired and we're racing it, the redundant |
| // call to MarkDead is harmless. |
| cc.stopIdleTimer() |
| pool.MarkDead(cc) |
| } |
| if err != nil { |
| t.vlogf("RoundTrip failure: %v", err) |
| return nil, err |
| } |
| return res, nil |
| } |
| } |
| |
| // shouldRetryRequest is called by RoundTrip when a request fails to get |
| // response headers. It is always called with a non-nil error. |
| // It returns either a request to retry (either the same request, or a |
| // modified clone), or an error if the request can't be replayed. |
| func shouldRetryRequest(req *http.Request, err error) (*http.Request, error) { |
| if !canRetryError(err) { |
| return nil, err |
| } |
| // If the Body is nil (or http.NoBody), it's safe to reuse |
| // this request and its Body. |
| if req.Body == nil || req.Body == http.NoBody { |
| return req, nil |
| } |
| |
| // If the request body can be reset back to its original |
| // state via the optional req.GetBody, do that. |
| if req.GetBody != nil { |
| body, err := req.GetBody() |
| if err != nil { |
| return nil, err |
| } |
| newReq := *req |
| newReq.Body = body |
| return &newReq, nil |
| } |
| |
| // The Request.Body can't reset back to the beginning, but we |
| // don't seem to have started to read from it yet, so reuse |
| // the request directly. |
| if err == errClientConnUnusable { |
| return req, nil |
| } |
| |
| return nil, fmt.Errorf("http2: Transport: cannot retry err [%v] after Request.Body was written; define Request.GetBody to avoid this error", err) |
| } |
| |
| func canRetryError(err error) bool { |
| if err == errClientConnUnusable || err == errClientConnGotGoAway { |
| return true |
| } |
| if se, ok := err.(StreamError); ok { |
| return se.Code == ErrCodeRefusedStream |
| } |
| return false |
| } |
| |
| func (t *Transport) vlogf(format string, args ...interface{}) { |
| if VerboseLogs { |
| t.logf(format, args...) |
| } |
| } |
| |
| func (t *Transport) logf(format string, args ...interface{}) { |
| log.Printf(format, args...) |
| } |
| |
| func (t *Transport) dialTLS(ctx context.Context, network, addr string, tlsCfg *tls.Config) (net.Conn, error) { |
| if t.DialTLSContext != nil { |
| return t.DialTLSContext(ctx, network, addr, tlsCfg) |
| } else if t.DialTLS != nil { |
| return t.DialTLS(network, addr, tlsCfg) |
| } |
| |
| tlsCn, err := t.dialTLSWithContext(ctx, network, addr, tlsCfg) |
| if err != nil { |
| return nil, err |
| } |
| state := tlsCn.ConnectionState() |
| if p := state.NegotiatedProtocol; p != NextProtoTLS { |
| return nil, fmt.Errorf("http2: unexpected ALPN protocol %q; want %q", p, NextProtoTLS) |
| } |
| if !state.NegotiatedProtocolIsMutual { |
| return nil, errors.New("http2: could not negotiate protocol mutually") |
| } |
| return tlsCn, nil |
| } |
| |
| // dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS |
| // connection. |
| func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) { |
| dialer := &tls.Dialer{ |
| Config: cfg, |
| } |
| cn, err := dialer.DialContext(ctx, network, addr) |
| if err != nil { |
| return nil, err |
| } |
| tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed |
| return tlsCn, nil |
| } |
| |
| // GoAwayError is returned by the Transport when the server closes the |
| // TCP connection after sending a GOAWAY frame. |
| type GoAwayError struct { |
| LastStreamID uint32 |
| ErrCode ErrCode |
| DebugData string |
| } |
| |
| func (e GoAwayError) Error() string { |
| return fmt.Sprintf("http2: server sent GOAWAY and closed the connection; LastStreamID=%v, ErrCode=%v, debug=%q", |
| e.LastStreamID, e.ErrCode, e.DebugData) |
| } |
| |
| // noCachedConnError is the concrete type of ErrNoCachedConn, which |
| // needs to be detected by net/http regardless of whether it's its |
| // bundled version (in h2_bundle.go with a rewritten type name) or |
| // from a user's x/net/http2. As such, as it has a unique method name |
| // (IsHTTP2NoCachedConnError) that net/http sniffs for via func |
| // isNoCachedConnError. |
| type noCachedConnError struct{} |
| |
| func (noCachedConnError) IsHTTP2NoCachedConnError() {} |
| func (noCachedConnError) Error() string { return "http2: no cached connection was available" } |
| |
| // isNoCachedConnError reports whether err is of type noCachedConnError |
| // or its equivalent renamed type in net/http2's h2_bundle.go. Both types |
| // may coexist in the same running program. |
| func isNoCachedConnError(err error) bool { |
| _, ok := err.(interface{ IsHTTP2NoCachedConnError() }) |
| return ok |
| } |
| |
| var ErrNoCachedConn error = noCachedConnError{} |