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.
Uh oh!
There was an error while loading. Please reload this page.
-
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/http2into 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/nettostd. 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/http2into 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/netpackage until no supported versions of Go use a vendored version of it, at which point we will deprecate it. We will also update thex/netpackage to forward tostdwhen available.Background
The
net/httppackage 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/http2package. To provide transparent support for HTTP/2, this package is vendored into the standard library. To avoid an import cycle (thehttp2package depends onnet/http), this vendoring is done by thebundletool, which converts the entirehttp2package into a single source file (h2_bundle.go) which is then included innet/http.Users can replace the bundled version of the
http2package with the x/net one usinghttp2.ConfigureServerorhttp2.ConfigureTransports.Users who want to tune HTTP/2 configuration settings are required to replace the bundled
http2package 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/http2is 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/httpservers 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/http2live 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 thehttp2package 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/netallowed 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/httpoutweigh the benefits. It's time to make it part of the standard library.API
The
golang.org/x/net/http2package has a substantial API surface.We will add new APIs to
net/httpto preserve the ability to configure HTTP/2 clients and servers. Some configuration settings supported bygolang.org/x/net/http2have proven to not be useful as implemented, and will be deprecated instead.The following set of proposals evolve the
net/httpandgolang.org/x/net/http2packages 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/http2other 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/httpandgolang.org/x/net/http2, but I've got a proof of concept implementation that demonstrates that we can do this while keepinghttp2in 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/http2to forward to the implementation instdwhen 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/http2to 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 vendorhttp2and 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/http2by using thehttp2.Serverand/orhttp2.Transportdirectly, or by usinghttp2.ConfigureServerand/orhttp2.ConfigureTransportsto instruct anet/httpServerand/orTransportto use the non-bundled implementation.Users select the
golang.org/x/net/http2implementation 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.NewWriteSchedulerorhttp2.Transport.ConnPool).proposal: x/net/http2: move frame operations to x/net/http2/http2frame
The
golang.org/x/net/http2package includes support for reading and writing HTTP/2 frames. Thehttp2.Framertype 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
Framerand all of its ancillary types into a newgolang.org/x/net/http2/http2framepackage and eventually deprecating the versions inhttp2.Specifically, the following types will move into the new package:
ContinuationFrameDataFrameFrameFrameHeaderFrameTypeFrameWriteRequestFramerGoAwayFrameHeadersFrameHeadersFrameParamMetaHeadersFramePingFramePriorityFramePriorityParamPushPromiseFramePushPromiseParamRSTStreamFrameSettingsFrameUnknownFrameWindowUpdateFrameAlternatively, 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.WriteSchedulerinterface type defines a scheduler used to select the order in which data is written to HTTP/2 streams. TheServer.NewWriteSchedulerconfiguration 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:
FrameWriteRequestNewPriorityWriteSchedulerNewRandomWriteSchedulerOpenStreamOptionsPriorityWriteSchedulerConfigServer.NewWriteSchedulerWriteSchedulerI 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
NewWriteSchedulerfield.proposal: x/net/http2: deprecate ClientConnPool
The
http2.ClientConnPoolinterface 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/httppackage and upgraded to HTTP/2 by ALPN cannot be pooled.We cannot evolve the
ClientConnPoolinterface 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
ClientConnPoolis not a good basis for that feature.I propose that we add a deprecation notice to
ClientConnPoolandTransport.ConnPooltoday, and remove support for it in one year. After removing support, transports will ignore the value of theConnPoolconfiguration field.proposal: net/http: HTTP/2 configuration API
Configuring HTTP/2-specific protocol options currently requires users to import the
golang.org/x/net/http2package and callhttp2.ConfigureServerorhttp2.ConfigureTransports.Configuring options in this fashion has the side effect of replacing the bundled HTTP/2 implementation in
net/httpwith 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
MaxDecoderHeaderTableSizefield ofhttp2.Serverandhttp2.Transportsets theSETTINGS_HEADER_TABLE_SIZEsetting sent to the peer. We will unify these settings into a single configuration struct, and add this configuration tohttp.Serverandhttp.Transport.The
SendPingTimeoutandPingTimeoutfields presume #XXXX is accepted, adding support for configurable pings to HTTP/2 servers.Concurrent request limit
The
http2.Transport.StrictMaxConcurrentStreamsfield 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 whenStrictMaxConcurrentStreamsis 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.Serverandhttp.Transport, and require no new API surface.The following fields exist on both
httpandhttp2versions of the type:Server.IdleTimeoutServer.WriteByteTimeout(assuming #XXXX is accepted)Transport.DialTLSTransport.DialTLSContextTransport.DisableCompressionTransport.WriteByteTimeout(assuming #XXXX is accepted)The default value of the
http2.Transport.MaxHeaderListSizefield is set byhttp.Transport.MaxResponseHeaderBytes.Assuming #XXXX is accepted, the
AllowHTTPfield ofhttp2.Transportwill 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/httppackage 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.AllowHTTPand providing aDialTLSfunction which opens an unencrypted connection. Users may accept unencrypted HTTP/2 requests by using thegolang.org/x/net/http2/h2cpackage.Neither of these mechanisms is very clean: Returning an unencrypted connection from
DialTLSis unfortunate, and the interactions betweennet/http,h2c, andgolang.org/x/net/http2are 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.Protocolvalue:When
Server.ProtocolcontainsUnencryptedHTTP2, the server will accept HTTP/2 requests on its unencrypted port(s).When
Protocolscontains bothHTTP1andUnencryptedHTTP2, bothServerandTransportwill support theUpgrade: h2cheader specified in RFC 7540 Section 3.2. The server will upgrade HTTP/1 requests withUpgrade: h2cto unencrypted HTTP/2, and the transport will send anUpgrade: h2cheader.When
Transport.ProtocolcontainsUnencryptedHTTP2but 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.AllowHTTPsetting controls whether the transport permits requests using thehttp://scheme. WhenProtocolscontainsUnencryptedHTTP1, the transport will ignore this setting and always permithttp://requests.Backwards compatibility
The
golang.org/x/net/http2/h2cpackage enables unencrypted HTTP/2 on a per-handler basis, while the proposed API here enables it on a per-server basis. In addition, theh2cpackage has the side effect of selecting the HTTP/2 implementation ingolang.org/x/net/http2rather than the one bundled innet/http.For these reasons, it is not possible to duplicate the behavior of the
h2cpackage using the new API.Instead, we will change the handler created by
h2c.NewHandleras follows:If the Server for a request has a
Protocolfield containingUnencryptedHTTP2, then pass through the request unchanged and allow thenet/httppackage to manage protocol selection.Otherwise, behave as today, hijacking the request and directing it to the
golang.org/x/net/http2package.Deprecation of
Upgrade: h2cThe
Upgrade: h2cupgrade path was deprecated by RFC 9113.The
h2cpackage 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.Protocolscontains bothHTTP1andUnencryptedHTTP2, 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/httpServerorTransport.Background
By default, a
net/httpserver listening on a TLS connection will accept both HTTP/1 and HTTP/2 requests, and anet/httpclient sending HTTPS requests will use HTTP/2 when available and HTTP/1 otherwise.Users may disable HTTP/2 support by setting
Server.TLSNextProtoorTransport.TLSNextPrototo an empty map.Users may disable HTTP/1 support by using an
http2.Serverorhttp2.Transportdirectly.The
net/httppackage 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
TLSNextProtowill continue to work as they do today, since doing so limits the default protocol set to HTTP/1.Programs which use
http2.Serverorhttp2.Transportdirectly will behave as they do today.The
http2.ConfigureServerandhttp2.ConfigureTransportsfunctions configure an HTTP/1 server or transport to use HTTP/2. We will modify these functions to addHTTP2to theProtocolslist 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.ReadIdleTimeoutandhttp2.PingTimeoutfields 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.Serverdoes 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.WriteByteTimeoutconfiguration 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