Skip to content

fix(node:http): emit headers passed via res.writeHead (#2132) - #2335

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-fix-2132-http-bytes
May 29, 2026
Merged

fix(node:http): emit headers passed via res.writeHead (#2132)#2335
proggeramlug merged 1 commit into
mainfrom
worktree-fix-2132-http-bytes

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Fixes a concrete byte-level diff from #2132: headers set via res.writeHead(...) were silently dropped from the http(s) server response.

writeHead's headers argument was typed NA_STR in the native dispatch table (native_table/http.rs), so when a user passed a headers object it was coerced to the literal string "[object Object]" — never valid JSON — and the runtime discarded it. The writeHead(status, statusMessage, headers) overload also couldn't be distinguished from writeHead(status, headers) because both arg slots were fixed strings.

Before / after (raw response bytes vs Node)

res.writeHead(200, { "Content-Type": "text/plain" }); res.end("Hello")

before: HTTP/1.1 200 OK\r\ncontent-length: 5\r\nconnection: close\r\ndate: ...\r\n\r\nHello   ← Content-Type dropped
after:  HTTP/1.1 200 OK\r\ncontent-length: 5\r\ncontent-type: text/plain\r\nconnection: close\r\ndate: ...\r\n\r\nHello

All overload forms now emit their headers: writeHead(status), writeHead(status, headersObj), and writeHead(status, statusMessage, headersObj).

How

  • native_table/http.rs: writeHead args [NA_F64, NA_STR, NA_STR][NA_F64, NA_JSV, NA_JSV] so arg2/arg3 reach the runtime as raw NaN-boxed JSValues. The codegen extern decl was already [I64, DOUBLE, I64, I64], so this is ABI-compatible.
  • response.rs::js_node_http_res_write_head: resolve the overloads in the runtime — is_string() (STRING_TAG) ⇒ statusMessage, is_pointer() (POINTER_TAG heap object) ⇒ bulk headers. Headers objects are serialized with js_json_stringify and merged via a new apply_headers_json helper (lowercase lookup key, original case retained for getHeaderNames()).

Scope

This fixes the dropped-header class of #2132's diffs. The remaining #2132 differences — lowercase header casing, header ordering, custom reason-phrase (202 Accepted!), and chunked-vs-content-length framing — are all serialized by hyper (which lowercases names, controls ordering, and derives canonical reason phrases) and are out of scope for this change; they'd need a separate response-writer change. The issue explicitly invites such per-cluster sub-fixes.

Test plan

  • New unit tests in response.rs for apply_headers_json (case preservation, non-string value stringification, empty/sentinel handling).
  • cargo test -p perry-ext-http-server — 17 passed.
  • Verified end-to-end against Node v25 by capturing raw response bytes from a Perry http server through a raw TCP client for setHeader and all three writeHead overloads.

writeHead's headers argument was typed NA_STR in the native dispatch
table, so a headers *object* was coerced to the literal string
"[object Object]" and every header set through res.writeHead(status,
headers) / res.writeHead(status, statusMessage, headers) was silently
dropped from the response.

Pass arg2/arg3 as raw JSValues (NA_JSV) and resolve the Node overloads
in the runtime: a string in slot 2 is the statusMessage, a heap object
in slot 2 or 3 is the bulk headers, which are JSON-serialized via
js_json_stringify and merged (lowercase lookup key, original case
retained for getHeaderNames).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant