feat(core): add WebSocket over HTTP/2
This commit is contained in:
@@ -1042,3 +1042,25 @@ general-purpose stack.
|
||||
bounded pool or client-side multiplexing without changing the proxy-facing API.
|
||||
|
||||
---
|
||||
|
||||
## DEC-32 — Reuse the WebSocket router and session for extended CONNECT
|
||||
|
||||
**Context.** RFC 8441 changes the HTTP handshake and transport framing, but not the application
|
||||
route, RFC 6455 message semantics, or handler lifecycle. Introducing an HTTP/2-specific router,
|
||||
handler, or session would duplicate public and internal behavior.
|
||||
|
||||
**Decision.** Validate CONNECT and `:protocol` at the HTTP/2 wire boundary, then expose a
|
||||
`websocket` extended CONNECT as GET only while resolving the existing `AbstractWsRouter` route.
|
||||
Feed request DATA to the existing `WebSocketSession` and adapt the protocol-neutral
|
||||
`ResponseStream` to its `OutputStream` contract. Publish response HEADERS in their own first batch
|
||||
so the full-duplex producer cannot block the handshake while waiting for request DATA.
|
||||
|
||||
**Consequence.** One `ws(path, handler)` registration behaves the same on HTTP/1.1 and HTTP/2;
|
||||
masking, fragmentation, callbacks, and close handling have one implementation. HTTP/2 contributes
|
||||
only pseudo-header validation and DATA flow control, while the shared response bridge remains
|
||||
usable by other streaming adapters.
|
||||
|
||||
**Revisit when.** Only if a future WebSocket transport cannot be represented by the existing
|
||||
stream pair without losing protocol semantics.
|
||||
|
||||
---
|
||||
|
||||
@@ -76,7 +76,7 @@ Status values: `not started` / `in progress` / `blocked` / `done`.
|
||||
| 12 — Trailers, half-close, gRPC | done | `feature/core/http2` | Protocol-neutral request/response trailers, bounded push streaming, four half-close orderings and authority-form CONNECT tunnels complete. Real grpcurl 1.9.3 unary/server-streaming/error interop passes. EX-48/49 fixed. HTTP/2 remains opt-in until the Phase 13 hostile-peer gate (DEC-29). 649/649 tests green from a clean `-Pjmh` build. |
|
||||
| 13 — Security hardening & abuse resistance | done | `feature/core/http2` | Two-bucket Rapid Reset/stream/settings/ping/aggregate counters, control/write queue bounds, optional stream/byte/lifetime budgets, absolute header and idle-stream deadlines, and hostile-peer suite complete. Security review found+fixed EX-50/51. JMH counter: 38.083 ns/op, ~10^-4 B/op, no GC. 100k-CONTINUATION attack terminates in under 2 s with bounded retained heap. 663/663 tests green from a clean `-Pjmh` build. |
|
||||
| 14 — h2c prior knowledge + proxy support | done | `feature/core/http2` | Independent TLS/h2c gates, pooled proxy-oriented h2 client with TLS ALPN and h2c, bidirectional h1/h2 trailer relay, shared four-direction hop-by-hop policy and certificate-backed 421 handling complete. Real grpcurl h2c interop passes. 670/670 tests green from a clean `-Pjmh` build. |
|
||||
| 15 — RFC 8441 extended CONNECT (WS over h2) | not started | — | — |
|
||||
| 15 — RFC 8441 extended CONNECT (WS over h2) | done | `feature/core/http2` | SETTINGS_ENABLE_CONNECT_PROTOCOL, shared WS router/session, DATA flow control, >1 MiB message, h1/h2 parity and lifecycle hardening complete. EX-52/53 fixed; DEC-32 recorded. 675/675 tests green from a clean `-Pjmh` build; real grpcurl interop remains green. |
|
||||
| 16 — Compliance test suite | not started | — | — |
|
||||
| 17 — Benchmarks, allocation gates, tuning | not started | — | — |
|
||||
| 18 — Documentation | not started | — | — |
|
||||
@@ -813,6 +813,25 @@ stream retirement atomic in `Http2StreamTable` and require both the expected str
|
||||
identity to match the live table entry. A regression test proves that a stale retirement cannot
|
||||
remove the next generation of the same pooled object. **Phase**: 13.
|
||||
|
||||
### EX-52 — WebSocket `onOpen` failures bypassed lifecycle cleanup
|
||||
|
||||
Found while routing extended CONNECT through the existing WebSocket loop. `onOpen` ran before the
|
||||
loop's `try/finally`, and runtime failures from application callbacks were not handled alongside
|
||||
I/O failures. An exception could therefore escape without `onError`, `onClose`, or guaranteed
|
||||
transport release. **Fix**: include `onOpen` and all callback dispatch in the guarded lifecycle,
|
||||
report runtime failures, and force-close in a nested `finally` even if `onClose` fails.
|
||||
`WebSocketLoopTest` is the regression test. **Phase**: 15.
|
||||
|
||||
### EX-53 — Push-streaming HTTP/2 responses could deadlock before response headers
|
||||
|
||||
Found in the first live extended-CONNECT test. `Http2ResponseWriter.startFlowControlled` tried to
|
||||
read the first push-streaming body byte while constructing the same batch as the response HEADERS.
|
||||
A full-duplex producer waiting for request DATA therefore blocked before the client could receive
|
||||
the successful response and send that DATA. **Fix**: publish push-streaming HEADERS as the first
|
||||
batch and start body reads only from the post-write resume batch. `WebSocketOverH2Test` proves the
|
||||
handshake completes before sending a message and then carries a message beyond the flow window.
|
||||
**Phase**: 15.
|
||||
|
||||
---
|
||||
|
||||
# PART III — The phases
|
||||
@@ -2956,8 +2975,9 @@ defines the h2 mechanism.
|
||||
- `flash/docs/http2/WEBSOCKET.md`.
|
||||
|
||||
### DoD
|
||||
- [ ] A browser negotiating h2 can open a WebSocket to a Flash `ws()` route.
|
||||
- [ ] `AbstractWsRouter` and `FastPathWsRouterImpl` unchanged.
|
||||
- [x] An RFC 8441 client negotiating h2 can open a WebSocket to a Flash `ws()` route
|
||||
(`WebSocketOverH2Test`; the release-browser matrix remains Phase 16 scope).
|
||||
- [x] `AbstractWsRouter` and `FastPathWsRouterImpl` unchanged.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# WebSockets over HTTP/2
|
||||
|
||||
Flash implements RFC 8441 extended CONNECT alongside the existing HTTP/1.1 WebSocket upgrade.
|
||||
Both transports resolve the same `ws(path, handler)` registration through `AbstractWsRouter` and
|
||||
run the same `WebSocketSession`, frame parser, handler callbacks, and close lifecycle.
|
||||
|
||||
## Protocol negotiation
|
||||
|
||||
Every HTTP/2 server connection advertises `SETTINGS_ENABLE_CONNECT_PROTOCOL` (`0x8`) with value
|
||||
`1`. A WebSocket request uses this pseudo-header shape:
|
||||
|
||||
```text
|
||||
:method CONNECT
|
||||
:protocol websocket
|
||||
:scheme https # or http
|
||||
:authority example.com
|
||||
:path /live
|
||||
```
|
||||
|
||||
The normal HTTP/1.1 upgrade fields (`Connection`, `Upgrade`, `Sec-WebSocket-Key`, and
|
||||
`Sec-WebSocket-Accept`) are neither required nor permitted on this path. A matched route receives
|
||||
status `200`; a missing route receives `404`.
|
||||
|
||||
## Shared application behavior
|
||||
|
||||
At the router boundary, an extended CONNECT for `websocket` is represented as a GET so the
|
||||
existing WebSocket router can be reused without a second registration table or protocol-specific
|
||||
handler API. The wire validator retains the original CONNECT semantics and rejects malformed
|
||||
pseudo-header combinations before dispatch.
|
||||
|
||||
Request DATA is exposed through the existing streaming `RequestBody`. WebSocket output passes
|
||||
through the common push-style `ResponseStream`, so HTTP/2 stream and connection flow-control
|
||||
windows apply without changing the WebSocket codec. Messages may cross any number of DATA-frame
|
||||
boundaries; those boundaries are invisible to RFC 6455 framing. Client-to-server masking remains
|
||||
mandatory and is validated by the same frame parser used for HTTP/1.1.
|
||||
|
||||
## Lifecycle and backpressure
|
||||
|
||||
Response HEADERS are sent before the push producer is allowed to wait for request DATA. This is
|
||||
required for a full-duplex protocol: waiting for the first WebSocket frame before publishing the
|
||||
successful CONNECT response would deadlock compliant clients. Subsequent response batches block
|
||||
behind the bounded response bridge and resume when HTTP/2 flow-control credit becomes available.
|
||||
|
||||
Handler failures from `onOpen` or `onMessage` are reported through `onError`; `onClose` is invoked
|
||||
once and the transport is released even if the close callback itself fails.
|
||||
|
||||
## Verification
|
||||
|
||||
`WebSocketOverH2Test` exercises the extended CONNECT exchange, fragmented text, masking, graceful
|
||||
close, and a binary message larger than the initial one-mebibyte stream window.
|
||||
`WebSocketParityTest` sends the same message through one route and handler over HTTP/1.1 and
|
||||
HTTP/2 and compares the result byte for byte.
|
||||
Reference in New Issue
Block a user