fix(http): make Agent/ClientRequest/IncomingMessage/ServerResponse constructable via new (#4904) - #4936
Merged
Merged
Conversation
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
force-pushed
the
worktree-fix-4904-http-internal-classes
branch
from
June 10, 2026 16:37
cb423d2 to
f7d2a48
Compare
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #4904 — Node exposes
http.Agent,http.ClientRequest,http.IncomingMessage, andhttp.ServerResponseas constructable classes; under Perry,new-ing them threwTypeError: <X> is not a constructorthrough every value-aliasing path (const { Agent } = require('http'),require('_http_agent').Agent,const CR = http.ClientRequest,new http.IncomingMessage(), …).Mechanism (mirrors the existing
OutgoingMessageroute end-to-end).lengtharities, plus the previously missinghttp.get/http.requestvalue exports (thehttpstwins already existed;const { Agent, get } = require('http')destructuring needs both).js_new_function_constructforwards(module, class, args)throughJS_NATIVE_HTTP_DISPATCH; the real module name is forwarded sohttps.Agentconstructs with the https protocol default.Agent→js_http_agent_new,ClientRequest→ newjs_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; plusget/requestvalue-call arms.new http.{ClientRequest,IncomingMessage,ServerResponse}()joins the OutgoingMessageNewDynamicroute; 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 viaHANDLE_*_DISPATCH.require('_http_agent')(and the other_http_*internal modules) binds its hoisted import to the publichttpsurface.Instance surface
socket/connectionget/set aliasing (Node'sconnectionaccessor writesthis.socket);_addHeaderLinewith Node'smatchKnownFieldssemantics (first-wins singles,', '/'; 'joins, set-cookie array).req.method; HEAD suppresses the body),assignSocket/detachSocketwithERR_HTTP_SOCKET_ASSIGNEDon double assignment,write(chunk, cb)callback queueing,end()flushes head+body through the assigned socket's JSwrite(one corked write + the zero-length finish chunk Node emits), write/end callbacks invoked in order.maxSockets,freeSockets,protocol, …) and writes (tunables +createConnection/createSocketmonkeypatching) through handle dispatch in both perry-stdlib and perry-ext-http.Drive-by SIGSEGV fix
json/stringify.rs'sis_closure_valueprobedCLOSURE_MAGICat offset 12 of POINTER_TAG payloads without the handle-band guard (same #2154 bug class as the sibling probe at line ~1106), soJSON.stringifyof{ agent, lookup: () => {} }dereferenced unmapped low memory and crashedhttp.get({ agent: new Agent({ timeout: 50 }), lookup: () => {} }). Now routes throughaddr_class::is_handle_bandfirst.Validation
test/parallel): 7 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(was 0/13; all 13 previously died at construction). The other 6 construct and run to their assertions:agent-keepalive,agent-keepalive-delay,agent-error-on-idle,agent-abort-controller,client-set-timeout-after-endneed real Agent pool emulation (per-keysockets/freeSocketsarrays, pool-drivencreateConnectionwith live socket objects) — Agent-pool follow-up territory (node:http: missing server/timer/socket lifecycle methods (closeAllConnections, setTimeout, timer.unref, ...) #4905/node:http/https: 89 tests exit non-zero with no output (triage; likely server-hang/framing, partly cascades from #4903) #4909 family).incoming-matchKnownFieldshits a pre-existingassert.deepStrictEqualdivergence:const d = {}; d.x = 'v'never compares equal to{ x: 'v' }(reproduces without any http involvement). Will file separately.matchKnownFieldsclassification and the socket-alias assignment.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.