Chapter Orientation
Volume 9 starts at HTTP's foundation layer — no networking, no versions, just the vocabulary every upper layer shares: "what a field looks like, how methods are recognized, how targets are split". xrtHttpFieldParse strictly parses a single header field into borrowed views; xrtHttpTokenNext slices token-lists by HTTP rules; Host splitting and parameter coding handle the URL layer's two kinds of structured text; FieldBlockWrite writes field views back verbatim. Chapter 90's HTTP/1 parser, Chapter 98's xhttp runtime, your own gateways and middlewares — all stand on this set of zero-allocation primitives. After this chapter you will recognize: HTTP header handling's safety and performance are, at heart, three things — strict parsing + borrowed views + capacity atomicity.
89.1引入
What is the innermost operation of writing a reverse proxy? "Change one header and forward" — parse the request head, look up a field by name, change its value or add a field, then write the whole head block back to the byte stream. Sounds simple, but every step carries historical pits: how to treat field-name case? How to slice token-list items with spaces and quotes? Do comments in values (obsoleted by RFC 7230 but still on the wire) blow up the parser? What if the buffer runs out mid-rewrite — write half?
The foundation layer's answer distills into three disciplines. Strict parsing: illegal input is rejected on the spot (never silently repaired) — half the HTTP parsing CVEs in the databases stem from "lenient parsing" turning into ambiguity in an attacker's hands. Borrowed views: parse results are all views of the original input (zero-copy, zero allocation) — a high-concurrency gateway's per-request memory cost approaches zero. Capacity atomicity: a write-back either succeeds completely or touches not one byte — the nastiest intermediate state, "half a message written", is eliminated at the API layer.
89.2概念
The field model: name, value, and borrowed views
xhttpfield is just two views: Name and Value. xrtHttpFieldParse(文本, &字段) (text, &field) strictly parses "name: value" — before the colon is the field name (token character-set validated), after it a value trimmed of surrounding OWS (optional whitespace). The parse result is borrowed: Name.Data/Value.Data point inside the original text — no allocation, no copy. Three companions: xrtHttpFieldCount/FieldFind/FieldNext (search and traversal over field arrays), and xrtHttpFieldBlockWrite(字段数组, 数量, 输出, 容量, &长度) (field array, count, output, capacity, &length) — writing the field array back as a complete "name: value\r\n...\r\n" head block; insufficient capacity returns failure and writes not a single byte (atomicity).
token-list: the iterator of comma culture
HTTP is a comma-separated world: Connection: keep-alive, Upgrade, Accept-Encoding: gzip, deflate, Cache-Control: no-cache, no-store. xrtHttpTokenNext(值视图, &偏移, &token) (value view, &offset, &token) is the standard iterator — splitting on , and ;, skipping whitespace, returning XHTTP_NEXT_ITEM (got one) / XHTTP_NEXT_END (clean end).How parameters ride: in gzip;q=0.5 the q=0.5 is a parameter after the semicolon — the iterator stops at the semicolon, and parameter parsing belongs to the per-field layers (like Chapter 91's TE q-values). This "tokens first, parameters after" model runs through every list field.
Methods, targets, and Host splitting
- Methods:
xrtHttpMethodParserecognizes standard methods (GET/POST/...) and extension methods (custom tokens) — strict token charset;GET;with a separator is rejected outright. - Targets: the four request-target shapes (origin-form
/path?query, absolute-form full URL, authority-form, asterisk-form) are distinguished at the parsing layer; this layer providesxrtHttpTargetParse's shape recognition and path/query splitting. - Host splitting:
xrtHttpHostParse(值, &authority)(value, &authority) yields anxhttpauthorityview (host/port) — handling domains, IPv4, and bracketed IPv6 (the brackets of[2001:db8::1]:8443must pair, the IPv6 literal is validated group by group — ambiguous inputs like a domain-colon-port are never misjudged).
Parameter coding: the two roads of the query string
xrtHttpParamBuild/xrtHttpParamWrite (encoding side) and xrtHttpParamCount/xrtHttpParamNext/xrtHttpParamFind (parsing side) handle a=1&b=2-shaped key-value pairs: encoding escapes arbitrary bytes into percent form, decoding restores. Two disciplines: the semantics of + and %20 — decoding follows the application/x-www-form-urlencoded rules (+ → space); spaces and reserved characters — the encoding side escapes only what is necessary (preserving &/= as structural separators). Forms, query strings, and Cookie values share this set.
The isomorphism with Chapter 25's strings and Chapter 78's DER
You will find this chapter's APIs isomorphic with Chapter 25 (Trim/Cut's view pipeline) and Chapter 78 (the DER cursor): borrowed input → strict validation → view output → convert on demand. It is XRT's library-wide philosophy of text processing — zero-copy runs through to the protocol layer; "parse once, reference everywhere".
The parsing pipeline panorama
89.3示例
First complete program: field parsing, token iteration, and write-back
The program below is from examples/http/base/main.c — the minimal loop of the foundation trio:
#include <stdio.h> #include <xrt.h> */ /* 展示传输无关的字段解析与 token-list 迭代。 */ int main(void) { xhttpfield Field; xhttpnext Next; xstrview Token; char Output[128]; size_t iOffset = 0; size_t iSize; if ( !xrtHttpFieldParse( XRT_STR_LITERAL("Connection: keep-alive, Upgrade"), &Field ) ) { return 1; } while ( (Next = xrtHttpTokenNext( Field.Value, &iOffset, &Token )) == XHTTP_NEXT_ITEM ) { printf("%.*s\n", (int)Token.Size, Token.Data); } if ( (Next != XHTTP_NEXT_END) || !xrtHttpFieldBlockWrite( &Field, 1, Output, sizeof(Output), &iSize ) ) { return 2; } printf("%.*s", (int)iSize, Output); return 0; }
What just happened. (1) FieldParse parses one field into borrowed views (Name=Connection, Value=keep-alive, Upgrade — the leading whitespace after the colon is trimmed). (2) The TokenNext loop slices two tokens: keep-alive and Upgrade — the offset is held by the caller, the iterator stateless; NEXT_END is a clean end, not an error. (3) FieldBlockWrite writes the field back verbatim as a complete head block — note the canonical form after the colon in the output (one space): parsing normalized the OWS, and write-back yields the canonical shape — after a proxy "reads, modifies, writes", the on-the-wire bytes are deterministic. When capacity runs short it writes not one byte — atomicity lets you safely "try a small buffer first, swap in a bigger one on failure".
Second complete program: the strictness of Host splitting
The second program is from examples/http/host/main.c — the standard split of bracketed IPv6 and port:
#include <stdio.h> #include <xrt.h> */ /* 解析 Host 字段并直接读取借用的主机与端口视图。 */ int main(void) { xhttpauthority Host; uint16 iPort; if ( !xrtHttpHostParse( XRT_STR_LITERAL("[2001:db8::1]:8443"), &Host ) || !xrtHttpAuthorityPort(&Host, 80u, &iPort) ) { return 1; } printf( "host=%.*s port=%u\n", (int)Host.Host.Size, Host.Host.Data, (unsigned)iPort ); return 0; }
What just happened. (1) Input [2001:db8::1]:8443 splits into host 2001:db8::1 and port 8443 — brackets validated in pairs, the IPv6 literal checked group by group. (2) Strictness boundaries: example.com:8443 (domain with port) is legal; 2001:db8::1 (bare IPv6 without brackets plus an intended port) is ambiguous — the parser refuses rather than guesses.Why so fussy: Host/Authority feeds routing and identity decisions (Chapter 80's SAN matching of IP shapes, a proxy's target selection); ambiguous parsing means founding routing decisions on an understanding that differs from the peer's. The companion examples/http/field_tour (3 output lines) tours the complete API surface of field-block counting, finding, token iteration, and write-back — this chapter's "executable cheat sheet".
89.4契约
- Borrowed views: parse results of fields/methods/targets/Host all borrow the original input; valid while the input lives (Chapter 15's view rules).
- Strict parsing: illegal token characters, ambiguous Hosts, unpaired brackets, illegal field-name characters — all rejected on the spot; no repairing, no guessing.
- Capacity atomicity: write-back interfaces like
FieldBlockWritewrite zero bytes on insufficient capacity; the length is published only on success — no half messages exist. - Token iteration: split on
,/;, skip whitespace;NEXT_ITEM/NEXT_ENDtwo states; the iterator holds no internal state (the offset belongs to the caller). - OWS normalization: parsing trims surrounding whitespace of values; write-back produces the canonical shape ("name: value").
- Host semantics: domain/IPv4/bracketed IPv6; port optional; ambiguous input rejected.
- Parameter coding:
+decodes by form semantics; encoding escapes only necessary characters (&/=structure preserved). - Zero allocation: every API in this chapter allocates no heap memory — per-request cost is predictable.
- Trimming:
XRT_MODULE_HTTPis independent; subdomains likeHTTP_HOST/HTTP_PARAM/HTTP_TARGETtrim independently.
Common Mistakes
Symptom: hand-writing a strchr(',') loop for Accept-Encoding: gzip, deflate, br — slicing wrong on quoted tokens (filename="a,b.txt") or empty items (a,,b).
Cause: HTTP list comma semantics carry escaping rules for quotes and comments; bare strchr knows none of them. TokenNext has the rules built in — list handling always goes through it.
char* p = strtok(Value, ","); /* the comma inside quotes gets cut */ while ( p ) { handle(p); p = strtok(NULL, ","); }
size_t iOffset = 0; xstrview Token; while ( (Next = xrtHttpTokenNext(Value, &iOffset, &Token)) == XHTTP_NEXT_ITEM ) { handle(Token); /* quote/whitespace/parameter boundaries handled by the iterator */ }
Symptom: a homegrown proxy uses an "append-style" rewrite — the buffer fills midway: the first half already sent, the second dropped; the peer receives a truncated head block and the connection is wrecked.
Cause: a writing style without capacity-atomicity awareness. An HTTP head block is one complete unit; half a unit is a protocol error.
size_t iUsed = 0; for ( each field ) { iUsed += snprintf(Out + iUsed, Cap - iUsed, "%.*s: %.*s\r\n", ...); /* capacity runs out midway: half a head block */ }
if ( !xrtHttpFieldBlockWrite(Fields, iCount, Out, sizeof(Out), &iSize) ) { /* swap in a bigger buffer or a chunked strategy - Out stays untouched (zero bytes written) */ } send(Out, iSize);
Symptom: passing the raw value of Host: [2001:db8::1]:8443 to a connect function fails or connects to the wrong port; domains differing only in case treated as different hosts (cache-key splits).
Cause: a Host value is structured text, not a bare hostname — brackets, port, and case normalization must be split off first. Only after HostParse yields host and port do you have usable routing/connection input.
connect(g_Target /* the raw Host value used directly */, ...); /* a raw value with brackets and port is not a hostname */
xhttpauthority Authority; if ( !xrtHttpHostParse(Field.Value, &Authority) ) { return reject("malformed host"); } normalize case (domains are case-insensitive) before routing/cache keys */
Programming Exercises
Run both samples, base and field_tour, annotating line by line which API each output line verifies. Then change base's input to Connection: keep-alive,, Upgrade (an empty item) and observe the iterator's behavior. Acceptance criteria: you can explain whether the empty item is "skipped" or "yields an empty token"; field_tour's three lines all green.
Implement rewrite_host(字段数组, 数量, 新主机, 输出, 容量, &长度) (field array, count, new host, output, capacity, &length): find the Host field by name (case-insensitive), replace its value, leave the rest as-is, then FieldBlockWrite the block back. Acceptance criteria: an explicit failure when Host is absent; the rewritten block byte-for-byte canonical; zero writes on insufficient capacity (verify with an 8-byte buffer).
Read a complete request head (multiple lines) into a field array (your own loop + FieldParse), then tally: total fields, duplicated names, Connection's token set, the Host split result, query-string parameter count (ParamDecode). Rewrite "X-Forwarded-For" on the same input and write the block back. Acceptance criteria: zero heap allocations throughout (Chapter 6's statistics); malformed input (field name with a space, ambiguous Host) rejected with the first error position reported.
Summary
| Topic | Quick reference |
|---|---|
| Foundation disciplines | strict parsing (no repair) + borrowed views (zero-copy) + capacity atomicity (no half messages) |
| Field model | xhttpfield = two views Name/Value; Parse trims OWS, validates the token charset |
| Write-back | FieldBlockWrite turns a field array into a canonical head block; insufficient capacity writes zero |
| Token iteration | TokenNext splits on ,/;; NEXT_ITEM/NEXT_END; the offset belongs to the caller |
| List parameters | parameters after the semicolon (q-values etc.) belong to the per-field layers; the token iterator stops at the semicolon |
| Host split | HostParse yields xhttpauthority; domain/IPv4/bracketed IPv6; ambiguous rejected |
| Method/target | standard + extension methods token-validated; target four-shape recognition with path/query split |
| Parameter coding | +→space (form semantics); encoding escapes only the necessary; &/= structure preserved |
| Zero allocation | no heap allocation in the whole chapter - per-request cost predictable |
| Trimming | XRT_MODULE_HTTP independent; the fields subdomain trims independently |
📖 API references in this chapter:http (72 entries)http_connection (7 entries)
Click for full signatures, parameter constraints, return values and error notes