2.9 KiB
HTTP/2 bodies and flow control
HTTP/2 applies flow control independently to the connection and to every stream. Flash advertises a 1 MiB receive window at both levels and sends WINDOW_UPDATE only after the application has consumed at least half a window. A DATA frame decrements both windows by its complete payload length, including the pad-length byte and padding; only its unpadded data reaches the handler.
Request bodies
Known bodies up to 64 KiB remain in one reusable contiguous stream buffer. Their handler is
dispatched at END_STREAM, and RequestBody.bytes() performs the only allocation: the byte array
returned to application code. For a 1,024-byte body JMH reports exactly 1,040 B/op, the array plus
its object header, with no framework allocation around it.
Larger or unknown-length bodies dispatch after request headers. DATA is copied out of the frame
reader into a connection-owned pool of 64 reusable 16 KiB buffers. Small adjacent frames coalesce
inside a buffer, so the pool is bounded by bytes rather than frame count. The existing
RequestBody.stream() blocks only the handler's virtual thread when data is absent. Buffers return
to the pool as reads consume them, and that consumption reopens both receive windows. If a handler
does not read its body, the normal post-handler drain performs the same bounded consumption.
The connection window and pool both cover exactly 1 MiB, so the peer can never hold more credit
than the server can store before backpressure takes effect. Per-stream accepted body bytes remain
bounded by MAX_REQUEST_BODY_SIZE. Declared content length is parsed without a String and checked
against the unpadded DATA total at END_STREAM.
Responses
Fixed byte arrays, known-length streams and unknown-length streams all use one resumable
Http2ResponseWriter. It emits DATA frames no larger than the peer's frame limit, the available
connection window, the available stream window and the reusable 16 KiB relay buffer. A
WINDOW_UPDATE schedules the stream on the shared virtual-thread executor; application streams are
never read by the demultiplexer.
Response.chunked(InputStream) means unknown-length streaming at the application API. HTTP/2 has
no chunked transfer coding, so Flash emits ordinary DATA followed by END_STREAM and never sends a
transfer-encoding field. Response.stream(InputStream, length) emits content-length and fails
the stream if the source ends before that length.
Verification
- A real Java HTTP/2 client uploads and downloads 100 MiB over TLS; both directions are validated byte-for-byte without materializing the test payload.
- A synthetic 100 MiB response proves serialized scratch storage stays below 64 KiB.
- h2spec sections 5, 6.1, 6.9 and 8: 50 passed, one h2spec-skipped case, zero failures.
- Clean Maven build with JMH sources: 633 tests, no failures.
- JMH request streaming: 159.408 ns/op, 0.001 B/op, no GC.
- JMH response streaming frame: 219.090 ns/op, 0.002 B/op, no GC.