Skip to content

Conversation

@mafredri
Copy link
Member

@mafredri mafredri commented Sep 29, 2025

This change adds experimental support for WebSockets over HTTP/2 (RFC 8441).

Implementation notes:

  • To avoid breaking any flows, HTTP/2 functionality is currently opt-in on both server and client
  • The HTTP/2 tests are currently kept in internal/thirdparty/http2 (they require importing golang.org/x/net/http2, see net/http: move HTTP/2 into std golang/go#67810)
  • HTTP/2 extended CONNECT must currently be enabled via GODEBUG, see net/http: add support for SETTINGS_ENABLE_CONNECT_PROTOCOL golang/go#53208
  • Explicit selection of HTTP/1 or HTTP/2 is required during Dial because we cannot test the underlying transport for support (for instance, http.Transport does not allow setting the HTTP/2 pseudo-header :protocol)
  • To Dial with HTTP/2 support, a http2.Transport must be provided via DialOptions.Client (since we do not import golang.org/x/net/http2)
  • There are a few changes and cleanups unrelated to this feature, if needed they can be broken out into a separate branch

Closes #4


This change does not yet include benchmark tests for HTTP/2, those will be added at a later date as a follow-up.

@mafredri mafredri force-pushed the mafredri/http2 branch 11 times, most recently from 469125f to 9470379 Compare September 29, 2025 18:59
@mafredri mafredri self-assigned this Sep 29, 2025
@mafredri mafredri marked this pull request as ready for review October 1, 2025 13:09
@mafredri mafredri requested a review from code-asher October 1, 2025 13:10
@mtojek mtojek self-requested a review October 1, 2025 14:06
Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice 🤌 I messed around with the example as well and it all looks good.

Copy link
Member

@mtojek mtojek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing skills 👍 👍

Have you tried to establish a connection using other tools? JS possibly?

protocol.go Outdated

// ProtocolHTTP1 selects HTTP/1.1 GET+Upgrade for the WebSocket handshake.
// This is the default (zero value).
ProtocolHTTP1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This model will work until one day we will need to switch to HTTP3.99 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProtocolHTTP3_99 😂

accept.go Outdated
}); ok {
ginWriter.WriteHeaderNow()
switch opts.Protocol {
case ProtocolHTTP2:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we support http.UnencryptedHTTP2?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know about this, I'll need to investigate. Thanks for the heads up 👍🏻

Copy link
Member Author

@mafredri mafredri Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this. Server-side already works with native h2c since the code checks r.ProtoMajor == 2, which handles both approaches the same way. The GODEBUG=http2xconnect=1 env is still needed though.

Client-side is the main issue. I tested Go 1.25, and the native http.Transport still rejects the :protocol pseudo-header, so the http2.Transport from x/net is needed.

Looks like full stdlib support is targeted for Go 1.26 (golang/go#67810), we can revisit then.

@code-asher
Copy link
Member

code-asher commented Oct 3, 2025

Have you tried to establish a connection using other tools? JS possibly?

While I was testing I experimented with connecting to the server example from a JS (Node) client. Here is what I used for anyone interested (needs ws).

const http2 = require("http2")
const { Receiver, Sender } = require("ws")

const client = http2.connect("http://127.0.0.1:8080")

const req = client.request({
 ":method": "CONNECT",
 ":protocol": "websocket",
 ":scheme": "http",
 ":path": "/",
 ":authority": "127.0.0.1",
 "Sec-WebSocket-Key": "AQIDBAUGBwgJCgsMDQ4PEC==",
 "Sec-WebSocket-Version": "13",
})

req.on("response", (headers) => {
 console.log("status:", headers[":status"])
})

req.on("error", (err) => {
 console.log("error:", err)
})

const receiver = new Receiver()
receiver.on("message", (data) => {
 console.log("got message:", data.toString())
})

req.on("data", (chunk) => {
 receiver.write(chunk)
})

setInterval(() => {
 const sender = new Sender(req)
 sender.send("hey", { binary: false, mask: true, fin: true })
}, 1000)

sample output:

status: 200
got message: hey
got message: hey
got message: hey

- Fix 'exampels' typo in CI workflow comments
- Fix README paths to use 'internal/examples/http2'
- Change visibleAddr to display 0.0.0.0 for all-interface bindings
mafredri added a commit that referenced this pull request Dec 16, 2025
Remove bench-dev and test-dev jobs from daily workflow since the dev
branch no longer exists.

Add ci/static.sh script to generate coverage badge for GitHub Pages.

Refs #539
mafredri added a commit that referenced this pull request Dec 16, 2025
Add resp.ProtoMajor check in verifyServerResponseH1 for consistency
with verifyServerResponseH2 which already performs this check.

Refs #539
Rename the Protocol type to HTTPProtocol for consistency with existing
HTTPClient and HTTPHeader field naming in DialOptions.

Constants are also renamed:
- ProtocolHTTP1 → HTTPProtocol1
- ProtocolHTTP2 → HTTPProtocol2
- ProtocolAcceptAny → HTTPProtocolAny
Simplify the TLS example by requiring -cert and -key flags instead of
auto-generating a self-signed certificate. This reduces complexity and
keeps the example focused on HTTP/2 WebSocket usage.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WebSockets over HTTP/2

4 participants