| // 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 | |
| package http | |
| import ( | |
| "context" | |
| "crypto/tls" | |
| "errors" | |
| "io" | |
| "log" | |
| "net" | |
| "net/http/internal/http2" | |
| "time" | |
| _ "unsafe" // for go:linkname | |
| ) | |
| // net/http supports HTTP/2 by default, but this support is removed when | |
| // the nethttpomithttp2 build tag is set. | |
| // | |
| // HTTP/2 support is provided by the net/http/internal/http2 package. | |
| // | |
| // This file (http2.go) connects net/http to the http2 package. | |
| // Since http imports http2, to avoid an import cycle we need to | |
| // translate http package types (e.g., Request) into the equivalent | |
| // http2 package types (e.g., http2.ClientRequest). | |
| // | |
| // The golang.org/x/net/http2 package is the original source of truth for | |
| // the HTTP/2 implementation. At this time, users may still import that | |
| // package and register its implementation on a net/http Transport or Server. | |
| // However, the x/net package is no longer synchronized with std. | |
| func init() { | |
| // NoBody and LocalAddrContextKey need to have the same value | |
| // in the http and http2 packages. | |
| // | |
| // We can't define these values in net/http/internal, | |
| // because their concrete types are part of the net/http API and | |
| // moving them causes API checker failures. | |
| // Override the http2 package versions at init time instead. | |
| http2.LocalAddrContextKey = LocalAddrContextKey | |
| http2.NoBody = NoBody | |
| } | |
| type http2Server = http2.Server | |
| type http2Transport = http2.Transport | |
| func (s *Server) configureHTTP2() { | |
| h2srv := &http2.Server{} | |
| // Historically, we've configured the HTTP/2 idle timeout in this fashion: | |
| // Set once at configuration time. | |
| if s.IdleTimeout != 0 { | |
| s.h2IdleTimeout = s.IdleTimeout | |
| } else { | |
| s.h2IdleTimeout = s.ReadTimeout | |
| } | |
| if s.TLSConfig == nil { | |
| s.TLSConfig = &tls.Config{} | |
| } | |
| s.nextProtoErr = h2srv.Configure(http2ServerConfig{s}, s.TLSConfig) | |
| if s.nextProtoErr != nil { | |
| return | |
| } | |
| s.RegisterOnShutdown(h2srv.GracefulShutdown) | |
| if s.TLSNextProto == nil { | |
| s.TLSNextProto = make(map[string]func(*Server, *tls.Conn, Handler)) | |
| } | |
| // 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. | |
| s.TLSNextProto["h2"] = func(hs *Server, c *tls.Conn, h Handler) { | |
| c.Close() | |
| } | |
| s.h2 = h2srv | |
| } | |
| func (s *Server) setHTTP2Config(conf http2ExternalServerConfig) { | |
| if s.h2Config != nil { | |
| panic("http: HTTP/2 Server already registered") | |
| } | |
| s.h2Config = conf | |
| s.h2Config.ServeConnFunc(s.serveHTTP2Conn) | |
| } | |
| func (s *Server) serveHTTP2Conn(ctx context.Context, nc net.Conn, h Handler, sawClientPreface bool, upgradeReq *Request, settings []byte) { | |
| s.setupHTTP2_ServeTLS() | |
| var serverUpgradeReq *http2.ServerRequest | |
| if upgradeReq != nil { | |
| serverUpgradeReq = http2ServerRequestFromRequest(upgradeReq) | |
| } | |
| s.h2.ServeConn(nc, &http2.ServeConnOpts{ | |
| Context: ctx, | |
| Handler: http2Handler{h}, | |
| BaseConfig: http2ServerConfig{s}, | |
| SawClientPreface: sawClientPreface, | |
| UpgradeRequest: serverUpgradeReq, | |
| Settings: settings, | |
| }) | |
| } | |
| func http2ServerRequestFromRequest(req *Request) *http2.ServerRequest { | |
| return &http2.ServerRequest{ | |
| Context: req.Context(), | |
| Proto: req.Proto, | |
| ProtoMajor: req.ProtoMajor, | |
| ProtoMinor: req.ProtoMinor, | |
| Method: req.Method, | |
| URL: req.URL, | |
| Header: http2.Header(req.Header), | |
| Trailer: http2.Header(req.Trailer), | |
| Body: req.Body, | |
| Host: req.Host, | |
| ContentLength: req.ContentLength, | |
| RemoteAddr: req.RemoteAddr, | |
| RequestURI: req.RequestURI, | |
| TLS: req.TLS, | |
| MultipartForm: req.MultipartForm, | |
| } | |
| } | |
| type http2Handler struct { | |
| h Handler | |
| } | |
| func (h http2Handler) ServeHTTP(w *http2.ResponseWriter, req *http2.ServerRequest) { | |
| h.h.ServeHTTP(http2ResponseWriter{w}, &Request{ | |
| ctx: req.Context, | |
| Proto: "HTTP/2.0", | |
| ProtoMajor: 2, | |
| ProtoMinor: 0, | |
| Method: req.Method, | |
| URL: req.URL, | |
| Header: Header(req.Header), | |
| RequestURI: req.RequestURI, | |
| Trailer: Header(req.Trailer), | |
| Body: req.Body, | |
| Host: req.Host, | |
| ContentLength: req.ContentLength, | |
| RemoteAddr: req.RemoteAddr, | |
| TLS: req.TLS, | |
| MultipartForm: req.MultipartForm, | |
| }) | |
| } | |
| type http2ResponseWriter struct { | |
| *http2.ResponseWriter | |
| } | |
| // Optional http.ResponseWriter interfaces implemented. | |
| var ( | |
| _ CloseNotifier = http2ResponseWriter{} | |
| _ Flusher = http2ResponseWriter{} | |
| _ io.StringWriter = http2ResponseWriter{} | |
| ) | |
| func (w http2ResponseWriter) Flush() { w.ResponseWriter.FlushError() } | |
| func (w http2ResponseWriter) FlushError() error { return w.ResponseWriter.FlushError() } | |
| func (w http2ResponseWriter) Header() Header { return Header(w.ResponseWriter.Header()) } | |
| func (w http2ResponseWriter) Push(target string, opts *PushOptions) error { | |
| var ( | |
| method string | |
| header http2.Header | |
| ) | |
| if opts != nil { | |
| method = opts.Method | |
| header = http2.Header(opts.Header) | |
| } | |
| err := w.ResponseWriter.Push(target, method, header) | |
| if err == http2.ErrNotSupported { | |
| err = ErrNotSupported | |
| } | |
| return err | |
| } | |
| type http2ServerConfig struct { | |
| s *Server | |
| } | |
| func (s http2ServerConfig) MaxHeaderBytes() int { return s.s.MaxHeaderBytes } | |
| func (s http2ServerConfig) ConnState(c net.Conn, st http2.ConnState) { | |
| if s.s.ConnState != nil { | |
| s.s.ConnState(c, ConnState(st)) | |
| } | |
| } | |
| func (s http2ServerConfig) DoKeepAlives() bool { return s.s.doKeepAlives() } | |
| func (s http2ServerConfig) WriteTimeout() time.Duration { return s.s.WriteTimeout } | |
| func (s http2ServerConfig) SendPingTimeout() time.Duration { return s.s.ReadTimeout } | |
| func (s http2ServerConfig) ErrorLog() *log.Logger { return s.s.ErrorLog } | |
| func (s http2ServerConfig) ReadTimeout() time.Duration { return s.s.ReadTimeout } | |
| func (s http2ServerConfig) DisableClientPriority() bool { return s.s.DisableClientPriority } | |
| func (s http2ServerConfig) IdleTimeout() time.Duration { | |
| if s.s.h2Config != nil { | |
| return s.s.h2Config.IdleTimeout() | |
| } | |
| return s.s.h2IdleTimeout | |
| } | |
| func (s http2ServerConfig) HTTP2Config() http2.Config { | |
| return mergeHTTP2Config(s.s.HTTP2, s.s.h2Config) | |
| } | |
| // http2ExternalServerConfig is an HTTP/2 configuration provided by x/net/http2. | |
| // | |
| // When a x/net/http2.Server wraps a net/http.Server, we need to support the user | |
| // setting configuration settings on the x/net Server: | |
| // | |
| // s1 := &http.Server{} | |
| // s2 := &http2.Server{} | |
| // http2.ConfigureServer(s1, s2) | |
| // | |
| // // This setting needs to affect s1: | |
| // s2.MaxReadFrameSize = 10000 | |
| // | |
| // We handle this by having http2.ConfigureServer pass us an http2ExternalServerConfig | |
| // (see http.Server.Serve) which we can use to query the current state of the http2.Server. | |
| type http2ExternalServerConfig interface { | |
| // Various configuration settings: | |
| HTTP2Config() HTTP2Config | |
| IdleTimeout() time.Duration | |
| // ServeConnFunc provides a function to the x/net/http2.Server which it | |
| // can use to serve a new connection. | |
| ServeConnFunc(func(ctx context.Context, nc net.Conn, h Handler, sawClientPreface bool, upgradeReq *Request, settings []byte)) | |
| } | |
| // 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 | |
| } | |
| type http2Configer interface { | |
| HTTP2Config() HTTP2Config | |
| } | |
| func mergeHTTP2Config(c1 *HTTP2Config, confer http2Configer) http2.Config { | |
| if c1 == nil && confer == nil { | |
| return http2.Config{} | |
| } | |
| var c http2.Config | |
| if c1 != nil { | |
| c = (http2.Config)(*c1) | |
| } | |
| var c2 HTTP2Config | |
| if confer != nil { | |
| c2 = confer.HTTP2Config() | |
| } | |
| if c.MaxConcurrentStreams == 0 { | |
| c.MaxConcurrentStreams = c2.MaxConcurrentStreams | |
| } | |
| if c2.StrictMaxConcurrentRequests { | |
| c.StrictMaxConcurrentRequests = true | |
| } | |
| if c.MaxDecoderHeaderTableSize == 0 { | |
| c.MaxDecoderHeaderTableSize = c2.MaxDecoderHeaderTableSize | |
| } | |
| if c.MaxEncoderHeaderTableSize == 0 { | |
| c.MaxEncoderHeaderTableSize = c2.MaxEncoderHeaderTableSize | |
| } | |
| if c.MaxReadFrameSize == 0 { | |
| c.MaxReadFrameSize = c2.MaxReadFrameSize | |
| } | |
| if c.MaxReceiveBufferPerConnection == 0 { | |
| c.MaxReceiveBufferPerConnection = c2.MaxReceiveBufferPerConnection | |
| } | |
| if c.MaxReceiveBufferPerStream == 0 { | |
| c.MaxReceiveBufferPerStream = c2.MaxReceiveBufferPerStream | |
| } | |
| if c.SendPingTimeout == 0 { | |
| c.SendPingTimeout = c2.SendPingTimeout | |
| } | |
| if c.PingTimeout == 0 { | |
| c.PingTimeout = c2.PingTimeout | |
| } | |
| if c.WriteByteTimeout == 0 { | |
| c.WriteByteTimeout = c2.WriteByteTimeout | |
| } | |
| if c2.PermitProhibitedCipherSuites { | |
| c.PermitProhibitedCipherSuites = true | |
| } | |
| if c.CountError == nil { | |
| c.CountError = c2.CountError | |
| } | |
| return c | |
| } | |
| 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 | |
| } |