Commits
Tarun Sukhani authored a5bc3c573d1
feat(websocket): WebSocket over HTTP/2 via Extended CONNECT (PF-157) Implements RFC 8441 so a WebSocket can be established on an h2 stream with Extended CONNECT, instead of the client opening a second HTTP/1.1-over-TLS connection for wss://. Http2OrHttp1Negotiator now advertises SETTINGS_ENABLE_CONNECT_PROTOCOL. Note that Netty never reads that setting back — no codec in netty-codec-http2 gates inbound validation on it, and :protocol is accepted as a pseudo-header unconditionally — so the setting is purely the advertisement RFC 8441 section 3 requires and every correctness check is ours. Detection has to happen on raw frames, above Http2StreamFrameToHttpObjectCodec. That codec destroys exactly what Extended CONNECT carries: HttpConversionUtil returns :authority rather than :path as the URI when the method is CONNECT, and its header translator silently drops every pseudo-header lacking an HTTP/1.x mapping — both :protocol and :path. A request arriving through it would have no path left to route on. Http2ExtendedConnectHandler therefore sits at the head of the stream pipeline and removes itself on the first frame of any ordinary stream, so the plain h2 request path is unchanged in steady state. None of Netty's WebSocket handshake machinery applies either: RFC 8441 drops the Sec-WebSocket-Key/Accept exchange, which is the only thing WebSocketServerHandshaker exists to compute. Only the frame codec is reused, driven over the stream's DATA frames by a bridge kept protocol-agnostic in ExtendedConnectWebSocketHandler so the h3 story (RFC 9220) can reuse it. PlayHandler keeps its HTTP/1.1 behaviour; websocketHandshake is split so the route-resolve-and-dispatch tail is shared, and closeWebSocket gains a handshaker-free branch that writes the 1000 Normal Closure directly — without it h2/h3 peers would observe 1006 Abnormal Closure. Tested by a hand-rolled Netty h2 client (no Java WebSocket library implements RFC 8441): text and binary echo end to end through a WebSocketController, the setting advertisement, a 404 for an unrouted path, the close handshake, and a plain GET multiplexed on the same connection. HTTP/1.1 ws:// and wss:// are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>