feat(core): add HTTP/2 stream dispatch
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# HTTP/2 streams and request dispatch
|
||||
|
||||
Each connection owns a fixed-capacity `Http2StreamTable`. Client stream identifiers are validated
|
||||
as odd, non-zero and strictly increasing before a stream object is acquired. The table uses
|
||||
primitive open addressing and a bounded object free list; it never grows beyond the advertised 64
|
||||
concurrent streams. An excess request receives `REFUSED_STREAM`, allowing the peer to retry it.
|
||||
|
||||
## State model
|
||||
|
||||
`Http2StreamState` represents `IDLE`, `OPEN`, `HALF_CLOSED_REMOTE`, `HALF_CLOSED_LOCAL` and
|
||||
`CLOSED`. A class-initialized table maps every receive/send event to either its next state or the
|
||||
correct stream error. Frames racing with a recently closed stream follow RFC 9113 §5.1 rather than
|
||||
being rejected uniformly.
|
||||
|
||||
## Header and request model
|
||||
|
||||
Decoded HPACK fields are copied into storage owned by the stream. Before dispatch,
|
||||
`PseudoHeaders` enforces ordering, uniqueness, required request pseudo-fields, lowercase regular
|
||||
names, connection-specific-field rejection, the `te: trailers` exception and host/authority
|
||||
consistency. Pseudo-fields are not exposed as regular headers; `:authority` is also visible as
|
||||
`host` so existing middleware sees the same authority through HTTP/1.1 and HTTP/2.
|
||||
|
||||
The stream assembles the existing protocol-neutral `Request`, `RequestLine`, `RequestBody` and
|
||||
`HeaderView` models. Path/query splitting, routing, middleware, not-found handling and exception
|
||||
handling therefore use the same code as HTTP/1.1. `FastPathRouterImpl` is unchanged.
|
||||
|
||||
## Dispatch and ownership
|
||||
|
||||
The connection thread decodes and validates frames only. Completed bodyless streams are queued in
|
||||
a fixed array while more frame bytes are already buffered, then submitted to the server's shared
|
||||
virtual-thread executor before the demultiplexer waits for the network again. This preserves burst
|
||||
admission semantics without adding a dispatch timer or blocking the connection thread.
|
||||
|
||||
The stream owns its pooled request, response, body, decoded-header arena and response writer.
|
||||
Normal response completion releases it through the serialized writer callback. RST_STREAM marks a
|
||||
queued or running stream cancelled and defers release to that sole owner; setup, routing and handler
|
||||
failures send an appropriate stream reset and release in the failure path. A 100,000-cycle test
|
||||
proves stable pool counts, and an immediate request/reset/request regression test covers reuse
|
||||
while dispatch is pending.
|
||||
|
||||
## Verification
|
||||
|
||||
- Clean Maven build with JMH sources: 618 tests, no failures.
|
||||
- h2spec sections 5 and 8: 37/39. The two remaining cases require request DATA byte accounting and
|
||||
are completed with body flow control.
|
||||
- Java `HttpClient` negotiates HTTP/2 over TLS and runs an existing parameterized route unchanged.
|
||||
- curl prior-knowledge h2c receives a valid `200` response and body.
|
||||
- JMH pooled lifecycle (HPACK decode, request assembly, response write and release):
|
||||
458.499 ns/op, 0.003 B/op, no GC.
|
||||
Reference in New Issue
Block a user