Repository navigation
feat(web): report response headers in wire order for HTTP/1.1 - #285
Conversation
`uvp_http_request` reported only a normalised map, which loses three things a proxy-shaped consumer needs: repeated lines collapse into one comma-joined value, names are lower-cased, and the order is gone. A response with two `Set-Cookie` lines could not be reconstructed from the result. The HTTP/1.1 client now keeps Node's `rawHeaders` (spelling, order, duplicates) and the result carries them as `headers_raw`; `headers` stays the convenience view. The field is absent when the transport has no raw list: Node's HTTP/2 client folds the response before uvp sees it, and the HTTP/3 probe reports the headers it parsed. Absence is that fact rather than an empty list. The `body.bytes` half of the issue is not changed: the owner's review could not reproduce it — the field is a number on every transport. Assisted-by: Claude Code (Antruly-dev-1)
Review at
|
|
🤖 Antruly-dev-1 · AI Engineer 1 · agent:dev-ai-1 Thanks — the CLI reproduction is the observation I could not make from the lane, and it confirms the ordering and duplicates exactly. On the non-blocking note: agreed that the human renderer should keep showing the map. |
|
Approved. Reviewed head
Merging with --match-head-commit. |
Summary
uvp_http_requestreported only the normalisedheadersmap. A response withtwo
Set-Cookielines came back as one comma-joined value, names werelower-cased, and the order was gone — so a proxy-shaped consumer could not
reconstruct the response.
The HTTP/1.1 client now reports
headers_raw: the response headers in wireorder, one entry per line, with the peer's spelling and duplicates kept.
headersstays the convenience view.Fixes #277
Problem and expected behavior
Smallest trigger, as the owner reproduced it at
bc17f63: a local server sendingtwo
Set-Cookieand twoX-Dupelines yieldsExpected: the ordered, duplicate-preserving list beside it, so a consumer can
put the response back together.
Change and scope
application/httpClient.ts:HttpExchange.rawHeaderscarries the HTTP/1.1response's
rawHeadersas{name, value}entries. HTTP/2 sets it toundefined— Node folds the response before uvp sees it — and the HTTP/3probe and a WebSocket handshake report only the parsed map, so they do the same.
application/WebService.ts/httpToolOutput.ts: the result carriesheaders_rawwhen the transport exposed a raw list.contracts/webToolContracts.ts: the output gains optionalheaders_raw,documented as transport-dependent, and
docs/public/tool-catalog.jsonisregenerated.
tests/conformance/fixtures/fixtureServer.mjs: a/duplicate-headersroutewritten as raw header pairs (the only form Node sends without folding).
tests/conformance/httpBehaviour.test.ts: one case asserts order, spelling andduplicates over HTTP/1.1 and that the map still shows what it lost; a second
runs a real h2c server and asserts the field is absent, not an empty list.
Intentionally not included: the
body.byteshalf of the issue. The owner couldnot reproduce it,
bytesis a number on every transport observed, and the schemaand projection already declare
z.number().int(); re-scoping it would be a changewith no observed defect behind it.
Not applicable: scope/authority decisions — no new provider, credential, or
network boundary; the request already contacts the URL the caller names.
Contract and boundary impact
application, thencontractsdocs/public/tool-catalog.jsonwas regenerated: yes —uvp_http_requestoutput gains optionalheaders_raw; regenerated withnpm run docs:generate, andnpm run docs:checkpasses.headers_rawstates that this transport exposed no raw list; it is not an empty response.Expected effects:
Evidence and regression coverage
tests/conformance/httpBehaviour.test.ts(two cases, above) and the fixture route.bc17f63, showed the two collapsed headers inheaderswith no raw list. The new presence case fails with the capture removed (expected undefined to be defined); the absence case fails when the projection emits[]instead of omitting the field (expected … to not have property "headers_raw").LIBUVCPP_ROOT=/home/zwy/下载/libuvcpp-1.7.0-linux-x64 PKG_CONFIG_PATH=$LIBUVCPP_ROOT/lib/pkgconfig LD_LIBRARY_PATH=$LIBUVCPP_ROOT/lib npm run check:prFor evidence-bearing changes:
unknown, notcomplete.undetermined, not a fabricated yes or no.Validation performed
npm run compile— exit 0.npx vitest run tests/conformance/httpBehaviour.test.ts— 14 passed.Two mutation probes (restored afterwards): dropping the raw capture → 1 failed; emitting
[]instead of omitting → 1 failed.Full
npx vitest runwith libuvcpp 1.7.0 — 1674 passed, 14 skipped, and the same two host-environmental real-toolchain files that fail on an untouched tree (nativeProbeGatelacks the C ABI in the host install;realToolchainhas port 8090 held by an unrelated process).npm run docs:check,npm run typecheck,npm run lint,npm run format:check— exit 0.A template change was generated, built, and run against a real libuvcpp install.
Not applicable (no template or libuvcpp-facing change).
Compatibility, safety, and release
headersis unchanged.Author readiness
Human owner: @Antruly
Before merge
type(scope): outcome.🤖 Antruly-dev-1 · AI Engineer 1 · agent:dev-ai-1