Skip to content

net/http: add support for SETTINGS_ENABLE_CONNECT_PROTOCOL #53208

Open
@ethanpailes

Description

@ethanpailes

Overview

I propose extending the x/net/http2 and net/http packages to support the extended CONNECT protocol described in RFC 8441. Support for this will be always-on in the http server, and the http client will automatically detect the presence of support based on the settings flag and adjust the checks it applies to outgoing requests to allow the for the extended CONNECT protocol. http.Request will be extended with a new Protocol member to allow users to set the :protocol psudoheader.

Public Interface Changes

http.Request will gain a new member, Protocol of type string.

All other functionality can be achieved with existing interfaces.

Implementation

An example implementation of SETTINGS_ENABLE_CONNECT_PROTOCOL support for both the client and the server can be found on gerrit. This is just meant to be a proof of concept, not a fully production ready implementation. Rather than using a field on http.Request it uses a magic header called HACK-HTTP2-Protocol, but the intention is that this would be switched out for req.Protocol for real code.

The implementation is pretty non-invasive. It consists of extending the code to allow the use of a new :protocol psudo header, sending the new settings flag during the initial handshake, and loosening some checks when the extended CONNECT protocol is in use.

Probably the most complex thing about the implementation I was able to come up with is the fact that a CONNECT request coming from the client can now trigger a ping HTTP/2 frame if the server has not yet sent headers. This is required for an extended CONNECT to work if is the very first request on a given http2.Transport object.

Related Issues

#49918 contains some discussion about some of my initial implementation work and links to further context.

Activity

added this to the Proposal milestone on Jun 2, 2022
ianlancetaylor

ianlancetaylor commented on Jun 8, 2022

@ianlancetaylor
Contributor
neild

neild commented on Jun 8, 2022

@neild
Contributor

How do we handle a request sent on an HTTP/1 connection with a Protocol field set? Return an error? Set Upgrade: $PROTOCOL and Connection: upgrade?

An alternative to adding a new field might be to set a :protocol key in the Request.Header map.

ethanpailes

ethanpailes commented on Jun 8, 2022

@ethanpailes
Author

How do we handle a request sent on an HTTP/1 connection with a Protocol field set? Return an error? Set Upgrade: $PROTOCOL and Connection: upgrade?

My inclination is to return an error since my understanding of go's philosophy is that it is a one-way-to-do-things language and you can already set the Upgrade and Connection headers in the Request.Header map. I can definitely sympathize with the alternative view that it is less surprising for Protocol = "websocket" to work regardless of the underlying transport though so I'd be happy to do it either way.

An alternative to adding a new field might be to set a :protocol key in the Request.Header map.

I like this approach because it feels a little weird for Protocol to be a top level field on such a common type when it will be so rarely used. I think I actually tried this when implementing support but ran into an error. I decided to propose a new field because psudoheaders are not really supposed to be set by the user, but I'm not so sure how good a justification this is. On reflection, maybe allowing people to set ":protocol" in the headers map by relaxing some checks that forbid user defined psudoheaders is the best way to go since it doesn't shove the feature in users face quite so much. If you're happy with that approach, I would be as well (really I don't have particularly strong feelings in any direction so I'm happy to do whatever, but this design seems good).

neild

neild commented on Jun 8, 2022

@neild
Contributor

I don't have a strong feeling on a Protocol field vs. setting a :protocol header. Perhaps @bradfitz has an opinion.

An argument for a Protocol field might be if allows websockets implementations to be agnostic of HTTP/1 or HTTP/2 with net/http handling translation between the Protocol field and the underlying request (pseudo-)headers. I don't know if that's feasible.

We do have existing cases where you can set either a field in the Request or a header (e.g., Content-Length), so doing that for Upgrade and Connection wouldn't necessarily be horrible.

ethanpailes

ethanpailes commented on Jun 8, 2022

@ethanpailes
Author

An argument for a Protocol field might be if allows websockets implementations to be agnostic of HTTP/1 or HTTP/2 with net/http handling translation between the Protocol field and the underlying request (pseudo-)headers. I don't know if that's feasible.

The websocket handshake for HTTP/1.1 and HTTP/2 are sufficiently different that attempting this is probably not a good idea. There is a bunch of Sec-* header stuff in HTTP/1.1 that isn't needed in HTTP/2 because psudoheaders are not user-settable from within a browser. The HTTP/2 handshake doesn't need to muck about with nonces for example. Since they are different enough any websocket library will need to explicitly support both protocols unless we move most of the handshake logic into the http and http2 packages themselves, which doesn't seem ideal.

We do have existing cases where you can set either a field in the Request or a header (e.g., Content-Length), so doing that for Upgrade and Connection wouldn't necessarily be horrible.

Gotcha, that is good context and weakens my one-way-to-do-things argument.

I think I'm still a little inclined towards allowing a :protocol header, but not that strongly.

moved this to Incoming in Proposalson Aug 10, 2022
bradfitz

bradfitz commented on Oct 21, 2022

@bradfitz
Contributor

I'd rather put :protocol in Request.Header (akin to http.TrailerPrefix) over adding a new Request.Protocol string field.

neild

neild commented on Oct 21, 2022

@neild
Contributor

Proposal SGTM with a :protocol pseudo-header in Request.Header.

ethanpailes

ethanpailes commented on Oct 21, 2022

@ethanpailes
Author

Should I clean up the gerrit patch to use a :protocol pseudo-header in Request.Header and request another review, or is there another step required for the proposal to be accepted?

35 remaining items

neild

neild commented on Jan 31, 2025

@neild
Contributor

Late in cycle, we discovered an issue with the implementation of this proposal: https://go.dev/issue/71128

It turns out that browsers interpret a server advertising Extended CONNECT support as the server also advertising WebSocket-over-HTTP/2 support. This causes problems when net/http supports Extended CONNECT but the WebSocket server implementation does not.

As a result, we have disabled Extended CONNECT by default for Go 1.24. It can still be enabled by setting GODEBUG=http2xconnect=1.

Since enabling Extended CONNECT requires cooperation between the HTTP and WebSocket layers, I don't see any way to enable Extended CONNECT in a backwards-compatible way without providing a knob accessible to the WebSocket layer to enable it.

I propose adding such a knob to http.HTTP2Config:

package http

type HTTP2Config {
  // EnableConnectProtocol, if true, enables support for
  // the Extended CONNECT protocol defined in RFC 8441.
  // When enabled, HTTP/2 servers will advertise support for Extended CONNECT.
  // Extended CONNECT requests will include a ":protocol" pseudo header
  // in the request headers.
  EnableConnectProtocol bool
}
rsc

rsc commented on Feb 12, 2025

@rsc
Contributor

Pushing back to proposal process for a final check.

moved this from Accepted to Incoming in Proposalson Feb 12, 2025
rsc

rsc commented on Feb 13, 2025

@rsc
Contributor

Have all remaining concerns about this proposal been addressed?

It turns out that browsers interpret a server advertising Extended CONNECT support as the server also advertising WebSocket-over-HTTP/2 support. This causes problems when net/http supports Extended CONNECT but the WebSocket server implementation does not.

As a result, we have disabled Extended CONNECT by default for Go 1.24. It can still be enabled by setting GODEBUG=http2xconnect=1.

Since enabling Extended CONNECT requires cooperation between the HTTP and WebSocket layers, I don't see any way to enable Extended CONNECT in a backwards-compatible way without providing a knob accessible to the WebSocket layer to enable it.

I propose adding such a knob to http.HTTP2Config:

package http

type HTTP2Config {
  // EnableConnectProtocol, if true, enables support for
  // the Extended CONNECT protocol defined in RFC 8441.
  // When enabled, HTTP/2 servers will advertise support for Extended CONNECT.
  // Extended CONNECT requests will include a ":protocol" pseudo header
  // in the request headers.
  EnableConnectProtocol bool
}
moved this from Incoming to Active in Proposalson Feb 13, 2025
rsc

rsc commented on Feb 13, 2025

@rsc
Contributor

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

aclements

aclements commented on Feb 19, 2025

@aclements
Member

Based on the discussion above, this proposal seems like a likely accept.
— aclements for the proposal review group

It turns out that browsers interpret a server advertising Extended CONNECT support as the server also advertising WebSocket-over-HTTP/2 support. This causes problems when net/http supports Extended CONNECT but the WebSocket server implementation does not.

As a result, we have disabled Extended CONNECT by default for Go 1.24. It can still be enabled by setting GODEBUG=http2xconnect=1.

Since enabling Extended CONNECT requires cooperation between the HTTP and WebSocket layers, I don't see any way to enable Extended CONNECT in a backwards-compatible way without providing a knob accessible to the WebSocket layer to enable it.

The proposed solution is to add such a knob to http.HTTP2Config:

package http

type HTTP2Config {
  // EnableConnectProtocol, if true, enables support for
  // the Extended CONNECT protocol defined in RFC 8441.
  // When enabled, HTTP/2 servers will advertise support for Extended CONNECT.
  // Extended CONNECT requests will include a ":protocol" pseudo header
  // in the request headers.
  EnableConnectProtocol bool
}
moved this from Active to Likely Accept in Proposalson Feb 19, 2025
aclements

aclements commented on Feb 26, 2025

@aclements
Member

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.
— aclements for the proposal review group

It turns out that browsers interpret a server advertising Extended CONNECT support as the server also advertising WebSocket-over-HTTP/2 support. This causes problems when net/http supports Extended CONNECT but the WebSocket server implementation does not.

As a result, we have disabled Extended CONNECT by default for Go 1.24. It can still be enabled by setting GODEBUG=http2xconnect=1.

Since enabling Extended CONNECT requires cooperation between the HTTP and WebSocket layers, I don't see any way to enable Extended CONNECT in a backwards-compatible way without providing a knob accessible to the WebSocket layer to enable it.

The proposed solution is to add such a knob to http.HTTP2Config:

package http

type HTTP2Config {
  // EnableConnectProtocol, if true, enables support for
  // the Extended CONNECT protocol defined in RFC 8441.
  // When enabled, HTTP/2 servers will advertise support for Extended CONNECT.
  // Extended CONNECT requests will include a ":protocol" pseudo header
  // in the request headers.
  EnableConnectProtocol bool
}
moved this from Likely Accept to Accepted in Proposalson Feb 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Accepted

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @bradfitz@neild@rsc@mdwn@taoso

        Issue actions

          net/http: add support for SETTINGS_ENABLE_CONNECT_PROTOCOL · Issue #53208 · golang/go