net/http: move HTTP/2 into the standard library #60746
Replies: 6 comments 9 replies
-
Great to see this happen, thank you for the detailed proposal @neild! I haven't thought through all the specifics of it yet, but there's an effort at the IETF to support WebTransport over HTTP/2. It hasn't made as much progress as WebTransport over HTTP/3 yet, but the use case is pretty compelling: Developers could write their (browser) application using the WebTransport API, and use an automatic HTTP/2 fallback if HTTP/3 is not available (UDP blocked, for example). It can also be used as a replacement for WebSocket. |
Beta Was this translation helpful? Give feedback.
-
Yes, please. I had a look at the codebase at work, and the only reason |
Beta Was this translation helpful? Give feedback.
-
I'm very excited to see this! We rely heavily on http2 and working with x/net has been a bit painful.
For context, we've been running a high traffic revproxy on our edge network written in Go since ~2016. It's strictly http2 from the proxy to origin, and it's essential that we have fine-grained control over the characteristics of those pooled upstream connections - how many are made, how quickly, how long they live, under what conditions idle connections are reaped, and so on. Like you mentioned, http2.ClientConnPool's current API is necessary but not sufficient for this, it only represents 1/2 of the required API for these kinds of use cases. There've been various discussions about what a suitable API would look like over the years (eg. #17776), but it was hard to get interest or push to a decision and seemed like there was churn in who was maintaining net, so they always petered out. At the moment we carry patches to provide the other required API. My concern with deprecating and removing is that it'll make things harder in the short-term (we lose a fairly straightforward point to apply out of tree patches), and there's no guarantee that a replacement API will be made in the medium/long term. I'd be very keen to get a replacement proposal in place before deprecation, and happy to contribute towards that if there's upstream interest. |
Beta Was this translation helpful? Give feedback.
-
I agree that we need a new client connection pool API. I think that it must cover HTTP/1 and HTTP/2 connections, with room to expand for HTTP/3. The existing I don't know what that design should look like yet. I'm very interested in ideas if you (or anyone else) has any. |
Beta Was this translation helpful? Give feedback.
-
Issue #41238 sounds like it could belong in here as well. |
Beta Was this translation helpful? Give feedback.
-
Thanks for all the useful discussion! Moving this into actual issues to start progress on implementation: https://go.dev/issue/67810 |
Beta Was this translation helpful? Give feedback.
-
Overview
This is a discussion to talk about a collection of proposals that collectively move the HTTP/2 client and server implementation currently located in
golang.org/x/net/http2
into the standard library.The change is split across a number of proposals which make various elements of HTTP/1 and HTTP/2 configuration more consistent, add the ability to configure the HTTP/2 implementation to
net/http
, and finally move the source of truth fromx/net
tostd
. The following presents these proposals starting at the highest level (moving HTTP/2 intostd
) and progressing to lower-level items.Proposal: Move HTTP/2 into the standard library
I propose that we move the HTTP/2 client and server implementations from
golang.org/x/net/http2
into an internal standard library package,net/http/internal/http2
. All new development will occur in the standard library package. We will backport fixes to the HTTP/2 implementation into thex/net
package until no supported versions of Go use a vendored version of it, at which point we will deprecate it. We will also update thex/net
package to forward tostd
when available.Background
The
net/http
package has provided transparent support for the HTTP/2 protocol since Go 1.6.The source of truth for the net/http package's HTTP/2 implementation is the
golang.org/x/net/http2
package. To provide transparent support for HTTP/2, this package is vendored into the standard library. To avoid an import cycle (thehttp2
package depends onnet/http
), this vendoring is done by thebundle
tool, which converts the entirehttp2
package into a single source file (h2_bundle.go
) which is then included innet/http
.Users can replace the bundled version of the
http2
package with the x/net one usinghttp2.ConfigureServer
orhttp2.ConfigureTransports
.Users who want to tune HTTP/2 configuration settings are required to replace the bundled
http2
package in this fashion.The current design has some advantages, notably that it permits users to acquire new versions of the HTTP/2 implementation outside of the Go release cycle. Users can pick up fixes or new features without the need to upgrade their Go version or wait for a fix to become available in a released version of Go.
This design also has many disadvantages. The relationship between
net/http
, the bundled HTTP/2 implementation inh2_bundle.go
, andgolang.org/x/net/http2
is complicated and confusing. The process for backporting HTTP/2 fixes into minor releases is difficult. The process for backporting security fixes is even more so. Users can't configure HTTP/2 settings innet/http
servers and clients without importing an external package. Configuring these settings doesn't just change the configuration, it changes the implementation used bynet/http
.The fact that net/http and
golang.org/x/net/http2
live in separate modules complicates development. Most of the tests in the net/http package exercise both the HTTP/1 and HTTP/2 implementations, but running those tests for a change to thehttp2
package is difficult. In addition, there are interactions between the two packages which are very difficult to change without breaking some set of users. For example, #52459 is an issue caused by the fact that both the HTTP/1 and HTTP/2 implementations contain a separate retry loop to retry failed requests. The simplest fix for this I can think of is to have a single retry loop that covers both protocols, but making this change without the ability to atomically adjust both packages at the same time is difficult.Initially developing HTTP/2 support in
x/net
allowed us to iterate on the package in a place not covered by the compatibility promise and make changes at a pace not limited by the Go release cycle. Now that the package is stable, the costs of keeping it in a separate repository fromnet/http
outweigh the benefits. It's time to make it part of the standard library.API
The
golang.org/x/net/http2
package has a substantial API surface.We will add new APIs to
net/http
to preserve the ability to configure HTTP/2 clients and servers. Some configuration settings supported bygolang.org/x/net/http2
have proven to not be useful as implemented, and will be deprecated instead.The following set of proposals evolve the
net/http
andgolang.org/x/net/http2
packages in preparation for replacing the HTTP/2 package. Combined, they bring us to a state where there is no need for users to importgolang.org/x/net/http2
other than to get a newer or older version than is bundled innet/http
: Every non-deprecated feature of the package is available elsewhere, and all non-deprecated configuration settings can be set fromnet/http
.Proposal
We implement the set of API changes above.
We move the HTTP/2 implementation into
std
. This is technically a bit tricky to do, since there's currently an import cycle betweennet/http
andgolang.org/x/net/http2
, but I've got a proof of concept implementation that demonstrates that we can do this while keepinghttp2
in a separate (now internal) package. The required changes are surprisingly small, outside of tests. This step requires no additional API changes.We update
golang.org/x/net/http2
to forward to the implementation instd
when available.Once no supported Go version uses a vendored
golang.org/x/net/http2
, we deprecategolang.org/x/net/http2
.Backports
We backport fixes for security issues and serious problems with no workaround. (https://github.com/golang/go/wiki/MinorReleases)
For HTTP/2, we have generally considered the ability to import and use a newer version of
golang.org/x/net/http2
to be a workaround, and therefore only backport fixes for HTTP/2 security issues.Once HTTP/2 moves into the standard library, this will not be a viable workaround. We will therefore need to backport fixes for serious, non-security HTTP/2 problems in the future.
For so long as there are supported Go versions that vendor
golang.org/x/net/http2
, we will also need to backport fixes to that package. Once no supported Go versions vendorhttp2
and the package is deprecated, we will stop backporting changes to it and include a warning about the lack of security fixes in the deprecation notice.Backwards compatibility and migration
Existing users select the HTTP/2 implementation in
golang.org/x/net/http2
by using thehttp2.Server
and/orhttp2.Transport
directly, or by usinghttp2.ConfigureServer
and/orhttp2.ConfigureTransports
to instruct anet/http
Server
and/orTransport
to use the non-bundled implementation.Users select the
golang.org/x/net/http2
implementation in this fashion to configure HTTP/2 parameters, to upgrade to a newer version of the HTTP/2 implementation than provided by their Go version, or to pin to a specific version of the HTTP/2 implementation. There is no way to distinguish between these use cases (which is one of the problems with the current situation).For all existing versions of
golang.org/x/net/http2
, we will preserve the existing behavior: A user who selects this implementation will get it, overriding the standard library implementation.Newer versions of
golang.org/x/net/http2
, when used with a Go version that contains non-vendored HTTP/2 implementation, will use the standard library HTTP/2 implementation unless the configuration uses a feature not supported by the standard library (http2.Server.NewWriteScheduler
orhttp2.Transport.ConnPool
).proposal: x/net/http2: move frame operations to x/net/http2/http2frame
The
golang.org/x/net/http2
package includes support for reading and writing HTTP/2 frames. Thehttp2.Framer
type reads and writes frames, and a number of additional types represent specific frames.Frame support adds a large amount of API surface that few users need. Users who send and receive HTTP/2 requests operate at a higher level than framing, which is an internal protocol detail.
I propose moving
Framer
and all of its ancillary types into a newgolang.org/x/net/http2/http2frame
package and eventually deprecating the versions inhttp2
.Specifically, the following types will move into the new package:
ContinuationFrame
DataFrame
Frame
FrameHeader
FrameType
FrameWriteRequest
Framer
GoAwayFrame
HeadersFrame
HeadersFrameParam
MetaHeadersFrame
PingFrame
PriorityFrame
PriorityParam
PushPromiseFrame
PushPromiseParam
RSTStreamFrame
SettingsFrame
UnknownFrame
WindowUpdateFrame
Alternatively, we could deprecate the frame types and provide no alternative. I have not done the analysis to see how many, if any, users this would affect; I suspect the number is very low, but not zero.
proposal: x/net/http2: deprecate WriteScheduler
The
http2.WriteScheduler
interface type defines a scheduler used to select the order in which data is written to HTTP/2 streams. TheServer.NewWriteScheduler
configuration setting selects a write scheduler to use for server connections. Client connections do not permit configuring the write scheduler.The package includes three implementations of this interface: Round robin (the default), random, and priority. The priority write scheduler is known to be buggy and the HTTP/2 stream prioritization scheme it implements is deprecated; see #58804 (comment).
Support for user-defined write schedulers is used seldom, if ever, and substantially constrains the HTTP/2 implementation.
Users should not need to configure the HTTP/2 implementation at this level. Of the current set of scheduler options, there is a clear correct choice: The round robin scheduler outperforms the random scheduler, and the priority scheduler is buggy and we have no plans to fix it. If we come up with a better scheduler, we should just make it the default. If we do discover a reason for users to configure this, we can and should provide a simpler configuration setting than what exists now.
I propose deprecating the write scheduler selection mechanism, consisting of the following types and functions:
FrameWriteRequest
NewPriorityWriteScheduler
NewRandomWriteScheduler
OpenStreamOptions
PriorityWriteSchedulerConfig
Server.NewWriteScheduler
WriteScheduler
I propose that we add a deprecation notice to these types and functions today, and remove support for them in one year. After removing support, servers will ignore the value of the
NewWriteScheduler
field.proposal: x/net/http2: deprecate ClientConnPool
The
http2.ClientConnPool
interface type and associated types define an API for a user-defined client connection pool. This interface does not sufficiently describe the features required by a useful connection pool. It has no mechanism for notifying the pool when a request on a pooled connection has completed. It has no mechanism for adding a connection to the pool, so connections created by thenet/http
package and upgraded to HTTP/2 by ALPN cannot be pooled.We cannot evolve the
ClientConnPool
interface to address its limitations, because adding methods to the interface is not backwards compatible.There might be value in supporting user-defined connection pools, but
ClientConnPool
is not a good basis for that feature.I propose that we add a deprecation notice to
ClientConnPool
andTransport.ConnPool
today, and remove support for it in one year. After removing support, transports will ignore the value of theConnPool
configuration field.proposal: net/http: HTTP/2 configuration API
Configuring HTTP/2-specific protocol options currently requires users to import the
golang.org/x/net/http2
package and callhttp2.ConfigureServer
orhttp2.ConfigureTransports
.Configuring options in this fashion has the side effect of replacing the bundled HTTP/2 implementation in
net/http
with the one ingolang.org/x/net/http2
.I propose adding HTTP/2 configuration options to
net/http
, permitting HTTP/2 servers and clients to be configured without importing an external package and separating configuration from version selection.Parameters common to servers and clients
Many of the HTTP/2 settings are identical for server and client connections. For example, the
MaxDecoderHeaderTableSize
field ofhttp2.Server
andhttp2.Transport
sets theSETTINGS_HEADER_TABLE_SIZE
setting sent to the peer. We will unify these settings into a single configuration struct, and add this configuration tohttp.Server
andhttp.Transport
.The
SendPingTimeout
andPingTimeout
fields presume #XXXX is accepted, adding support for configurable pings to HTTP/2 servers.Concurrent request limit
The
http2.Transport.StrictMaxConcurrentStreams
field controls whether a new connection should be opened to a server if an existing connection has exceeded its stream limit. For example, if an HTTP/2 server advertises a stream limit of 100 concurrent streams, then the 101st concurrent stream opened by the client will block waiting for an existing stream to complete whenStrictMaxConcurrentStreams
is true, or create a new connection when it is false. There is no equivalent to this setting for HTTP/1 connections, which only support a single concurrent request per connection. We will add this setting tohttp.Transport
, since it could be used to configure HTTP/3 connections (if and when we support HTTP/3):Settings common to HTTP/1 and HTTP/2
Some HTTP/2 settings can already be configured via parameters on
http.Server
andhttp.Transport
, and require no new API surface.The following fields exist on both
http
andhttp2
versions of the type:Server.IdleTimeout
Server.WriteByteTimeout
(assuming #XXXX is accepted)Transport.DialTLS
Transport.DialTLSContext
Transport.DisableCompression
Transport.WriteByteTimeout
(assuming #XXXX is accepted)The default value of the
http2.Transport.MaxHeaderListSize
field is set byhttp.Transport.MaxResponseHeaderBytes
.Assuming #XXXX is accepted, the
AllowHTTP
field ofhttp2.Transport
will be supplanted byhttp.Transport.Protocols
.proposal: net/http: support unencrypted HTTP/2 (h2c)
This proposal depends on the version selection API proposed in #XXXX.
The
net/http
package does not directly support unencrypted HTTP/2, sometimes referred to as "h2c".Users may send HTTP/2 requests over an unencrypted connection by setting
http2.Transport.AllowHTTP
and providing aDialTLS
function which opens an unencrypted connection. Users may accept unencrypted HTTP/2 requests by using thegolang.org/x/net/http2/h2c
package.Neither of these mechanisms is very clean: Returning an unencrypted connection from
DialTLS
is unfortunate, and the interactions betweennet/http
,h2c
, andgolang.org/x/net/http2
are fairly complicated.I propose adding support for unencrypted HTTP/2 as a first-class feature to
net/http
.HTTP/2 will be enabled by a new
http.Protocol
value:When
Server.Protocol
containsUnencryptedHTTP2
, the server will accept HTTP/2 requests on its unencrypted port(s).When
Protocols
contains bothHTTP1
andUnencryptedHTTP2
, bothServer
andTransport
will support theUpgrade: h2c
header specified in RFC 7540 Section 3.2. The server will upgrade HTTP/1 requests withUpgrade: h2c
to unencrypted HTTP/2, and the transport will send anUpgrade: h2c
header.When
Transport.Protocol
containsUnencryptedHTTP2
but notHTTP1
, the transport will use unencrypted HTTP/2 for requests forhttp://
URLs ("Starting HTTP/2 with prior knowledge", RFC 7540 Section 3.4).http2.Transport.AllowHTTP
The HTTP/2
http2.Transport.AllowHTTP
setting controls whether the transport permits requests using thehttp://
scheme. WhenProtocols
containsUnencryptedHTTP1
, the transport will ignore this setting and always permithttp://
requests.Backwards compatibility
The
golang.org/x/net/http2/h2c
package enables unencrypted HTTP/2 on a per-handler basis, while the proposed API here enables it on a per-server basis. In addition, theh2c
package has the side effect of selecting the HTTP/2 implementation ingolang.org/x/net/http2
rather than the one bundled innet/http
.For these reasons, it is not possible to duplicate the behavior of the
h2c
package using the new API.Instead, we will change the handler created by
h2c.NewHandler
as follows:If the Server for a request has a
Protocol
field containingUnencryptedHTTP2
, then pass through the request unchanged and allow thenet/http
package to manage protocol selection.Otherwise, behave as today, hijacking the request and directing it to the
golang.org/x/net/http2
package.Deprecation of
Upgrade: h2c
The
Upgrade: h2c
upgrade path was deprecated by RFC 9113.The
h2c
package provides server support forUpgrade: h2c
, but I don't believe we have existing client support for this path.We could choose to support only HTTP/2 with prior knowledge (RFC 9113, Section 3.3). In this case, when
Transport.Protocols
contains bothHTTP1
andUnencryptedHTTP2
, the transport will select whichever appears first in the list.proposal: net/http: HTTP version selection API
I propose adding a new mechanism for selecting what HTTP versions will be used by a
net/http
Server
orTransport
.Background
By default, a
net/http
server listening on a TLS connection will accept both HTTP/1 and HTTP/2 requests, and anet/http
client sending HTTPS requests will use HTTP/2 when available and HTTP/1 otherwise.Users may disable HTTP/2 support by setting
Server.TLSNextProto
orTransport.TLSNextProto
to an empty map.Users may disable HTTP/1 support by using an
http2.Server
orhttp2.Transport
directly.The
net/http
package does not currently directly support HTTP/3, but if and when it does, there will need to be a mechanism for enabling or disabling HTTP/3.The existing APIs for selecting a protocol version are confusing, inconsistent, expose internal implementation details, and don't generalize well to additional protocol versions. The above proposal replaces them with a single, clear mechanism that allows for future expansion.
Backwards compatibility
Programs which disable HTTP/2 support by setting
TLSNextProto
will continue to work as they do today, since doing so limits the default protocol set to HTTP/1.Programs which use
http2.Server
orhttp2.Transport
directly will behave as they do today.The
http2.ConfigureServer
andhttp2.ConfigureTransports
functions configure an HTTP/1 server or transport to use HTTP/2. We will modify these functions to addHTTP2
to theProtocols
list when the list is non-empty and does not containHTTP2
.proposal: x/net/http2: configurable server pings
A HTTP/2 client or server may send PING frames to its peer. (RFC 9113 Section 6.7)
The
http2.ReadIdleTimeout
andhttp2.PingTimeout
fields configure an HTTP/2 client to send a PING when a connection has been idle for some amount of time, and to close the connection if no response is received.The
http2.Server
does not support sending PINGs on idle connections.I propose adding the ability to configure servers to send pings as well. This setting will be off by default.
proposal: net/http: add per-write timeouts (WriteByteTimeout)
HTTP/2 transports have a
Transport.WriteByteTimeout
configuration setting, which sets the maximum time a single write to a connection may take. The timeout begins when a write is made, and is extended whenever any data is written.This setting can be used to detect unresponsive connections when the timeout for an entire request may be large or unbounded.
I propose extending this feature to apply to HTTP/1 server and client connections, and HTTP/2 server connections.
Beta Was this translation helpful? Give feedback.
All reactions