Skip to content

fix(http): make Agent/ClientRequest/IncomingMessage/ServerResponse constructable via new (#4904) - #4936

Merged
proggeramlug merged 3 commits into
mainfrom
worktree-fix-4904-http-internal-classes
Jun 10, 2026
Merged

fix(http): make Agent/ClientRequest/IncomingMessage/ServerResponse constructable via new (#4904)#4936
proggeramlug merged 3 commits into
mainfrom
worktree-fix-4904-http-internal-classes

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Fixes #4904 — Node exposes http.Agent, http.ClientRequest, http.IncomingMessage, and http.ServerResponse as constructable classes; under Perry, new-ing them threw TypeError: <X> is not a constructor through every value-aliasing path (const { Agent } = require('http'), require('_http_agent').Agent, const CR = http.ClientRequest, new http.IncomingMessage(), …).

Mechanism (mirrors the existing OutgoingMessage route end-to-end)

  • runtime/native_module — export the four classes as bound callable values with Node .length arities, plus the previously missing http.get / http.request value exports (the https twins already existed; const { Agent, get } = require('http') destructuring needs both).
  • runtime/class_registry — extend the http construct arm so js_new_function_construct forwards (module, class, args) through JS_NATIVE_HTTP_DISPATCH; the real module name is forwarded so https.Agent constructs with the https protocol default.
  • stdlib/dispatch — constructor arms: Agentjs_http_agent_new, ClientRequest → new js_http_client_request_standalone_new (defers the send to .end(), matching Perry's client model), IncomingMessage / ServerResponse → new standalone factories in perry-ext-http-server; plus get/request value-call arms.
  • HIR — member-form new http.{ClientRequest,IncomingMessage,ServerResponse}() joins the OutgoingMessage NewDynamic route; bare-ident forms (destructured imports) added for all four; the three handle-backed classes are skip-listed from typed native-instance registration so instances dispatch dynamically via HANDLE_*_DISPATCH.
  • cjs_wraprequire('_http_agent') (and the other _http_* internal modules) binds its hoisted import to the public http surface.

Instance surface

  • IncomingMessage: standalone ctor stores the socket argument verbatim; socket/connection get/set aliasing (Node's connection accessor writes this.socket); _addHeaderLine with Node's matchKnownFields semantics (first-wins singles, ', '/'; ' joins, set-cookie array).
  • ServerResponse: standalone ctor (captures req.method; HEAD suppresses the body), assignSocket/detachSocket with ERR_HTTP_SOCKET_ASSIGNED on double assignment, write(chunk, cb) callback queueing, end() flushes head+body through the assigned socket's JS write (one corked write + the zero-length finish chunk Node emits), write/end callbacks invoked in order.
  • Agent: dynamic property reads (maxSockets, freeSockets, protocol, …) and writes (tunables + createConnection/createSocket monkeypatching) through handle dispatch in both perry-stdlib and perry-ext-http.

Drive-by SIGSEGV fix

json/stringify.rs's is_closure_value probed CLOSURE_MAGIC at offset 12 of POINTER_TAG payloads without the handle-band guard (same #2154 bug class as the sibling probe at line ~1106), so JSON.stringify of { agent, lookup: () => {} } dereferenced unmapped low memory and crashed http.get({ agent: new Agent({ timeout: 50 }), lookup: () => {} }). Now routes through addr_class::is_handle_band first.

Validation

  • Issue's 13 Node corpus tests (pinned v22 test/parallel): 7 now pass outrightclient-defaults, agent-timeout-option, client-timeout-option-with-agent, incoming-message-connection-setter, incoming-message-destroy, outgoing-message-write-callback, server-response-standalone (was 0/13; all 13 previously died at construction). The other 6 construct and run to their assertions:
  • Curated node-suite: http 18/18, https 5/5 — 100%, 0 regressions.
  • Crate tests: perry-ext-http-server / perry-ext-http / perry-runtime / perry-stdlib / perry-hir / perry(--lib) all green; new unit tests for matchKnownFields classification and the socket-alias assignment.
  • Gates: cargo fmt --check, file-size gate, GC store-site inventory, addr-class inventory all pass.

Version bump + changelog left for merge time per the maintainer-folds-metadata flow.

Part of #2132.

Ralph Küpper added 2 commits June 10, 2026 18:37
…nstructable via new (#4904)

Node exposes http.Agent, http.ClientRequest, http.IncomingMessage, and
http.ServerResponse as constructable classes; under Perry, new-ing them
threw 'TypeError: <X> is not a constructor' through every value-aliasing
path (const { Agent } = require('http'), require('_http_agent').Agent,
const CR = http.ClientRequest, new http.IncomingMessage(), ...).

Mechanism (mirrors the existing OutgoingMessage route end-to-end):

- runtime/native_module: export the four classes (plus the previously
  missing http.get / http.request twins of the https entries) as bound
  callable values with Node .length arities.
- runtime/class_registry: extend the http construct arm so
  js_new_function_construct forwards (module, class, args) through
  JS_NATIVE_HTTP_DISPATCH; forward the real module name so https.Agent
  constructs with the https protocol default.
- stdlib/dispatch: constructor arms — Agent -> js_http_agent_new,
  ClientRequest -> new js_http_client_request_standalone_new,
  IncomingMessage/ServerResponse -> new standalone factories in
  perry-ext-http-server; plus get/request value-call arms.
- HIR: member-form new http.{ClientRequest,IncomingMessage,
  ServerResponse}() joins the OutgoingMessage NewDynamic route;
  bare-ident forms (destructured imports) added for all four classes;
  the three handle-backed classes are skip-listed from typed
  native-instance registration so instances dispatch dynamically.
- cjs_wrap: require('_http_agent') (and the other _http_* internal
  modules) binds its hoisted import to the public 'http' surface.

Instance surface (perry-ext-http-server):
- IncomingMessage: standalone constructor storing the socket argument;
  socket/connection get/set aliasing (Node's connection accessor writes
  this.socket); _addHeaderLine with Node's matchKnownFields semantics
  (first-wins singles, ', '/'; ' joins, set-cookie array).
- ServerResponse: standalone constructor (req.method captured; HEAD
  suppresses the body), assignSocket/detachSocket with
  ERR_HTTP_SOCKET_ASSIGNED on double assignment, write(chunk, cb)
  callback queueing, end() flushing head+body through the assigned
  socket's JS write method (one corked write + the zero-length finish
  chunk), write/end callbacks invoked in order.
- Agent: dynamic property reads (maxSockets, freeSockets, protocol, ...)
  and writes (tunables + createConnection/createSocket monkeypatching)
  through handle dispatch in both perry-stdlib and perry-ext-http.

Also fixes a latent SIGSEGV: json/stringify's is_closure_value probed
CLOSURE_MAGIC at offset 12 of POINTER_TAG payloads without the handle-
band guard (same #2154 bug class as the sibling probe in the file), so
JSON.stringify of { agent, lookup: () => {} } dereferenced unmapped low
memory. Route through addr_class::is_handle_band first.

Node corpus (test/parallel, pinned v22): 7 of the 13 tests in #4904 now
pass outright (client-defaults, agent-timeout-option,
client-timeout-option-with-agent, incoming-message-connection-setter,
incoming-message-destroy, outgoing-message-write-callback,
server-response-standalone); the other 6 construct and run to their
assertions, failing on deeper Agent pool emulation (sockets/freeSockets
per-key arrays, real createConnection sockets) and a pre-existing
deepStrictEqual divergence, tracked separately.

Closes #4904. Part of #2132.
@proggeramlug
proggeramlug force-pushed the worktree-fix-4904-http-internal-classes branch from cb423d2 to f7d2a48 Compare June 10, 2026 16:37
…ine CI cap

#4930 (regex d flag) pushed regex.rs to 2260 lines, tripping the lint
job's file-size gate on every PR merged after it. Extract the match-
result array decoration group (index/input/groups/indices builders for
both the regex-crate fast path and the fancy_regex fallback, plus the
char/byte index converters) into regex/exec_array.rs — same recipe as
the date.rs split (#4925). No behavior change; d-flag output verified
byte-identical with Node post-split.
@proggeramlug
proggeramlug merged commit 2022f82 into main Jun 10, 2026
12 of 13 checks passed
@proggeramlug
proggeramlug deleted the worktree-fix-4904-http-internal-classes branch June 10, 2026 16:56
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.

node:http: Agent/ClientRequest/IncomingMessage/ServerResponse not constructable via new

1 participant