| // 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 |
| |
| 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 { |
| h2srv.IdleTimeout = s.IdleTimeout |
| } else { |
| h2srv.IdleTimeout = 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)) |
| } |
| type baseContexter interface { |
| BaseContext() context.Context |
| } |
| s.TLSNextProto["h2"] = func(hs *Server, c *tls.Conn, h Handler) { |
| h2srv.ServeConn(c, &http2.ServeConnOpts{ |
| Context: h.(baseContexter).BaseContext(), |
| Handler: http2Handler{h}, |
| BaseConfig: http2ServerConfig{hs}, |
| }) |
| } |
| s.TLSNextProto[nextProtoUnencryptedHTTP2] = func(hs *Server, c *tls.Conn, h Handler) { |
| nc := c.NetConn().(interface { |
| UnencryptedNetConn() net.Conn |
| }).UnencryptedNetConn() |
| h2srv.ServeConn(nc, &http2.ServeConnOpts{ |
| Context: h.(baseContexter).BaseContext(), |
| Handler: http2Handler{h}, |
| BaseConfig: http2ServerConfig{hs}, |
| SawClientPreface: true, |
| }) |
| } |
| |
| s.h2 = h2srv |
| } |
| |
| func serveHTTP2Conn(ctx context.Context, c *conn, h Handler) bool { |
| if c.server.h2 == nil { |
| return false |
| } |
| c.server.h2.ServeConn(c.rwc, &http2.ServeConnOpts{ |
| Context: ctx, |
| Handler: http2Handler{h}, |
| BaseConfig: http2ServerConfig{c.server}, |
| SawClientPreface: true, |
| }) |
| return true |
| } |
| |
| 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) IdleTimeout() time.Duration { return s.s.IdleTimeout } |
| func (s http2ServerConfig) ReadTimeout() time.Duration { return s.s.ReadTimeout } |
| func (s http2ServerConfig) DisableClientPriority() bool { return s.s.DisableClientPriority } |
| func (s http2ServerConfig) HTTP2Config() http2.Config { |
| if s.s.HTTP2 == nil { |
| return http2.Config{} |
| } |
| return (http2.Config)(*s.s.HTTP2) |
| } |
| |
| 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}) |
| t2.AllowHTTP = true |
| 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) |
| } |
| t.TLSNextProto["h2"] = func(authority string, c *tls.Conn) RoundTripper { |
| err := t2.AddConn("https", authority, c) |
| if err != nil { |
| return http2ErringRoundTripper{err} |
| } |
| return http2RoundTripper{t2, false} |
| } |
| t.TLSNextProto[nextProtoUnencryptedHTTP2] = func(authority string, c *tls.Conn) RoundTripper { |
| unencrypted, ok := c.NetConn().(unencryptedNetConnInTLSConn) |
| if !ok { |
| return http2ErringRoundTripper{errors.New("http: *tls.Conn expected to wrap an unencrypted conn, but does not (BUG)")} |
| } |
| err := t2.AddConn("http", authority, unencrypted.conn) |
| if err != nil { |
| return http2ErringRoundTripper{err} |
| } |
| return http2RoundTripper{t2, false} |
| } |
| |
| // Auto-configure the http2.Transport's MaxHeaderListSize from |
| // the http.Transport's MaxResponseHeaderBytes. They don't |
| // exactly mean the same thing, but they're close. |
| if limit1 := t.MaxResponseHeaderBytes; limit1 != 0 && t2.MaxHeaderListSize == 0 { |
| const h2max = 1<<32 - 1 |
| if limit1 >= h2max { |
| t2.MaxHeaderListSize = h2max |
| } else { |
| t2.MaxHeaderListSize = uint32(limit1) |
| } |
| } |
| |
| // 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 |
| } |
| |
| 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 |
| } |
| |
| func (rt http2RoundTripper) NewClientConn(nc net.Conn, internalStateHook func()) (RoundTripper, error) { |
| cc, err := rt.t.NewClientConn(nc, internalStateHook) |
| if err != nil { |
| return nil, err |
| } |
| return http2ClientConn{cc}, nil |
| } |
| |
| type http2ClientConn struct { |
| http2.NetHTTPClientConn |
| } |
| |
| func (cc http2ClientConn) RoundTrip(req *Request) (*Response, error) { |
| return http2RoundTrip(req, cc.NetHTTPClientConn.RoundTrip) |
| } |
| |
| type transportConfig struct { |
| t *Transport |
| } |
| |
| func (t transportConfig) MaxResponseHeaderBytes() int64 { return t.t.MaxResponseHeaderBytes } |
| func (t transportConfig) DisableCompression() bool { return t.t.DisableCompression } |
| 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) IdleConnTimeout() time.Duration { return t.t.IdleConnTimeout } |
| |
| func (t transportConfig) HTTP2Config() http2.Config { |
| return *(*http2.Config)(t.t.HTTP2) |
| } |
| |
| // 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 |
| } |