Skip to content

Read http.request options at run time, speak HTTP over Unix sockets, and fill the boot path's net and fetch gaps - #1515

Merged
ctate merged 5 commits into
mainfrom
opt/http-socket-path
Oct 20, 2026
Merged

ctate merged 5 commits into
mainfrom
opt/http-socket-path

Conversation

@cramforce

@cramforce cramforce commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Read http.request options at run time, speak HTTP over Unix sockets, and fill the boot path's net and fetch gaps

Builds on #1429 (merged), which gives http.RequestOptions checked-dynamic storage.

Summary

Request options read at run time. http.request, https.request and get now accept options the compiler cannot see as an object literal:

  • a RequestOptions variable;
  • an object literal that spreads one;
  • a literal that sets socketPath.

The runtime reads that record the way Node's ClientRequest constructor does, in the same order and with the same errors. It covers:

  • agent, the path's unescaped-character check, and protocol;
  • defaultPort and port, where a string port such as a URL's .port works;
  • hostname/host and timeout;
  • method, validated and then upper-cased;
  • maxHeaderSize;
  • headers: an object with string or number values, arrays as repeated lines, or the flat array form;
  • signal.

Documented options the runtime does not implement (auth, createConnection, family, lookup, localAddress and others, plus setHost: false) throw a runtime fence error when they hold a value, instead of being silently ignored. Undocumented keys are ignored, as Node ignores them. https adds rejectUnauthorized, ca and servername.

Unix domain sockets. socketPath dials a Unix socket from the net unit.

  • The agent keys its pooled sockets by the path, as Node's getName does, and the Host header defaults to localhost.
  • server.listen(path[, callback]) binds a Unix socket and removes the socket file when the server closes, like libuv.
  • A string that reads as a number is still a port (Node's isPipeName).
  • Errors name the path: connect ENOENT /path, listen EADDRINUSE: address already in use /path.
  • Windows reports ENOTSUP.
  • server.address() on a Unix-socket server returns the socket path, as in Node, when its result is typed string | AddressInfo | null (the @types/node declaration). Where the result is typed as the AddressInfo record alone, which cannot hold the path, it throws for such a server. Only programs that call listen() with a string argument get this Unix-aware address(), so every other program compiles exactly as before.

A TypeScript object literal with no contextual type that spreads a checked-dynamic value now maps to the checked dynamic too, so const options = { ...location, path, method, headers, timeout, signal } compiles.

Socket 'lookup'. A socket's 'lookup' listeners get (err, address, family, host) for each address a host name resolves to, or once with the lookup error, before 'connect'. Only programs that register a 'lookup' listener install the lookup records, from their C main. In every other program the hook sites are a NULL check.

Smaller http fixes.

  • server.close((err) => ...) runs the callback with undefined at 'close', or with ERR_SERVER_NOT_RUNNING when the server was not listening.
  • A server that never listened now emits 'close'. Before, its close callbacks never ran.
  • IncomingMessage 'data' listeners may take string chunks after setEncoding.
  • res.end() accepts a string | undefined chunk.

fetch init members. Under @types/node, fetch's BodyInit and HeadersInit unions now map to the checked dynamic. So do Omit/Pick/Partial of a checked-dynamic stdlib interface such as RequestInit. Option types derived from RequestInit therefore compile.

A URLSearchParams body is now sent form-encoded, as Node sends it. It used to be sent as the text [object Object].

Evidence

Each program runs under Node and as a native binary, and the outputs must match byte for byte. All pass in both lanes.

  • 6440-http-socket-path: an HTTP server on a Unix socket.
    • Requests use a literal with socketPath, a RequestOptions variable, a spread literal, and http.get.
    • It also covers ENOENT for a missing socket, EADDRINUSE for a bound path, and removal of the socket file at close.
  • 6441-http-request-options-record:
    • a RequestOptions chosen at run time (TCP with a string port, or a socket path), spread into an unannotated options object;
    • agent: false and an aborted signal;
    • Node's errors for method, port, host, timeout, path, protocol and agent.
  • 6442-https-request-options-record: https with runtime options over TCP and over a Unix socket. It covers the CA, an unverifiable certificate with and without rejectUnauthorized, and the protocol check.
  • 6443-net-lookup-http-close-text:
    • 'lookup' for a host name, and none for an IP address;
    • string 'data' listeners after setEncoding on both sides;
    • res.end(string | undefined);
    • the close callback of a server that never listened.
  • tests/fixtures/node-types/http-socket-path.ts: a cell-API-style client typed by @types/node, plus close((err) => ...) with and without ERR_SERVER_NOT_RUNNING.
  • tests/fixtures/node-types/fetch-init-members.ts: an options interface extending Omit<RequestInit, ...>, destructured and passed back into fetch. It sends string, URLSearchParams and Buffer bodies and record, array and Headers headers, and carries the remaining members as an Omit<...>.
  • The networking corpus subset and the node-types cases fail on the same programs with and without this change. Those failures are environmental: the sandbox runs Node 24.15, and main pins 24.22.
  • Zero-cost gate against main (3,281 programs: bench, self and the whole corpus): IR and LLVM are identical for every program, including the self-compile. No executable gains dynamic-tier symbols. Programs that link http, net or fetch differ only in the runtime symbols scr_http_request_agent_ex, scr_net_dispatch and scr_fetch_static, which grow by about 1.3 KB of text in an http program. File sizes grow by up to +1.9% because the data segment moves to the next page. Bench geomean is -0.55% and self-compile +1.32%, both within tolerance.
  • Lint and format are clean. test:ts7 --baselines-only records 6 new baselines and changes none. The docs gate passes.

🤖 Generated with Claude Code

cramforce and others added 2 commits October 20, 2026 02:57
…and fill the boot path's net and fetch gaps

http.request, https.request and get now accept options the compiler
cannot see as an object literal: a RequestOptions variable, a literal
that spreads one, or a literal that sets socketPath. The runtime reads
the record in the ClientRequest constructor's order with Node's errors;
documented options it does not implement throw the runtime fence when
they carry a value. https adds rejectUnauthorized, ca and servername.

socketPath dials a Unix domain socket in the net unit; the agent keys
its sockets by the path as Node's getName does, and the Host header
defaults to localhost. server.listen(path[, cb]) binds a Unix socket and
removes the socket file when the server closes; a numeric string is
still a port. Errors name the path ("connect ENOENT /path", "listen
EADDRINUSE: address already in use /path"). Windows reports ENOTSUP.

A TypeScript object literal with no contextual type that spreads a
checked-dynamic value maps to the checked dynamic, so
`const options = { ...location, path, timeout }` compiles.

A socket's 'lookup' event reaches its listeners with (err, address,
family, host) for each address a host name resolves to, or once with
the lookup error, before 'connect'. Only programs that listen for
'lookup' install the lookup records; elsewhere the hook sites are a NULL
check.

server.close((err) => ...) runs the callback with undefined at 'close',
or with ERR_SERVER_NOT_RUNNING when the server was not listening; a
server that never listened now emits 'close'. IncomingMessage 'data'
listeners may take string chunks after setEncoding, and res.end()
accepts a string-or-undefined chunk.

fetch's BodyInit and HeadersInit unions, as RequestInit-derived option
types spell them under @types/node, map to the checked dynamic, and a
URLSearchParams body is sent form-encoded, as by Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
scriptc Ready Ready Preview, v0 Oct 20, 2026 3:24pm UTC

@vercel-internal-security-reviewer

Copy link
Copy Markdown

Security review details

Comment thread packages/runtime/src/scr_net.c Resolved
Comment thread packages/runtime/src/scr_http.c Resolved
Node answers a Unix-socket server's address() with its path. Programs
that call listen() with a string argument now get a Unix-aware
address(): typed `string | AddressInfo | null` (the @types/node
declaration), it answers the path; typed as the AddressInfo record alone,
which cannot hold the path, it throws for such a server, as does the
composed address().port read. Every other program keeps the record-only
lowering, so their output is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…socket closes

- http.request, https.request and get validate each name and value of a
  flat [name, value, ...] headers array, as Node's _storeHeader does: a
  name that is not an HTTP token throws ERR_INVALID_HTTP_TOKEN, a value
  with an invalid character throws ERR_INVALID_CHAR, and an odd length
  throws ERR_INVALID_ARG_VALUE with the inspected array. The
  [[name, value], ...] entry form throws the options fence.
- A closing socket drops its 'lookup' listeners with its other listeners,
  so a listener that holds the socket no longer keeps it alive.
- The options-record diagnostics snapshot no longer expects the
  socketPath refusal.
@ctate
ctate merged commit 200fe8d into main Oct 20, 2026
23 of 24 checks passed
@ctate
ctate deleted the opt/http-socket-path branch October 20, 2026 15:28

This branch was successfully deployed

1 active deployment
Preview — 89f8dbe9 Deployed Oct 20, 2026 by vercel[bot]
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.

2 participants