blob: a6f4f20f816449a4f6298ae234509069e57fb7a7 [file] [log] [blame]
// 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
}