This is a proposal to add the ability to plug an HTTP/3 implementation into the net/http Transport and Server. This is not a proposal to add HTTP/3 support (that's #32204).
The goal of this proposal is to make it possible for users to use HTTP/3 with net/http, with the HTTP/3 implementation provided by a separate package outside of net/http. For example, we would like to support sending HTTP/3 requests using a net/http.Transport:
tr := &http.Transport{}
// Register an external HTTP/3 implementation.
// "http3" is some package providing that implementation.
http3.RegisterTransport(tr)
// Configure the Transport to use HTTP/3.
tr.Protocols = new(http.Protocols)
tr.Protocols.SetHTTP3(true)
// This request is sent using HTTP/3.
resp, err := tr.RoundTrip(req)
We would also like to support serving HTTP/3 using a net/http.Server:
srv := &http.Server{Addr: "localhost:8000"}
// Register an external HTTP/3 implementation.
// "http3" is some package providing that implementation.
http3.RegisterServer(srv)
// Configure the Server to use HTTP/3.
tr.Protocols = new(http.Protocols)
tr.Protocols.SetHTTP3(true)
// The server serves HTTP/3 on localhost:8000.
srv.ListenAndServeTLS(certFile, keyFile)
There are several related changes to net/http required to make the above work.
Add HTTP/3 to http.Protocols
The http.Protocols type describes a selection from a set of protocols, currently HTTP/1, HTTP/2, and unencrypted HTTP/2. The Transport.Protocols and Server.Protocols configuration fields let a user select which protocols to use.
We will add HTTP/3 to the the set of protocols:
package http
// ...
// * HTTP3 is the HTTP/3 protocol over a QUIC connection.
// The net/http package does not directly support HTTP/3 at this time,
// but support may be provided by external packages.
type Protocols struct{} // existing type
// HTTP3 reports whether p includes HTTP/3.
func (p Protocols) HTTP3() bool
// SetHTTP3 adds or removes HTTP/3 from p.
func (p *Protocols) SetHTTP3(ok bool)
This is the only change to net/http's exported API.
Define HTTP/3-enabled client and server behavior
We must define what happens when Transport.Protocols or Server.Protocols includes HTTP/3.
When Transport.Protocols includes HTTP3 and does not include HTTP1 or HTTP2,
the transport will use HTTP/3 for requests for HTTP/3 URLs. Transports will not, at this time, use HTTP/3 when any other protocol is available.
This is the minimal viable choice for Transport: There are many possible ways to choose between HTTP/1, HTTP/2, and HTTP/3. We might, for example, use HTTP/3 when an HTTPS DNS record is present. We might upgrade from HTTP/1 or /2 after receiving an Alt-Svc HTTP response header or HTTP/2 ALTSVC frame. We might try initiating both TLS and QUIC connections at the same time and pick the first to return. All of these are reasonable choices for the future. For the purposes of this proposal, however, we will begin with the simplest option of using HTTP/3 only when the user has unambiguously indicated that this is the correct protocol to use.
When Server.Protocols includes HTTP3, Server.ListenAndServeTLS will listen for HTTP/3 connections.
HTTP/3 client registration
The net/http.Transport needs a way to create new HTTP/3 connections.
Our HTTP/2 implementation currently registers a way to create new HTTP/2 connections using the Transport.RegisterProtocol function. This is a long-standing abuse of RegisterProtocol: This function is intended for registering implementations of protocol schemes such as "ftp" or "file", but we also use it to connect the HTTP/2 implementation to net/http. We will extend the existing mechanism for creating HTTP/2 connections to handle HTTP/3 as well.
// RegisterProtocol is an existing method of Transport.
func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper)
To register an HTTP/3 client implementation, call Transport.RegisterProtocol with a scheme of "http/3" and a RoundTripper which implements the following interface:
type dialClientConner interface {
// DialClientConn creates a new client connection to address.
//
// If proxy is non-nil, the connection should use the provided proxy.
// If HTTP/3 proxies are not supported, DialClientConn should return
// an error wrapping [errors.ErrUnsupported].
//
// The RoundTripper returned by DialClientConn must also implement the
// following methods to support [ClientConn] methods of the same name:
// Close() error
// Err() error
// Reserve() error
// Release() error
// Available() int
// InFlight() int
//
// The client connection should arrange to call internalStateHook
// when the connection closes, when requests complete, and when the
// connection concurrency limit changes.
//
// The client connection must call the internal state hook when
// the connection state changes asynchronously, such as when a request completes.
//
// The internal state hook need not be called after synchronous changes
// to the state: Close, Reserve, Release, and RoundTrip calls
// which don't start a request do not need to call the hook.
DialClientConn(ctx context.Context, address string, proxy *url.URL, internalStateHook func()) (RoundTripper, error)
}
The RoundTripper may also implement the following interface:
type closeIdleConnectionser interface {
// CloseIdleConnections is called by Transport.CloseIdleConnections.
//
// The transport will close idle connections created with DialClientConn
// before calling this method. The HTTP/3 transport should not attempt to
// close idle connections, but may clean up shared resources such as
// UDP sockets if no connections remain.
CloseIdleConnections()
}
Note that while Transport.RegisterProtocol registers a RoundTripper, the RoundTrip method is never called. Note also that we do not propose adding exported definitions of the above interfaces to net/http: We expect that only a low single-digit number of packages will ever use this registration API, and it is not worth adding user-visible complexity to net/http for this case.
The above is an extension of the existing mechanism used to create HTTP/2 connections. It differs in that net/http takes responsibility for creating the *tls.Conn underlying an HTTP/2 connection, but the HTTP/3 implementation is responsible for creating the QUIC connection underlying an HTTP/3 connection.
HTTP/3 server registration
The net/http.Server needs a way to listen for HTTP/3 connections.
To register an HTTP/3 server implementation, add an "http/3" entry to the existing Server.TLSNextProto configuration field.
type Server {
// TLSNextProto is an existing field of Server.
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
}
Server.ListenAndServeTLS will call this function with the server, a nil *tls.Conn, and a Handler that implements the following additional methods:
type http3ServerHandler interface {
// Addr is the address to listen on.
Addr() string
// TLSConfig is the *tls.Config to use.
TLSConfig() *tls.Config
// BaseContext is the base context to use for server requests.
BaseContext() context.Context
// ListenErrHook is called by an HTTP/3 server implementation to
// propagate any error it encounters when trying to listen, if any, to
// net/http.
ListenErrHook(error)
}
The additional methods on the Handler are similar to an existing mechanism used to pass information to the HTTP/2 server.
Note that while existing TLSNextProto functions are called once per connection (net/http accepts a TLS connection and hands it off to the HTTP/2 implementation), TLSNextProto["http/3"] is called once at listen time. This is a somewhat confusing abuse of TLSNextProto, but has the advantage of avoiding the need to add additional user-visible API surface to net/http.
Anticipated Questions
Q: Implementing HTTP/2 as a package external to net/http led to a messy dependency situation which is still being cleaned up. Doesn't this do that all over again?
A: The messy dependency situation for HTTP/2 arises from us supporting HTTP/2 by default in net/http. We do not anticipate supporting HTTP/3 in net/http in the same way in the near future. HTTP/3 is a comparatively heavyweight dependency and comes with a complex set of tradeoffs. We do not want to require all net/http users to bring in that dependency.
So long as HTTP/3 users need to import a separate package such as github.com/quic-go/quic-go or golang.org/x/net/http3, the dependency situation is fine.
Q: Why not just let HTTP/3 be used independently of net/http? Why do we need to plug it into net/http at all?
A: There are several benefits to allowing HTTP/3 implementations to be plugged into net/http:
- Users can adopt HTTP/3 with relatively minimal change to their code.
- HTTP/3 libraries can re-use existing logic within
net/http, such as our connection pooling implementation.
- In the future,
net/http.Transport should be able to select between HTTP/1, HTTP/2, and HTTP/3 on a case-by-case basis.
- We can reuse most of our existing
net/http tests to test our HTTP/3 implementation.
This is a proposal to add the ability to plug an HTTP/3 implementation into the
net/httpTransportandServer. This is not a proposal to add HTTP/3 support (that's #32204).The goal of this proposal is to make it possible for users to use HTTP/3 with
net/http, with the HTTP/3 implementation provided by a separate package outside ofnet/http. For example, we would like to support sending HTTP/3 requests using anet/http.Transport:We would also like to support serving HTTP/3 using a
net/http.Server:There are several related changes to
net/httprequired to make the above work.Add HTTP/3 to http.Protocols
The
http.Protocolstype describes a selection from a set of protocols, currently HTTP/1, HTTP/2, and unencrypted HTTP/2. TheTransport.ProtocolsandServer.Protocolsconfiguration fields let a user select which protocols to use.We will add HTTP/3 to the the set of protocols:
This is the only change to
net/http's exported API.Define HTTP/3-enabled client and server behavior
We must define what happens when
Transport.ProtocolsorServer.Protocolsincludes HTTP/3.When
Transport.Protocolsincludes HTTP3 and does not include HTTP1 or HTTP2,the transport will use HTTP/3 for requests for HTTP/3 URLs. Transports will not, at this time, use HTTP/3 when any other protocol is available.
This is the minimal viable choice for
Transport: There are many possible ways to choose between HTTP/1, HTTP/2, and HTTP/3. We might, for example, use HTTP/3 when an HTTPS DNS record is present. We might upgrade from HTTP/1 or /2 after receiving an Alt-Svc HTTP response header or HTTP/2 ALTSVC frame. We might try initiating both TLS and QUIC connections at the same time and pick the first to return. All of these are reasonable choices for the future. For the purposes of this proposal, however, we will begin with the simplest option of using HTTP/3 only when the user has unambiguously indicated that this is the correct protocol to use.When
Server.Protocolsincludes HTTP3,Server.ListenAndServeTLSwill listen for HTTP/3 connections.HTTP/3 client registration
The
net/http.Transportneeds a way to create new HTTP/3 connections.Our HTTP/2 implementation currently registers a way to create new HTTP/2 connections using the
Transport.RegisterProtocolfunction. This is a long-standing abuse ofRegisterProtocol: This function is intended for registering implementations of protocol schemes such as "ftp" or "file", but we also use it to connect the HTTP/2 implementation to net/http. We will extend the existing mechanism for creating HTTP/2 connections to handle HTTP/3 as well.To register an HTTP/3 client implementation, call
Transport.RegisterProtocolwith a scheme of "http/3" and aRoundTripperwhich implements the following interface:The
RoundTrippermay also implement the following interface:Note that while
Transport.RegisterProtocolregisters aRoundTripper, theRoundTripmethod is never called. Note also that we do not propose adding exported definitions of the above interfaces tonet/http: We expect that only a low single-digit number of packages will ever use this registration API, and it is not worth adding user-visible complexity tonet/httpfor this case.The above is an extension of the existing mechanism used to create HTTP/2 connections. It differs in that
net/httptakes responsibility for creating the*tls.Connunderlying an HTTP/2 connection, but the HTTP/3 implementation is responsible for creating the QUIC connection underlying an HTTP/3 connection.HTTP/3 server registration
The
net/http.Serverneeds a way to listen for HTTP/3 connections.To register an HTTP/3 server implementation, add an "http/3" entry to the existing
Server.TLSNextProtoconfiguration field.Server.ListenAndServeTLSwill call this function with the server, a nil*tls.Conn, and aHandlerthat implements the following additional methods:The additional methods on the Handler are similar to an existing mechanism used to pass information to the HTTP/2 server.
Note that while existing
TLSNextProtofunctions are called once per connection (net/httpaccepts a TLS connection and hands it off to the HTTP/2 implementation),TLSNextProto["http/3"]is called once at listen time. This is a somewhat confusing abuse ofTLSNextProto, but has the advantage of avoiding the need to add additional user-visible API surface tonet/http.Anticipated Questions
Q: Implementing HTTP/2 as a package external to
net/httpled to a messy dependency situation which is still being cleaned up. Doesn't this do that all over again?A: The messy dependency situation for HTTP/2 arises from us supporting HTTP/2 by default in
net/http. We do not anticipate supporting HTTP/3 innet/httpin the same way in the near future. HTTP/3 is a comparatively heavyweight dependency and comes with a complex set of tradeoffs. We do not want to require allnet/httpusers to bring in that dependency.So long as HTTP/3 users need to import a separate package such as
github.com/quic-go/quic-goorgolang.org/x/net/http3, the dependency situation is fine.Q: Why not just let HTTP/3 be used independently of
net/http? Why do we need to plug it intonet/httpat all?A: There are several benefits to allowing HTTP/3 implementations to be plugged into
net/http:net/http, such as our connection pooling implementation.net/http.Transportshould be able to select between HTTP/1, HTTP/2, and HTTP/3 on a case-by-case basis.net/httptests to test our HTTP/3 implementation.