RinhaDeBackend::HttpParser
100% Crystal HTTP/1.x request parser. Drop-in replacement for the
phr_parse_request we used from h2o/picohttpparser — same external
contract (pointer/length out-params, last_len slowloris fast-path,
status codes: bytes consumed >= 0, -2 partial, -1 malformed).
Why we replaced the C version:
- kills the FFI surface and the cc step in the build,
- the SSE4.2 fast paths in upstream picohttpparser amortize over
long URIs and long header values; the Rinha workload sees
/fraud-score(12 B) and ~10 short headers from the k6 client, - we trust the rinha test harness to produce syntactically-valid
bytes (the prod LB is HAProxy in
mode tcp, byte-passthrough, so the parser sees client bytes verbatim), so we skip the per-byte tchar / CTL / DEL validation upstream picohttpparser does and just locate the structural delimiters (:, SP, CR, LF) via libcmemchr. Glibc/musl memchr is heavily SIMD-tuned (SSE4.2 / AVX2 on x86_64), winning on every input length we care about.
Out of scope vs upstream (we never exercise these in this server):
- response parsing (we never act as an HTTP client),
- chunked transfer decoding (k6 always sends Content-Length),
- obs-fold / multi-line header continuation (RFC 7230 obsoleted it and well-formed clients don't emit it).
Trust assumptions (i.e. things this parser does NOT detect):
- non-token bytes inside a header name,
- control bytes (other than CR/LF/HT) inside a header value,
- control bytes inside the path. The Rinha test harness is well-formed; if it ever stops being so we would surface the issue downstream as a malformed JSON and answer the safe 200/0.0 fallback in HttpServer#handle_fraud_score.
Constants
Class methods
Top-level entry point. Drop-in for phr_parse_request.
Returns:
= 0 : number of bytes consumed (request-line + headers, body starts at this offset) -2 : input is partial, caller should keep reading -1 : malformed
Out-params populated on success: method.value, method_len.value -> METHOD slice path.value, path_len.value -> request-target slice minor_version.value -> 0 for HTTP/1.0, 1 for HTTP/1.1 headers[0..num_headers.value-1] -> name/value slices num_headers.value -> count of headers parsed