Summary
The hand-written HTTP parser never chunk-decodes request bodies: BodyType::Chunked (src/http/request.rs) reads the raw stream in fixed 1024-byte read_exact blocks until EOF. With a finite payload the client keeps the connection open (keep-alive, no half-close), the final block never fills, and the server blocks forever instead of answering.
Impact
Any client that sends Transfer-Encoding: chunked stalls after its first payload. Concrete case: librespeed/speedtest-cli#122 — librespeed-cli v1.0.13+ uploads at 0.55 Mbps against librespeed-rs 1.4.0:
- The Go client wraps the upload body in a counting
io.TeeReader which hides its length, so it sends chunked.
- Server log from a replica of this body handling:
CHUNKED: read 1048837 raw bytes, then EOF/blocked — 1024×1024 payload + 261 bytes of chunk framing; 1048837 is not a multiple of 1024, so the last read_exact(1024) blocks waiting for 763 more bytes that never come.
- Upload rate = one payload / default 15 s duration = 1 MiB / 15 s ≈ 0.55 Mbps.
Same stall applies to curl -H "Transfer-Encoding: chunked" --data-binary @file and any chunked uploader. Content-Length requests (BodyType::Fixed) work fine.
Suggested fix
Parse chunked encoding in the Chunked branch (RFC 9112 §7.1): read the chunk-size line, the chunk data, and the terminating CRLF per chunk; stop at a 0-size chunk and consume the trailer section. Tokio's BufReader (already in use) provides lines() and read_exact for this.
Client-side note
librespeed/speedtest-cli now sends Content-Length for its fixed-size uploads (librespeed/speedtest-cli#143) as a workaround, but fixing chunked handling here makes the server robust for all clients.
Summary
The hand-written HTTP parser never chunk-decodes request bodies:
BodyType::Chunked(src/http/request.rs) reads the raw stream in fixed 1024-byteread_exactblocks until EOF. With a finite payload the client keeps the connection open (keep-alive, no half-close), the final block never fills, and the server blocks forever instead of answering.Impact
Any client that sends
Transfer-Encoding: chunkedstalls after its first payload. Concrete case: librespeed/speedtest-cli#122 — librespeed-cli v1.0.13+ uploads at0.55 Mbpsagainst librespeed-rs 1.4.0:io.TeeReaderwhich hides its length, so it sends chunked.CHUNKED: read 1048837 raw bytes, then EOF/blocked— 1024×1024 payload + 261 bytes of chunk framing; 1048837 is not a multiple of 1024, so the lastread_exact(1024)blocks waiting for 763 more bytes that never come.Same stall applies to
curl -H "Transfer-Encoding: chunked" --data-binary @fileand any chunked uploader.Content-Lengthrequests (BodyType::Fixed) work fine.Suggested fix
Parse chunked encoding in the Chunked branch (RFC 9112 §7.1): read the chunk-size line, the chunk data, and the terminating CRLF per chunk; stop at a 0-size chunk and consume the trailer section. Tokio's
BufReader(already in use) provideslines()andread_exactfor this.Client-side note
librespeed/speedtest-cli now sends
Content-Lengthfor its fixed-size uploads (librespeed/speedtest-cli#143) as a workaround, but fixing chunked handling here makes the server robust for all clients.