A response before the upload ends
HTTP/2 lets a server answer a request before it has read all of it, and then ask the client to stop sending. Here is what that looks like on the wire, a server you can try it against, and a capture to check your parser with offline.
What the standard allows
RFC 9113, section 8.1: when a response does not depend on the rest of the request, a server can send the whole
response before the client has finished sending. It can then send RST_STREAM with the error code
NO_ERROR, which asks the client to stop sending without treating anything as an error.
The standard is explicit about the client’s side: “Clients MUST NOT discard responses as a result of receiving such a RST_STREAM”.
So on one stream you see the request’s headers and some of its body, then the complete response, then the reset. Pieces of the upload that were already on their way can still arrive after the reset.
Why it catches parsers out
Usually the request has finished before the response starts, and code written for that order can come to depend on it. A
parser that waits for the request to end before it reads the response never reads this one, because the response
arrives before the request has ended. A parser that reads every RST_STREAM as a failure reports a complete response
as broken. Neither mistake is obvious in a log.
Test it live
Sea trials serves this case at h2.lab.shorenet.co.uk/f/h2-early-response.
It answers a POST at once, with a 200 and the text answered before the upload ended,
without reading the upload. If the upload is still going, the stream then ends with
RST_STREAM(NO_ERROR).
The easiest way to see it is in a browser:
h2.lab.shorenet.co.uk/t/h2-early-response
streams an upload in eight pieces of 16 KiB, half a second apart, and gets its answer while the rest is still being sent.
From a terminal, send an upload large enough to still be going when the answer comes back, and keep the TLS keys so you can read your capture afterwards:
head -c 33554432 /dev/zero > big.bin
SSLKEYLOGFILE=keys.txt curl --http2 --data-binary @big.bin https://h2.lab.shorenet.co.uk/f/h2-early-response
Record the traffic while it runs, and give your packet analyser keys.txt as its TLS key log. The
servers are rate limited and may be slow or offline; Sea trials lists the limits and the
other cases.
Reproduce it offline
A live server shows you what happens today. A capture lets you check the same case again, offline, and get the same answer every time. The Chart locker has a synthetic capture of this exchange: cleartext HTTP/2 on port 80, so there are no keys to load. Its server answers with a 413 rather than a 200, and two more pieces of the upload follow the reset.
A correct reading finds these seven frames on stream 1, in this order:
| From | Frame | Carries |
|---|---|---|
| client | HEADERS | POST /upload |
| client | DATA | first-chunk-of-the-upload |
| server | HEADERS | :status 413 |
| server | DATA, END_STREAM | payload too large |
| server | RST_STREAM | NO_ERROR |
| client | DATA | second-chunk-sent-after-the-response |
| client | DATA, END_STREAM | tail |
Your parser passes if it keeps the whole 413 response and every piece of the upload, including the two that
arrive after the reset. Download
h2-early-response.pcapng and its expected output,
h2-early-response.expected.json. The
Chart locker lists each file’s SHA-256 and describes the expected-output format
field by field.
Test live, reproduce offline
That pairing runs through this site. Sea trials gives you servers that behave in unusual ways on purpose, to test against live. The Chart locker gives you captures with the expected output written down, to reproduce a case offline and check your fix against.