feat(http): expand the static Node HTTP surface

- Add HTTP module values, request and response lifecycle state, and socket controls.
- Implement server timeout and connection shutdown behavior in the native runtime.
- Cover C and LLVM behavior with Node differential fixtures, refresh compatibility evidence, and recover interrupted Sandbox streams.
This commit is contained in:
Chris Tate
2026-09-24 21:53:55 -05:00
parent 8e9251e97b
commit 4e03835aad
37 changed files with 1977 additions and 692 deletions
@@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.15.0",
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
"artifactVersion": "f6573c7b1b95aaff0827",
"artifactVersion": "314fa26f7ef394cb4433",
"rowCount": 3662
}
+159 -159
View File
@@ -506,9 +506,9 @@
"apiEntries": 174,
"static": {
"supported": 0,
"partial": 79,
"partial": 131,
"refused": 0,
"not-implemented": 95,
"not-implemented": 43,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -45401,9 +45401,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "partial",
@@ -45513,9 +45513,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "partial",
@@ -45541,9 +45541,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -45569,9 +45569,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -45597,9 +45597,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -45625,9 +45625,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -45681,9 +45681,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -45709,9 +45709,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -45737,9 +45737,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "partial",
@@ -45765,9 +45765,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "partial",
@@ -45877,9 +45877,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46129,9 +46129,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46157,9 +46157,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46186,7 +46186,7 @@
},
"static": {
"status": "partial",
"detail": "Implemented by a dedicated static compiler/runtime path.",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
@@ -46269,9 +46269,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46297,9 +46297,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Static ServerResponse state, socket access, timeouts, and strict body length checks support the tested HTTP/1.1 forms.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46325,9 +46325,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46493,9 +46493,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46521,9 +46521,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Static ServerResponse state, socket access, timeouts, and strict body length checks support the tested HTTP/1.1 forms.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46577,9 +46577,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Static ServerResponse state, socket access, timeouts, and strict body length checks support the tested HTTP/1.1 forms.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46605,9 +46605,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46633,9 +46633,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46717,9 +46717,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46773,9 +46773,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46801,9 +46801,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46829,9 +46829,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46857,9 +46857,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46885,9 +46885,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "refused",
@@ -46941,9 +46941,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -46969,9 +46969,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -47025,9 +47025,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -47389,9 +47389,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -47417,9 +47417,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -47445,9 +47445,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -47501,9 +47501,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -47529,9 +47529,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -47557,9 +47557,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48089,9 +48089,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48369,9 +48369,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48397,9 +48397,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48425,9 +48425,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "partial",
@@ -48453,9 +48453,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "partial",
@@ -48481,9 +48481,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48509,9 +48509,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48565,9 +48565,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48593,9 +48593,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48761,9 +48761,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Static ServerResponse state, socket access, timeouts, and strict body length checks support the tested HTTP/1.1 forms.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48817,9 +48817,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Static ServerResponse state, socket access, timeouts, and strict body length checks support the tested HTTP/1.1 forms.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48929,9 +48929,9 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -48957,9 +48957,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
@@ -50105,9 +50105,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "partial",
@@ -50133,9 +50133,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "partial",
@@ -50189,9 +50189,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
},
"dynamic": {
"status": "not-implemented",
File diff suppressed because it is too large Load Diff
@@ -555,9 +555,9 @@
"apiEntries": 174,
"static": {
"supported": 0,
"partial": 79,
"partial": 131,
"refused": 0,
"not-implemented": 95,
"not-implemented": 43,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -62167,7 +62167,25 @@
"tests/corpus/2956-http-static-informational.ts",
"tests/corpus/2957-http-static-response-controls.ts",
"tests/corpus/2972-http-static-distinct-snapshots.ts",
"tests/fixtures/server/cases/http-trailers/main.ts"
"tests/corpus/2973-http-static-agent.ts",
"tests/corpus/2974-http-static-module-values.ts",
"tests/corpus/2975-http-static-client-controls.ts",
"tests/corpus/2976-http-static-incoming-state.ts",
"tests/corpus/2977-http-static-incoming-timeout.ts",
"tests/corpus/2978-http-static-client-timeout.ts",
"tests/corpus/2979-http-static-server-timeout.ts",
"tests/corpus/2980-http-static-server-timeout-default.ts",
"tests/corpus/2981-http-static-events.ts",
"tests/corpus/2982-http-static-close-all.ts",
"tests/corpus/2983-http-static-close-idle.ts",
"tests/corpus/2984-http-static-close-upgrade.ts",
"tests/corpus/2985-http-static-message-destroy.ts",
"tests/corpus/2986-http-static-client-socket-finish.ts",
"tests/corpus/2987-http-static-timeout-errors.ts",
"tests/corpus/2988-http-static-client-abort.ts",
"tests/fixtures/server/cases/http-trailers/main.ts",
"tests/fixtures/server/cases/http2-connect/main.ts",
"tests/fixtures/server/cases/upgrade-both/main.ts"
]
},
"dynamic": {
@@ -62278,8 +62296,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.http.Agent",
"tests": [
"tests/corpus/2973-http-static-agent.ts"
]
},
"dynamic": {
"status": "partial",
@@ -62389,8 +62410,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.agent.destroy",
"tests": [
"tests/corpus/2973-http-static-agent.ts"
]
},
"dynamic": {
"status": "partial",
@@ -62419,8 +62443,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.agent.getName",
"tests": [
"tests/corpus/2973-http-static-agent.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -62446,8 +62473,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.freeSockets",
"tests": [
"tests/corpus/2973-http-static-agent.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -62473,8 +62503,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.maxFreeSockets",
"tests": [
"tests/corpus/2973-http-static-agent.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -62500,8 +62533,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.maxSockets",
"tests": [
"tests/corpus/2973-http-static-agent.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -62554,8 +62590,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.requests",
"tests": [
"tests/corpus/2973-http-static-agent.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -62581,8 +62620,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.sockets",
"tests": [
"tests/corpus/2973-http-static-agent.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -62608,8 +62650,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.http.ClientRequest",
"tests": [
"tests/corpus/2981-http-static-events.ts",
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "partial",
@@ -62638,8 +62684,11 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.request.abort",
"tests": [
"tests/corpus/2988-http-static-client-abort.ts"
]
},
"dynamic": {
"status": "partial",
@@ -62772,8 +62821,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.destroyed",
"tests": [
"tests/corpus/2978-http-static-client-timeout.ts",
"tests/corpus/2986-http-static-client-socket-finish.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63055,8 +63108,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.request.setNoDelay",
"tests": [
"tests/corpus/2975-http-static-client-controls.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63082,8 +63138,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.request.setSocketKeepAlive",
"tests": [
"tests/corpus/2978-http-static-client-timeout.ts",
"tests/corpus/2987-http-static-timeout-errors.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63110,11 +63170,10 @@
},
"static": {
"status": "partial",
"evidence": "compiler-dedicated:request.setTimeout",
"evidence": "compiler-feature:http.request.setTimeout",
"tests": [
"tests/corpus/1780-http-res-surface.ts",
"tests/corpus/2672-http-request-response-callback.ts",
"tests/corpus/2689-http-server-timeout-properties.ts"
"tests/corpus/2978-http-static-client-timeout.ts",
"tests/corpus/2987-http-static-timeout-errors.ts"
]
},
"dynamic": {
@@ -63213,8 +63272,11 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.aborted",
"tests": [
"tests/corpus/2988-http-static-client-abort.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63240,8 +63302,11 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.connection",
"tests": [
"tests/corpus/2986-http-static-client-socket-finish.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63267,8 +63332,11 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.finished",
"tests": [
"tests/corpus/2975-http-static-client-controls.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63441,8 +63509,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.reusedSocket",
"tests": [
"tests/corpus/2975-http-static-client-controls.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63468,8 +63539,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.socket",
"tests": [
"tests/corpus/2986-http-static-client-socket-finish.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63525,8 +63599,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.writableFinished",
"tests": [
"tests/corpus/2986-http-static-client-socket-finish.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63552,8 +63629,11 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'abort'",
"tests": [
"tests/corpus/2988-http-static-client-abort.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63579,8 +63659,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'close'",
"tests": [
"tests/corpus/2981-http-static-events.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63660,8 +63743,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'finish'",
"tests": [
"tests/corpus/2986-http-static-client-socket-finish.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63714,8 +63800,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'response'",
"tests": [
"tests/corpus/2981-http-static-events.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63741,8 +63830,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'socket'",
"tests": [
"tests/corpus/2986-http-static-client-socket-finish.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63768,8 +63860,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'timeout'",
"tests": [
"tests/corpus/2978-http-static-client-timeout.ts",
"tests/corpus/2987-http-static-timeout-errors.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63795,8 +63891,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'upgrade'",
"tests": [
"tests/fixtures/server/cases/upgrade-both/main.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63822,8 +63921,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.http.Server",
"tests": [
"tests/corpus/2981-http-static-events.ts",
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "refused",
@@ -63880,8 +63983,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.server.closeAllConnections",
"tests": [
"tests/corpus/2982-http-static-close-all.ts",
"tests/corpus/2983-http-static-close-idle.ts",
"tests/corpus/2984-http-static-close-upgrade.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63907,8 +64015,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.server.closeIdleConnections",
"tests": [
"tests/corpus/2982-http-static-close-all.ts",
"tests/corpus/2983-http-static-close-idle.ts",
"tests/corpus/2984-http-static-close-upgrade.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -63965,8 +64078,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.server.setTimeout",
"tests": [
"tests/corpus/2979-http-static-server-timeout.ts",
"tests/corpus/2980-http-static-server-timeout-default.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -64344,8 +64461,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'close'",
"tests": [
"tests/corpus/2981-http-static-events.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -64371,8 +64491,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'connect'",
"tests": [
"tests/fixtures/server/cases/http2-connect/main.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -64398,8 +64521,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'connection'",
"tests": [
"tests/corpus/2981-http-static-events.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -64452,8 +64578,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'request'",
"tests": [
"tests/corpus/2981-http-static-events.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -64479,8 +64608,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'upgrade'",
"tests": [
"tests/fixtures/server/cases/upgrade-both/main.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -64506,8 +64638,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.http.ServerResponse",
"tests": [
"tests/corpus/2981-http-static-events.ts",
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65103,8 +65239,11 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.finished",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65400,8 +65539,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'close'",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65427,8 +65569,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'finish'",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65454,8 +65599,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.http.IncomingMessage",
"tests": [
"tests/corpus/2981-http-static-events.ts",
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "partial",
@@ -65484,8 +65633,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.message.destroy",
"tests": [
"tests/corpus/2985-http-static-message-destroy.ts"
]
},
"dynamic": {
"status": "partial",
@@ -65514,8 +65666,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.message.setTimeout",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts",
"tests/corpus/2977-http-static-incoming-timeout.ts",
"tests/corpus/2987-http-static-timeout-errors.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65541,8 +65698,11 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.aborted",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65598,8 +65758,11 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.message.connection",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65625,8 +65788,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.headers",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65810,8 +65976,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.socket",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65867,8 +66036,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.statusMessage",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -65992,8 +66164,11 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'aborted'",
"tests": [
"tests/corpus/2985-http-static-message-destroy.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -66019,8 +66194,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.'close'",
"tests": [
"tests/corpus/2976-http-static-incoming-state.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -67210,8 +67388,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.METHODS",
"tests": [
"tests/corpus/2974-http-static-module-values.ts"
]
},
"dynamic": {
"status": "partial",
@@ -67240,8 +67421,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.STATUS_CODES",
"tests": [
"tests/corpus/2974-http-static-module-values.ts"
]
},
"dynamic": {
"status": "partial",
@@ -67300,8 +67484,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:http"
"status": "partial",
"evidence": "compiler-feature:http.maxHeaderSize",
"tests": [
"tests/corpus/2974-http-static-module-values.ts"
]
},
"dynamic": {
"status": "not-implemented",
@@ -79,6 +79,27 @@
"stdlib.abort": ["tests/harness/fetch-conformance.test.ts"]
},
"features": [
{ "chapter": "http", "signatures": ["Class: `http.ClientRequest`", "Class: `http.Server`", "Class: `http.ServerResponse`", "Class: `http.IncomingMessage`"], "status": "partial", "evidence": ["tests/corpus/2981-http-static-events.ts", "tests/corpus/2976-http-static-incoming-state.ts"] },
{ "chapter": "http", "parent": "Class: `http.ClientRequest`", "signatures": ["`request.setSocketKeepAlive([enable][, initialDelay])`", "`request.setTimeout(timeout[, callback])`", "`destroyed` Type: {boolean}", "Event: `'timeout'`"], "status": "partial", "evidence": ["tests/corpus/2978-http-static-client-timeout.ts", "tests/corpus/2987-http-static-timeout-errors.ts"] },
{ "chapter": "http", "parent": "Class: `http.ClientRequest`", "signatures": ["`request.abort()`", "`aborted` Type: {boolean}", "Event: `'abort'`"], "status": "partial", "evidence": ["tests/corpus/2988-http-static-client-abort.ts"] },
{ "chapter": "http", "parent": "Class: `http.ClientRequest`", "signatures": ["Event: `'close'`", "Event: `'response'`"], "status": "partial", "evidence": ["tests/corpus/2981-http-static-events.ts"] },
{ "chapter": "http", "parent": "Class: `http.ClientRequest`", "signatures": ["`socket` Type: {stream.Duplex}", "`connection` Type: {stream.Duplex}", "`writableFinished` Type: {boolean}", "Event: `'socket'`", "Event: `'finish'`"], "status": "partial", "evidence": ["tests/corpus/2986-http-static-client-socket-finish.ts"] },
{ "chapter": "http", "signatures": ["`destroyed` Type: {boolean}"], "status": "partial", "evidence": ["tests/corpus/2978-http-static-client-timeout.ts", "tests/corpus/2986-http-static-client-socket-finish.ts"] },
{ "chapter": "http", "parent": "Class: `http.ClientRequest`", "signatures": ["Event: `'upgrade'`"], "status": "partial", "evidence": ["tests/fixtures/server/cases/upgrade-both/main.ts"] },
{ "chapter": "http", "parent": "Class: `http.IncomingMessage`", "signatures": ["`message.destroy([error])`", "Event: `'aborted'`"], "status": "partial", "evidence": ["tests/corpus/2985-http-static-message-destroy.ts"] },
{ "chapter": "http", "parent": "Class: `http.Server`", "signatures": ["`server.setTimeout([msecs][, callback])`"], "status": "partial", "evidence": ["tests/corpus/2979-http-static-server-timeout.ts", "tests/corpus/2980-http-static-server-timeout-default.ts"] },
{ "chapter": "http", "parent": "Class: `http.Server`", "signatures": ["`server.closeAllConnections()`", "`server.closeIdleConnections()`"], "status": "partial", "evidence": ["tests/corpus/2982-http-static-close-all.ts", "tests/corpus/2983-http-static-close-idle.ts", "tests/corpus/2984-http-static-close-upgrade.ts"] },
{ "chapter": "http", "parent": "Class: `http.Server`", "signatures": ["Event: `'close'`", "Event: `'connection'`", "Event: `'request'`"], "status": "partial", "evidence": ["tests/corpus/2981-http-static-events.ts"] },
{ "chapter": "http", "parent": "Class: `http.Server`", "signatures": ["Event: `'upgrade'`"], "status": "partial", "evidence": ["tests/fixtures/server/cases/upgrade-both/main.ts"] },
{ "chapter": "http", "parent": "Class: `http.Server`", "signatures": ["Event: `'connect'`"], "status": "partial", "evidence": ["tests/fixtures/server/cases/http2-connect/main.ts"] },
{ "chapter": "http", "signatures": ["Class: `http.Agent`"], "status": "partial", "evidence": ["tests/corpus/2973-http-static-agent.ts"] },
{ "chapter": "http", "parent": "Class: `http.Agent`", "signatures": ["`agent.destroy()`", "`agent.getName([options])`", "`freeSockets` Type: {Object}", "`maxFreeSockets` Type: {number}", "`maxSockets` Type: {number}", "`requests` Type: {Object}", "`sockets` Type: {Object}"], "status": "partial", "evidence": ["tests/corpus/2973-http-static-agent.ts"] },
{ "chapter": "http", "signatures": ["`METHODS` Type: {string\\[]}", "`STATUS_CODES` Type: {Object}", "`maxHeaderSize` Type: {number}"], "status": "partial", "evidence": ["tests/corpus/2974-http-static-module-values.ts"] },
{ "chapter": "http", "signatures": ["`request.setNoDelay([noDelay])`"], "status": "partial", "evidence": ["tests/corpus/2975-http-static-client-controls.ts"] },
{ "chapter": "http", "parent": "Class: `http.ClientRequest`", "signatures": ["`finished` Type: {boolean}", "`reusedSocket` Type: {boolean} Whether the request is send through a reused socket."], "status": "partial", "evidence": ["tests/corpus/2975-http-static-client-controls.ts"] },
{ "chapter": "http", "parent": "Class: `http.IncomingMessage`", "signatures": ["`message.setTimeout(msecs[, callback])`"], "status": "partial", "evidence": ["tests/corpus/2976-http-static-incoming-state.ts", "tests/corpus/2977-http-static-incoming-timeout.ts", "tests/corpus/2987-http-static-timeout-errors.ts"] },
{ "chapter": "http", "parent": "Class: `http.IncomingMessage`", "signatures": ["`aborted` Type: {boolean}", "`message.connection`", "`headers` Type: {Object}", "`socket` Type: {stream.Duplex}", "`statusMessage` Type: {string}", "Event: `'close'`"], "status": "partial", "evidence": ["tests/corpus/2976-http-static-incoming-state.ts"] },
{ "chapter": "http", "parent": "Class: `http.ServerResponse`", "signatures": ["`finished` Type: {boolean}", "Event: `'close'`", "Event: `'finish'`"], "status": "partial", "evidence": ["tests/corpus/2976-http-static-incoming-state.ts"] },
{ "chapter": "http", "signatures": ["`http.validateHeaderName(name[, label])`", "`http.validateHeaderValue(name, value)`"], "status": "partial", "evidence": ["tests/corpus/2953-http-header-validation.ts"] },
{ "chapter": "http", "signatures": ["`request.getHeader(name)`", "`request.getHeaderNames()`", "`request.getHeaders()`", "`request.getRawHeaderNames()`", "`request.hasHeader(name)`", "`request.removeHeader(name)`", "`request.setHeader(name, value)`", "`response.getHeaderNames()`", "`response.getHeaders()`"], "status": "partial", "evidence": ["tests/corpus/2954-http-outgoing-headers.ts"] },
{ "chapter": "http", "signatures": ["`path` Type: {string} The request path.", "`method` Type: {string} The request method.", "`host` Type: {string} The request host.", "`protocol` Type: {string} The request protocol.", "`listening` Type: {boolean} Indicates whether or not the server is listening for connections.", "`headersSent` Type: {boolean}", "`writableEnded` Type: {boolean}", "`complete` Type: {boolean}", "`httpVersion` Type: {string}", "`method` Type: {string}", "`url` Type: {string}", "`statusCode` Type: {number}", "`statusCode` Type: {number} **Default:** `200`"], "status": "partial", "evidence": ["tests/corpus/2955-http-static-state.ts"] },
+35 -9
View File
@@ -2247,6 +2247,7 @@ declare module "net" {
emit(event: "connection", socket: Socket): boolean;
on(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void;
on(event: "close" | "listening", listener: () => void): void;
on(event: "timeout", listener: (socket: Socket) => void): void;
on(event: "error", listener: (err: Error) => void): void;
/* The WebSocket handover: fires INSTEAD of 'request' for
* Connection: upgrade requests, with the raw socket + head bytes. */
@@ -2257,11 +2258,13 @@ declare module "net" {
/* addListener IS on (Node aliases them) — the suite spells both. */
addListener(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void;
addListener(event: "close" | "listening", listener: () => void): void;
addListener(event: "timeout", listener: (socket: Socket) => void): void;
addListener(event: "error", listener: (err: Error) => void): void;
addListener(event: "upgrade", listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void): void;
addListener(event: "request", listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void): void;
once(event: "connection" | "secureConnection", listener: (socket: Socket) => void): void;
once(event: "close" | "listening", listener: () => void): void;
once(event: "timeout", listener: (socket: Socket) => void): void;
once(event: "error", listener: (err: Error) => void): void;
once(event: "upgrade", listener: (req: import("http").IncomingMessage, socket: Socket, head: Buffer) => void): void;
once(event: "request", listener: (req: import("http").IncomingMessage, res: import("http").ServerResponse) => void): void;
@@ -2318,8 +2321,14 @@ declare module "node:net" {
* connection per call (no agent pooling) with Node's exact wire head. */
declare module "http" {
import { Server as NetServer, Socket } from "net";
export const METHODS: string[];
export const STATUS_CODES: { [status: number]: string | undefined };
export const maxHeaderSize: number;
export interface Server extends NetServer {
timeout: number;
setTimeout(msecs: number, callback?: (socket: Socket) => void): this;
closeAllConnections(): void;
closeIdleConnections(): void;
keepAliveTimeout: number;
keepAliveTimeoutBuffer: number;
headersTimeout: number;
@@ -2359,9 +2368,11 @@ declare module "http" {
readonly method: string;
readonly httpVersion: string;
readonly complete: boolean;
readonly aborted: boolean;
readonly statusCode: number | undefined;
readonly statusMessage: string | undefined;
readonly socket: Socket;
readonly connection: Socket;
readonly headers: { [name: string]: string | undefined };
readonly headersDistinct: { [name: string]: string[] | undefined };
readonly rawHeaders: string[];
@@ -2370,6 +2381,7 @@ declare module "http" {
readonly rawTrailers: string[];
resume(): void;
destroy(): void;
setTimeout(msecs: number, callback?: () => void): this;
/* setEncoding('utf8'): 'data' delivers strings (other real encodings
* fence loudly at runtime; unknown names throw ERR_UNKNOWN_ENCODING). */
setEncoding(encoding: string): void;
@@ -2377,19 +2389,20 @@ declare module "http" {
* ClientRequest, or a raw Socket; natural end ends the destination. */
pipe(destination: ServerResponse | import("http2").Http2ServerResponse | ClientRequest | Socket): void;
on(event: "data", listener: (chunk: any) => void): void;
on(event: "end" | "close", listener: () => void): void;
on(event: "end" | "close" | "aborted", listener: () => void): void;
on(event: "error", listener: (err: Error) => void): void;
/* addListener IS on (Node aliases them) — the suite spells both. */
addListener(event: "data", listener: (chunk: any) => void): void;
addListener(event: "end" | "close", listener: () => void): void;
addListener(event: "end" | "close" | "aborted", listener: () => void): void;
addListener(event: "error", listener: (err: Error) => void): void;
once(event: "data", listener: (chunk: any) => void): void;
once(event: "end" | "close", listener: () => void): void;
once(event: "end" | "close" | "aborted", listener: () => void): void;
once(event: "error", listener: (err: Error) => void): void;
}
export interface ServerResponse {
readonly headersSent: boolean;
readonly writableEnded: boolean;
readonly finished: boolean;
readonly writableFinished: boolean;
readonly writableCorked: number;
readonly req: IncomingMessage;
@@ -2425,10 +2438,10 @@ declare module "http" {
end(data?: string | Uint8Array, callback?: () => void): void;
end(callback: () => void): void;
destroy(): void;
on(event: "close", listener: () => void): void;
on(event: "close" | "finish", listener: () => void): void;
/* addListener IS on (Node aliases them) — the suite spells both. */
addListener(event: "close", listener: () => void): void;
once(event: "close", listener: () => void): void;
addListener(event: "close" | "finish", listener: () => void): void;
once(event: "close" | "finish", listener: () => void): void;
}
/* The CLIENT slice (http.request/http.get): the options-object form
* with hostname/host, port, path, method, timeout, and headers; the
@@ -2495,6 +2508,9 @@ declare module "http" {
export const globalAgent: Agent;
export interface ClientRequest {
readonly destroyed: boolean;
readonly aborted: boolean;
readonly socket: Socket;
readonly connection: Socket;
readonly writableCorked: number;
readonly method: string;
readonly path: string;
@@ -2502,6 +2518,9 @@ declare module "http" {
readonly protocol: string;
readonly headersSent: boolean;
readonly writableEnded: boolean;
readonly writableFinished: boolean;
readonly finished: boolean;
readonly reusedSocket: boolean;
setHeader(name: string, value: string): void;
getHeader(name: string): string | undefined;
hasHeader(name: string): boolean;
@@ -2513,21 +2532,28 @@ declare module "http" {
addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void;
cork(): void;
uncork(): void;
setNoDelay(noDelay?: boolean): void;
setSocketKeepAlive(enable?: boolean, initialDelay?: number): void;
setTimeout(timeout: number, callback?: () => void): this;
write(data: string | Uint8Array): void;
end(data?: string | Uint8Array): void;
destroy(): void;
abort(): void;
on(event: "response", listener: (res: IncomingMessage) => void): void;
on(event: "socket", listener: (socket: Socket) => void): void;
on(event: "upgrade", listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void): void;
on(event: "timeout" | "close", listener: () => void): void;
on(event: "timeout" | "close" | "finish" | "abort", listener: () => void): void;
on(event: "error", listener: (err: Error) => void): void;
/* addListener IS on (Node aliases them) — the suite spells both. */
addListener(event: "response", listener: (res: IncomingMessage) => void): void;
addListener(event: "socket", listener: (socket: Socket) => void): void;
addListener(event: "upgrade", listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void): void;
addListener(event: "timeout" | "close", listener: () => void): void;
addListener(event: "timeout" | "close" | "finish" | "abort", listener: () => void): void;
addListener(event: "error", listener: (err: Error) => void): void;
once(event: "response", listener: (res: IncomingMessage) => void): void;
once(event: "socket", listener: (socket: Socket) => void): void;
once(event: "upgrade", listener: (res: IncomingMessage, socket: Socket, head: Buffer) => void): void;
once(event: "timeout" | "close", listener: () => void): void;
once(event: "timeout" | "close" | "finish" | "abort", listener: () => void): void;
once(event: "error", listener: (err: Error) => void): void;
}
export function createServer(
+76 -5
View File
@@ -6007,9 +6007,7 @@ function emitNetworkLibCall(state: LibCallState): Temp {
const cb = args[1]!;
emitter.moveTemp(cb);
const adapter = cbT.params.length === 0 ? "scr_net_conn_thunk0" : "scr_net_conn_thunk_sock";
const entry = e.fn === "net.serverOnConnection"
? "scr_net_server_on_connection"
: "scr_net_server_on_secure_connection";
const entry = e.fn === "net.serverOnConnection" ? "scr_net_server_on_connection" : "scr_net_server_on_secure_connection";
emitter.line(`${entry}(${arg(0)}, ${cb.name}, &${adapter}, ${arg(2)});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
}
@@ -6661,6 +6659,31 @@ function emitHttpLibCall(state: LibCallState): Temp {
case "http.serverAllowMissingHostHeader":
emitter.line(`scr_http_server_allow_missing_host_header(${arg(0)});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
case "http.serverSetTimeout":
emitter.line(`scr_net_server_set_timeout_plain(${arg(0)}, ${arg(1)});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
case "http.serverCloseAllConnections":
case "http.serverCloseIdleConnections": {
const entry = e.fn === "http.serverCloseAllConnections"
? "scr_net_server_close_all_connections" : "scr_net_server_close_idle_connections";
emitter.line(`${entry}(${arg(0)});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
}
case "http.serverOnTimeout":
case "http.serverSetTimeoutCb": {
const cbIndex = e.fn === "http.serverSetTimeoutCb" ? 2 : 1;
const cbT = e.args[cbIndex]!.type;
if (cbT.kind !== "func") throw new InternalCompilerError(`emitter bug: ${e.fn} callback not a func`);
const cb = args[cbIndex]!;
emitter.moveTemp(cb);
const adapter = cbT.params.length === 0 ? "scr_net_conn_thunk0" : "scr_net_conn_thunk_sock";
const entry = e.fn === "http.serverOnTimeout" ? "scr_net_server_on_timeout" : "scr_net_server_set_timeout_cb";
const callArgs = e.fn === "http.serverOnTimeout"
? `${arg(0)}, ${cb.name}, &${adapter}, ${arg(2)}`
: `${arg(0)}, ${arg(1)}, ${cb.name}, &${adapter}`;
emitter.line(`${entry}(${callArgs});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
}
case "http.serverTimeoutGet":
return finish(`scr_net_server_timeout_get(${arg(0)}, ${arg(1)})`);
case "http.serverTimeoutSet":
@@ -6761,6 +6784,14 @@ function emitHttpLibCall(state: LibCallState): Temp {
return finish(`scr_http_res_write_head_dyn(${arg(0)}, ${arg(1)}, ${arg(2)})`);
case "http.reqSetEncoding":
return finish(`scr_http_req_set_encoding(${arg(0)}, ${arg(1)})`);
case "http.reqSetTimeout":
return finish(`scr_http_req_set_timeout_plain(${arg(0)}, ${arg(1)})`);
case "http.reqSetTimeoutCb": {
const cb = args[2]!;
emitter.moveTemp(cb);
emitter.line(`scr_http_req_set_timeout(${arg(0)}, ${arg(1)}, ${cb.name});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
}
// node:http, the client slice (scr_http.c over the net client).
case "http.request":
case "http.requestCb": {
@@ -6988,9 +7019,36 @@ function emitHttpLibCall(state: LibCallState): Temp {
return finish(`scr_http_client_headers_sent(${arg(0)})`);
case "http.clientWritableEnded":
return finish(`scr_http_client_writable_ended(${arg(0)})`);
case "http.clientWritableFinished":
return finish(`scr_http_client_writable_finished(${arg(0)})`);
case "http.clientSocket":
return finish(`scr_http_client_socket(${arg(0)})`);
case "http.clientReusedSocket":
return finish(`scr_http_client_reused_socket(${arg(0)})`);
case "http.clientSetNoDelay":
return finish(`scr_http_client_set_nodelay(${arg(0)}, ${arg(1)})`);
case "http.clientSetSocketKeepAlive":
return finish(`scr_http_client_set_socket_keepalive(${arg(0)}, ${arg(1)}, ${arg(2)})`);
case "http.clientSetTimeout":
return finish(`scr_http_client_set_timeout(${arg(0)}, ${arg(1)})`);
case "http.clientSetTimeoutCb": {
const cb = args[2]!;
emitter.moveTemp(cb);
emitter.line(`scr_http_client_set_timeout_cb(${arg(0)}, ${arg(1)}, ${cb.name});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
}
case "http.statusCodes":
return finish("scr_http_status_codes()");
case "http.methods":
return finish("scr_http_methods()");
case "http.clientDestroy":
emitter.line(`scr_http_client_destroy(${arg(0)});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
case "http.clientAbort":
emitter.line(`scr_http_client_abort(${arg(0)});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
case "http.clientAborted":
return finish(`scr_http_client_aborted(${arg(0)})`);
case "http.clientDestroyed":
return finish(`scr_http_client_destroyed(${arg(0)})`);
case "http.clientOnResponse": {
@@ -7002,6 +7060,15 @@ function emitHttpLibCall(state: LibCallState): Temp {
emitter.line(`scr_http_client_on_response(${arg(0)}, ${cb.name}, &${adapter}, ${arg(2)});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
}
case "http.clientOnSocket": {
const cbT = e.args[1]!.type;
if (cbT.kind !== "func") throw new InternalCompilerError("emitter bug: http.clientOnSocket callback not a func");
const cb = args[1]!;
emitter.moveTemp(cb);
const adapter = cbT.params.length === 0 ? "scr_net_conn_thunk0" : "scr_net_conn_thunk_sock";
emitter.line(`scr_http_client_on_socket(${arg(0)}, ${cb.name}, &${adapter}, ${arg(2)});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
}
case "http.clientOnError": {
const cbT = e.args[1]!.type;
if (cbT.kind !== "func") throw new InternalCompilerError("emitter bug: http.clientOnError callback not a func");
@@ -7012,10 +7079,14 @@ function emitHttpLibCall(state: LibCallState): Temp {
return { name: "", type: e.type };
}
case "http.clientOnTimeout":
case "http.clientOnClose": {
case "http.clientOnClose":
case "http.clientOnFinish":
case "http.clientOnAbort": {
const cb = args[1]!;
emitter.moveTemp(cb);
const fn = e.fn === "http.clientOnTimeout" ? "scr_http_client_on_timeout" : "scr_http_client_on_close";
const fn = e.fn === "http.clientOnTimeout" ? "scr_http_client_on_timeout"
: e.fn === "http.clientOnClose" ? "scr_http_client_on_close"
: e.fn === "http.clientOnAbort" ? "scr_http_client_on_abort" : "scr_http_client_on_finish";
emitter.line(`${fn}(${arg(0)}, ${cb.name}, ${arg(2)});${emitter.srcComment(e.loc)}`);
return { name: "", type: e.type };
}
@@ -167,8 +167,8 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
e.fn === "net.serverOnClose" || e.fn === "net.serverOnListening" ||
e.fn === "net.sockOnEnd" || e.fn === "net.sockOnClose" || e.fn === "net.sockOnConnect" ||
e.fn === "net.sockOnTimeout" || e.fn === "net.sockOnReadable" ||
e.fn === "http.reqOnEnd" || e.fn === "http.reqOnClose" || e.fn === "http.resOnClose" ||
e.fn === "http.clientOnTimeout" || e.fn === "http.clientOnClose"
e.fn === "http.reqOnEnd" || e.fn === "http.reqOnClose" || e.fn === "http.reqOnAborted" || e.fn === "http.resOnClose" ||
e.fn === "http.clientOnTimeout" || e.fn === "http.clientOnClose" || e.fn === "http.clientOnFinish" || e.fn === "http.clientOnAbort"
) {
// Adapter-free registrations: (recv, cb /moves/, once).
const entry = {
@@ -181,9 +181,12 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
"net.sockOnReadable": "scr_net_sock_on_readable",
"http.reqOnEnd": "scr_http_req_on_end",
"http.reqOnClose": "scr_http_req_on_close",
"http.reqOnAborted": "scr_http_req_on_aborted",
"http.resOnClose": "scr_http_res_on_close",
"http.clientOnTimeout": "scr_http_client_on_timeout",
"http.clientOnClose": "scr_http_client_on_close",
"http.clientOnFinish": "scr_http_client_on_finish",
"http.clientOnAbort": "scr_http_client_on_abort",
}[e.fn]!;
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
@@ -191,15 +194,23 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
B.line(`call void @${entry}(ptr ${args[0]!.name}, ptr ${args[1]!.name}, i1 ${args[2]!.name})`);
return { name: "", type: e.type };
}
if (e.fn === "net.serverOnConnection") {
const cbT = e.args[1]!.type;
if (cbT.kind !== "func") throw new InternalCompilerError("llvm emitter bug: net.serverOnConnection callback not a func");
if (e.fn === "net.serverOnConnection" || e.fn === "http.serverOnTimeout" || e.fn === "http.serverSetTimeoutCb" || e.fn === "http.clientOnSocket") {
const cbIndex = e.fn === "http.serverSetTimeoutCb" ? 2 : 1;
const cbT = e.args[cbIndex]!.type;
if (cbT.kind !== "func") throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[1]!);
host.moveTemp(args[cbIndex]!);
const adapter = cbT.params.length === 0 ? "scr_net_conn_thunk0" : "scr_net_conn_thunk_sock";
host.declare(`declare void @${adapter}(ptr, ptr)`);
host.declare(`declare void @scr_net_server_on_connection(ptr, ptr, ptr, i1 zeroext)`);
B.line(`call void @scr_net_server_on_connection(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ptr @${adapter}, i1 ${args[2]!.name})`);
if (e.fn === "http.serverSetTimeoutCb") {
host.declare("declare void @scr_net_server_set_timeout_cb(ptr, double, ptr, ptr)");
B.line(`call void @scr_net_server_set_timeout_cb(ptr ${args[0]!.name}, double ${args[1]!.name}, ptr ${args[2]!.name}, ptr @${adapter})`);
} else {
const entry = e.fn === "net.serverOnConnection" ? "scr_net_server_on_connection"
: e.fn === "http.clientOnSocket" ? "scr_http_client_on_socket" : "scr_net_server_on_timeout";
host.declare(`declare void @${entry}(ptr, ptr, ptr, i1 zeroext)`);
B.line(`call void @${entry}(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ptr @${adapter}, i1 ${args[2]!.name})`);
}
return { name: "", type: e.type };
}
if (e.fn === "net.connect" || e.fn === "net.connectCb") {
@@ -431,11 +442,19 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
B.line(`call void @scr_http_res_on_finish(ptr ${args[0]!.name}, ptr ${args[1]!.name})`);
return { name: "", type: e.type };
}
if (e.fn === "http.resSetTimeoutCb") {
if (e.fn === "http.resSetTimeoutCb" || e.fn === "http.reqSetTimeoutCb") {
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[2]!);
host.declare(`declare void @scr_http_res_set_timeout(ptr, double, ptr)`);
B.line(`call void @scr_http_res_set_timeout(ptr ${args[0]!.name}, double ${args[1]!.name}, ptr ${args[2]!.name})`);
const entry = e.fn === "http.resSetTimeoutCb" ? "scr_http_res_set_timeout" : "scr_http_req_set_timeout";
host.declare(`declare void @${entry}(ptr, double, ptr)`);
B.line(`call void @${entry}(ptr ${args[0]!.name}, double ${args[1]!.name}, ptr ${args[2]!.name})`);
return { name: "", type: e.type };
}
if (e.fn === "http.clientSetTimeoutCb") {
const args = e.args.map((a) => host.emitExpr(a));
host.moveTemp(args[2]!);
host.declare("declare void @scr_http_client_set_timeout_cb(ptr, double, ptr)");
B.line(`call void @scr_http_client_set_timeout_cb(ptr ${args[0]!.name}, double ${args[1]!.name}, ptr ${args[2]!.name})`);
return { name: "", type: e.type };
}
if (e.fn === "net.sockOnFinish") {
@@ -666,6 +666,9 @@ export const LIB_FN_SYMS: Record<string, string> = {
"http.serverJoinDupHeaders": "scr_http_server_join_duplicate_headers",
"http.serverAllowMissingHostHeader": "scr_http_server_allow_missing_host_header",
"http.serverTimeoutGet": "scr_net_server_timeout_get",
"http.serverSetTimeout": "scr_net_server_set_timeout_plain",
"http.serverCloseAllConnections": "scr_net_server_close_all_connections",
"http.serverCloseIdleConnections": "scr_net_server_close_idle_connections",
"http.serverTimeoutSet": "scr_net_server_timeout_set",
"http.serverTimeoutOptionSet": "scr_net_server_timeout_option_set",
"http.clientWrite": "scr_http_client_write_str",
@@ -692,7 +695,18 @@ export const LIB_FN_SYMS: Record<string, string> = {
"http.clientProtocol": "scr_http_client_protocol",
"http.clientHeadersSent": "scr_http_client_headers_sent",
"http.clientWritableEnded": "scr_http_client_writable_ended",
"http.clientWritableFinished": "scr_http_client_writable_finished",
"http.clientSocket": "scr_http_client_socket",
"http.clientReusedSocket": "scr_http_client_reused_socket",
"http.clientSetNoDelay": "scr_http_client_set_nodelay",
"http.clientSetSocketKeepAlive": "scr_http_client_set_socket_keepalive",
"http.clientSetTimeout": "scr_http_client_set_timeout",
"http.statusCodes": "scr_http_status_codes",
"http.methods": "scr_http_methods",
"http.reqSetTimeout": "scr_http_req_set_timeout_plain",
"http.clientDestroy": "scr_http_client_destroy",
"http.clientAbort": "scr_http_client_abort",
"http.clientAborted": "scr_http_client_aborted",
"http.clientDestroyed": "scr_http_client_destroyed",
"http2.streamUndefCall": "scr_http2_stream_undef_call",
"http.agentNew": "scr_http_agent_new",
@@ -938,6 +938,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
if (bi.module === "module" && bi.member === "builtinModules") {
return builtinModulesArrayLit(loc);
}
if (bi.module === "http" && bi.member === "METHODS") {
return { kind: "libCall", fn: "http.methods", args: [], type: arrayOf(STRING), loc };
}
if (bi.module === "http" && bi.member === "STATUS_CODES") {
return { kind: "libCall", fn: "http.statusCodes", args: [], type: DYN, loc };
}
// tls.rootCertificates: a runtime-valued module constant (the
// cached bundled-CA array) — the one member read that lowers
// to a libCall instead of a baked literal.
@@ -1031,6 +1031,26 @@ function lowerNetServerMethodCall(lowerer: Lowerer, call: ts.CallExpression,
const name = access.name.text;
const loc = locOf(call);
const args = call.arguments;
if (name === "setTimeout") {
if (args.length < 1 || args.length > 2) lowerer.noLowering("server.setTimeout arguments", call, "pass milliseconds and an optional socket callback");
const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T);
const ms = lowerer.lowerExprExpecting(args[0]!, F64);
const cb = args.length === 2
? lowerCallbackArg(lowerer, args[1]!, "server timeout callbacks", 1,
(p) => p.kind === "netSocket", "use (socket) or ()", [NETSOCKET_T]).cb
: null;
const fn: IrLibFn = cb === null ? "http.serverSetTimeout" : "http.serverSetTimeoutCb";
const callArgs = cb === null ? [receiver, ms] : [receiver, ms, cb];
if (resultIsDiscarded(call)) return { kind: "libCall", fn, args: callArgs, type: VOID, loc };
return receiverReturningCall(lowerer, fn, callArgs, NETSERVER_T, loc);
}
if (name === "closeAllConnections" || name === "closeIdleConnections") {
requireStatementPosition(lowerer, call, `server.${name}()`);
if (args.length !== 0) lowerer.noLowering(`server.${name} arguments`, call, `${name}() takes no arguments`);
const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T);
const fn: IrLibFn = name === "closeAllConnections" ? "http.serverCloseAllConnections" : "http.serverCloseIdleConnections";
return { kind: "libCall", fn, args: [receiver], type: VOID, loc };
}
if (name === "listen") {
if (args.length < 1 || args.length > 3) {
lowerer.noLowering(
@@ -1280,6 +1300,11 @@ function lowerNetServerMethodCall(lowerer: Lowerer, call: ts.CallExpression,
const { cb } = lowerCallbackArg(lowerer, args[1]!, "close listeners", 0, () => false, "use ()", []);
return { kind: "libCall", fn: "net.serverOnClose", args: [receiver, cb, once], type: VOID, loc };
}
if (event === "timeout") {
const { cb } = lowerCallbackArg(lowerer, args[1]!, "timeout listeners", 1,
(p) => p.kind === "netSocket", "use (socket) or ()", [NETSOCKET_T]);
return { kind: "libCall", fn: "http.serverOnTimeout", args: [receiver, cb, once], type: VOID, loc };
}
if (event === "listening") {
// The deferred bind emit — listen(port, cb)'s event twin: fires
// once after a successful listen; late registrations on an
@@ -2093,7 +2118,7 @@ export function lowerServerProperty(lowerer: Lowerer, expr: ts.PropertyAccessExp
const receiver = coerceToHandle(lowerer, expr.expression, HTTPREQ_T);
return { kind: "libCall", fn: "http.reqStatusCode", args: [receiver], type: lowerer.withUndefinedArm(F64), loc };
}
if (name === "socket") {
if (name === "socket" || name === "connection") {
const receiver = coerceToHandle(lowerer, expr.expression, HTTPREQ_T);
return { kind: "libCall", fn: "http.reqSocket", args: [receiver], type: NETSOCKET_T, loc };
}
@@ -2254,6 +2279,11 @@ export function lowerServerProperty(lowerer: Lowerer, expr: ts.PropertyAccessExp
const receiver = coerceToHandle(lowerer, expr.expression, HTTPCLIENTREQ_T);
return { kind: "libCall", fn: "http.clientDestroyed", args: [receiver], type: BOOL, loc };
}
if (recvKind === "httpClientReq" && lowerer.isStdlibMember(expr) &&
(expr.name.text === "socket" || expr.name.text === "connection")) {
const receiver = coerceToHandle(lowerer, expr.expression, HTTPCLIENTREQ_T);
return { kind: "libCall", fn: "http.clientSocket", args: [receiver], type: NETSOCKET_T, loc };
}
if (recvKind === "httpClientReq" && lowerer.isStdlibMember(expr) && expr.name.text === "writableCorked") {
const receiver = coerceToHandle(lowerer, expr.expression, HTTPCLIENTREQ_T);
return { kind: "libCall", fn: "http.clientWritableCorked", args: [receiver], type: F64, loc };
@@ -2268,8 +2298,11 @@ export function lowerServerProperty(lowerer: Lowerer, expr: ts.PropertyAccessExp
const receiver = coerceToHandle(lowerer, expr.expression, HTTPCLIENTREQ_T);
return { kind: "libCall", fn: strFn, args: [receiver], type: STRING, loc };
}
const boolFn: IrLibFn | null = name === "headersSent" ? "http.clientHeadersSent"
: name === "writableEnded" ? "http.clientWritableEnded" : null;
const boolFn: IrLibFn | null = name === "aborted" ? "http.clientAborted"
: name === "headersSent" ? "http.clientHeadersSent"
: name === "writableEnded" || name === "finished" ? "http.clientWritableEnded"
: name === "writableFinished" ? "http.clientWritableFinished"
: name === "reusedSocket" ? "http.clientReusedSocket" : null;
if (boolFn !== null) {
const receiver = coerceToHandle(lowerer, expr.expression, HTTPCLIENTREQ_T);
return { kind: "libCall", fn: boolFn, args: [receiver], type: BOOL, loc };
@@ -2293,6 +2326,10 @@ export function lowerServerProperty(lowerer: Lowerer, expr: ts.PropertyAccessExp
const receiver = coerceToHandle(lowerer, expr.expression, HTTPRES_T);
return { kind: "libCall", fn: "http.resWritableEnded", args: [receiver], type: BOOL, loc };
}
if (recvKind === "httpRes" && lowerer.isStdlibMember(expr) && expr.name.text === "finished") {
const receiver = coerceToHandle(lowerer, expr.expression, HTTPRES_T);
return { kind: "libCall", fn: "http.resWritableEnded", args: [receiver], type: BOOL, loc };
}
if (recvKind === "httpRes" && lowerer.isStdlibMember(expr) && expr.name.text === "writableCorked") {
const receiver = coerceToHandle(lowerer, expr.expression, HTTPRES_T);
return { kind: "libCall", fn: "http.resWritableCorked", args: [receiver], type: F64, loc };
@@ -4341,6 +4378,33 @@ function lowerHttpClientMethodCall(lowerer: Lowerer, call: ts.CallExpression,
: name === "cork" ? "http.clientCork" : "http.clientUncork";
return { kind: "libCall", fn, args: [receiver], type: VOID, loc };
}
if (name === "setNoDelay") {
requireStatementPosition(lowerer, call, "request.setNoDelay(...)");
if (args.length > 1) lowerer.noLowering("request.setNoDelay arguments", call, "pass an optional boolean");
const receiver = coerceToHandle(lowerer, access.expression, HTTPCLIENTREQ_T);
const enable = args.length === 0 ? boolLit(true, loc) : lowerer.lowerExprExpecting(args[0]!, BOOL);
return { kind: "libCall", fn: "http.clientSetNoDelay", args: [receiver, enable], type: VOID, loc };
}
if (name === "setSocketKeepAlive") {
requireStatementPosition(lowerer, call, "request.setSocketKeepAlive(...)");
if (args.length > 2) lowerer.noLowering("request.setSocketKeepAlive arguments", call, "pass an optional boolean and initial delay in milliseconds");
const receiver = coerceToHandle(lowerer, access.expression, HTTPCLIENTREQ_T);
const enable = args.length === 0 ? boolLit(true, loc) : lowerer.lowerExprExpecting(args[0]!, BOOL);
const delay = args.length < 2 ? numLit(0, loc) : lowerer.lowerExprExpecting(args[1]!, F64);
return { kind: "libCall", fn: "http.clientSetSocketKeepAlive", args: [receiver, enable, delay], type: VOID, loc };
}
if (name === "setTimeout") {
if (args.length < 1 || args.length > 2) lowerer.noLowering("request.setTimeout arguments", call, "pass milliseconds and an optional callback");
const receiver = coerceToHandle(lowerer, access.expression, HTTPCLIENTREQ_T);
const ms = lowerer.lowerExprExpecting(args[0]!, F64);
const cb = args.length === 2
? lowerCallbackArg(lowerer, args[1]!, "request timeout callbacks", 0, () => false, "use ()", []).cb
: null;
const fn: IrLibFn = cb === null ? "http.clientSetTimeout" : "http.clientSetTimeoutCb";
const callArgs = cb === null ? [receiver, ms] : [receiver, ms, cb];
if (resultIsDiscarded(call)) return { kind: "libCall", fn, args: callArgs, type: VOID, loc };
return receiverReturningCall(lowerer, fn, callArgs, HTTPCLIENTREQ_T, loc);
}
if (name === "addTrailers") {
requireStatementPosition(lowerer, call, "request.addTrailers(...)");
if (args.length !== 1) lowerer.noLowering("request.addTrailers argument count", call, "pass one trailer object or pair-list literal");
@@ -4378,13 +4442,13 @@ function lowerHttpClientMethodCall(lowerer: Lowerer, call: ts.CallExpression,
}
lowerer.noLowering(`${name} of '${lowerer.fmt(data.type)}' data`, args[0] ?? call, NARROW_DATA_HINT);
}
if (name === "destroy") {
requireStatementPosition(lowerer, call, "request.destroy()");
if (name === "destroy" || name === "abort") {
requireStatementPosition(lowerer, call, `request.${name}()`);
if (args.length !== 0) {
lowerer.noLowering(`destroy with ${args.length} arguments`, call, "destroy() takes no arguments here");
lowerer.noLowering(`${name} with ${args.length} arguments`, call, `${name}() takes no arguments here`);
}
const receiver = lowerer.lowerExpr(access.expression);
return { kind: "libCall", fn: "http.clientDestroy", args: [receiver], type: VOID, loc };
return { kind: "libCall", fn: name === "destroy" ? "http.clientDestroy" : "http.clientAbort", args: [receiver], type: VOID, loc };
}
if ((name === "on" || name === "once" || name === "addListener") && args.length === 2) {
requireStatementPosition(lowerer, call, `request.${name}(...)`);
@@ -4401,6 +4465,11 @@ function lowerHttpClientMethodCall(lowerer: Lowerer, call: ts.CallExpression,
);
return { kind: "libCall", fn: "http.clientOnResponse", args: [receiver, cb, once], type: VOID, loc };
}
if (event === "socket") {
const { cb } = lowerCallbackArg(lowerer, args[1]!, "socket listeners", 1,
(p) => p.kind === "netSocket", "use (socket) or ()", [NETSOCKET_T]);
return { kind: "libCall", fn: "http.clientOnSocket", args: [receiver, cb, once], type: VOID, loc };
}
if (event === "error") {
const { cb } = lowerCallbackArg(
lowerer, args[1]!, "error listeners", 1,
@@ -4410,9 +4479,11 @@ function lowerHttpClientMethodCall(lowerer: Lowerer, call: ts.CallExpression,
);
return { kind: "libCall", fn: "http.clientOnError", args: [receiver, cb, once], type: VOID, loc };
}
if (event === "timeout" || event === "close") {
if (event === "timeout" || event === "close" || event === "finish" || event === "abort") {
const { cb } = lowerCallbackArg(lowerer, args[1]!, `${event} listeners`, 0, () => false, "use ()", []);
const fn: IrLibFn = event === "timeout" ? "http.clientOnTimeout" : "http.clientOnClose";
const fn: IrLibFn = event === "timeout" ? "http.clientOnTimeout"
: event === "close" ? "http.clientOnClose"
: event === "abort" ? "http.clientOnAbort" : "http.clientOnFinish";
return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc };
}
if (event === "upgrade") {
@@ -4451,7 +4522,7 @@ function lowerHttpClientMethodCall(lowerer: Lowerer, call: ts.CallExpression,
lowerer.noLowering(
`request.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
args[0]!,
'"response", "upgrade", "error", "timeout", and "close" are the supported request events (as literals)',
'"response", "socket", "upgrade", "error", "timeout", "finish", "abort", and "close" are the supported request events (as literals)',
);
}
lowerer.noLowering(
@@ -4556,6 +4627,15 @@ function lowerHttpReqMethodCall(lowerer: Lowerer, call: ts.CallExpression,
const name = access.name.text;
const loc = locOf(call);
const args = call.arguments;
if (name === "setTimeout") {
requireStatementPosition(lowerer, call, "message.setTimeout(...)");
if (args.length < 1 || args.length > 2) lowerer.noLowering("message.setTimeout arguments", call, "pass milliseconds and an optional callback");
const receiver = coerceToHandle(lowerer, access.expression, HTTPREQ_T);
const ms = lowerer.lowerExprExpecting(args[0]!, F64);
if (args.length === 1) return { kind: "libCall", fn: "http.reqSetTimeout", args: [receiver, ms], type: VOID, loc };
const { cb } = lowerCallbackArg(lowerer, args[1]!, "message timeout callbacks", 0, () => false, "use ()", []);
return { kind: "libCall", fn: "http.reqSetTimeoutCb", args: [receiver, ms, cb], type: VOID, loc };
}
if (name === "resume" || name === "destroy") {
requireStatementPosition(lowerer, call, `req.${name}()`);
if (args.length !== 0) {
@@ -5007,10 +5087,14 @@ function lowerHttpResMethodCall(lowerer: Lowerer, call: ts.CallExpression,
const { cb } = lowerCallbackArg(lowerer, args[1]!, "close listeners", 0, () => false, "use ()", []);
return { kind: "libCall", fn: "http.resOnClose", args: [receiver, cb, once], type: VOID, loc };
}
if (event === "finish") {
const { cb } = lowerCallbackArg(lowerer, args[1]!, "finish listeners", 0, () => false, "use ()", []);
return { kind: "libCall", fn: "http.resOnFinish", args: [receiver, cb], type: VOID, loc };
}
lowerer.noLowering(
`res.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
args[0]!,
'"close" is the supported response event (as a literal)',
'"close" and "finish" are the supported response events (as literals)',
);
}
lowerer.noLowering(
@@ -10136,6 +10136,12 @@ export class Lowerer {
if (bi.module === "module" && bi.member === "builtinModules") {
return builtinModulesArrayLit(loc);
}
if (bi.module === "http" && bi.member === "METHODS") {
return { kind: "libCall", fn: "http.methods", args: [], type: arrayOf(STRING), loc };
}
if (bi.module === "http" && bi.member === "STATUS_CODES") {
return { kind: "libCall", fn: "http.statusCodes", args: [], type: DYN, loc };
}
// tls.rootCertificates through the namespace: the same runtime-valued
// constant the named-import read lowers to.
{
@@ -938,6 +938,7 @@ export const BUILTIN_MODULE_FNS: Record<string, Record<string, BuiltinModuleFn |
* thread machinery) and never runs as a cluster worker (cluster forks the
* node binary itself) — Node's own answers for a directly-run script. */
export const BUILTIN_MODULE_CONSTS: Record<string, Record<string, string | number | boolean | undefined> | undefined> = {
http: { maxHeaderSize: 16384 },
path: { sep: "/", delimiter: ":" },
"path/posix": { sep: "/", delimiter: ":" },
"path/win32": { sep: "\\", delimiter: ";" },
+21
View File
@@ -2752,6 +2752,11 @@ export type IrLibFn =
* The constructor-option setter takes dyn so explicit undefined remains
* distinguishable from a numeric value until its Node validation ladder. */
| "http.serverTimeoutGet"
| "http.serverSetTimeout"
| "http.serverSetTimeoutCb"
| "http.serverOnTimeout"
| "http.serverCloseAllConnections"
| "http.serverCloseIdleConnections"
| "http.serverTimeoutSet"
| "http.serverTimeoutOptionSet"
/** server.on("listening", cb) — the deferred listen-callback list. */
@@ -3212,17 +3217,33 @@ export type IrLibFn =
| "http.clientProtocol"
| "http.clientHeadersSent"
| "http.clientWritableEnded"
| "http.clientWritableFinished"
| "http.clientSocket"
| "http.clientReusedSocket"
| "http.clientSetNoDelay"
| "http.clientSetSocketKeepAlive"
| "http.clientSetTimeout"
| "http.clientSetTimeoutCb"
| "http.statusCodes"
| "http.methods"
| "http.reqSetTimeout"
| "http.reqSetTimeoutCb"
/** request/get with a URL-STRING first argument: the runtime parses it
* (WHATWG) and dials — throws catchably on an unparsable input or a
* non-http scheme. */
| "http.requestUrl"
| "http.requestUrlCb"
| "http.clientDestroy"
| "http.clientAbort"
| "http.clientAborted"
| "http.clientDestroyed"
| "http.clientOnResponse"
| "http.clientOnSocket"
| "http.clientOnFinish"
| "http.clientOnError"
| "http.clientOnTimeout"
| "http.clientOnClose"
| "http.clientOnAbort"
/** fs/promises (scr_lib.c over scr_async.c's settled minting): the SAME
* sync syscalls, wrapped in an ALREADY-SETTLED promise — failure
* REJECTS (catchable at the await) instead of throwing, so none of
+24 -1
View File
@@ -509,6 +509,11 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
"http.serverJoinDupHeaders": { argTypes: [NETSERVER_T], result: VOID },
"http.serverAllowMissingHostHeader": { argTypes: [NETSERVER_T], result: VOID },
"http.serverTimeoutGet": { argTypes: [NETSERVER_T, F64], result: F64 },
"http.serverSetTimeout": { argTypes: [NETSERVER_T, F64], result: VOID },
"http.serverSetTimeoutCb": { argTypes: [NETSERVER_T, F64, null], result: VOID },
"http.serverOnTimeout": { argTypes: [NETSERVER_T, null, BOOL], result: VOID },
"http.serverCloseAllConnections": { argTypes: [NETSERVER_T], result: VOID },
"http.serverCloseIdleConnections": { argTypes: [NETSERVER_T], result: VOID },
"http.serverTimeoutSet": { argTypes: [NETSERVER_T, F64, F64], result: VOID },
"http.serverTimeoutOptionSet": { argTypes: [NETSERVER_T, F64, DYN], result: VOID },
"net.serverOnListening": { argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }, BOOL], result: VOID },
@@ -785,12 +790,28 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
"http.clientProtocol": { argTypes: [HTTPCLIENTREQ_T], result: STRING },
"http.clientHeadersSent": { argTypes: [HTTPCLIENTREQ_T], result: BOOL },
"http.clientWritableEnded": { argTypes: [HTTPCLIENTREQ_T], result: BOOL },
"http.clientWritableFinished": { argTypes: [HTTPCLIENTREQ_T], result: BOOL },
"http.clientSocket": { argTypes: [HTTPCLIENTREQ_T], result: NETSOCKET_T },
"http.clientReusedSocket": { argTypes: [HTTPCLIENTREQ_T], result: BOOL },
"http.clientSetNoDelay": { argTypes: [HTTPCLIENTREQ_T, BOOL], result: VOID },
"http.clientSetSocketKeepAlive": { argTypes: [HTTPCLIENTREQ_T, BOOL, F64], result: VOID },
"http.clientSetTimeout": { argTypes: [HTTPCLIENTREQ_T, F64], result: VOID },
"http.clientSetTimeoutCb": { argTypes: [HTTPCLIENTREQ_T, F64, { kind: "func", params: [], ret: VOID }], result: VOID },
"http.statusCodes": { argTypes: [], result: DYN },
"http.methods": { argTypes: [], result: arrayOf(STRING) },
"http.reqSetTimeout": { argTypes: [HTTPREQ_T, F64], result: VOID },
"http.reqSetTimeoutCb": { argTypes: [HTTPREQ_T, F64, { kind: "func", params: [], ret: VOID }], result: VOID },
"http.clientDestroy": { argTypes: [HTTPCLIENTREQ_T], result: VOID },
"http.clientAbort": { argTypes: [HTTPCLIENTREQ_T], result: VOID },
"http.clientAborted": { argTypes: [HTTPCLIENTREQ_T], result: BOOL },
"http.clientDestroyed": { argTypes: [HTTPCLIENTREQ_T], result: BOOL },
"http.clientOnResponse": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID },
"http.clientOnSocket": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID },
"http.clientOnFinish": { argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], result: VOID },
"http.clientOnError": { argTypes: [HTTPCLIENTREQ_T, null, BOOL], result: VOID },
"http.clientOnTimeout": { argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], result: VOID },
"http.clientOnClose": { argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], result: VOID },
"http.clientOnAbort": { argTypes: [HTTPCLIENTREQ_T, { kind: "func", params: [], ret: VOID }, BOOL], result: VOID },
"cp.execSync": {
argTypes: [STRING, arrayOf(STRING), BOOL, STRING, BOOL, STRING, BOOL, arrayOf(STRING), F64, F64, F64],
result: STRING,
@@ -4301,13 +4322,15 @@ function validateFunction(
}
if (e.fn === "net.createServerCb" || e.fn === "net.serverOnConnection" ||
e.fn === "net.serverOnSecureConnection" ||
e.fn === "http.serverSetTimeoutCb" || e.fn === "http.serverOnTimeout" ||
e.fn === "http.clientOnSocket" ||
e.fn === "net.sockOnData" || e.fn === "net.serverOnError" ||
e.fn === "net.sockOnError") {
// The program-dependent listener shapes: a void closure with no
// params, or exactly the one supported parameter per event
// (socket handle / data chunk bytes / error %Error). The
// callback slot is arg 0 for createServerCb, arg 1 otherwise.
const cbT = e.args[e.fn === "net.createServerCb" ? 0 : 1]?.type;
const cbT = e.args[e.fn === "net.createServerCb" ? 0 : e.fn === "http.serverSetTimeoutCb" ? 2 : 1]?.type;
let ok = cbT?.kind === "func" && cbT.ret.kind === "void" && cbT.params.length <= 1;
if (ok && cbT?.kind === "func" && cbT.params.length === 1) {
const p = cbT.params[0]!;
+7
View File
@@ -1123,6 +1123,13 @@
"status": "static",
"note": "recognized module (bare and node:-prefixed specifiers)"
},
{
"id": "node-builtin.http.maxHeaderSize",
"kind": "node-builtin",
"name": "http.maxHeaderSize",
"status": "static",
"note": "constant value read"
},
{
"id": "node-builtin.http2",
"kind": "node-builtin",
+1 -1
View File
@@ -391,7 +391,7 @@ exit 99
expect(invocations.some((line) => /^cc .*vendor.*zlib/.test(line))).toBe(true);
expect(invocations.some((line) => /^ar rcs .*libqjs\.a/.test(line))).toBe(true);
expect(invocations.some((line) => /^ar rcs .*libmbedtls\.a/.test(line))).toBe(true);
expect(invocations.some((line) => line.includes("-lz"))).toBe(false);
expect(invocations.some((line) => /(?:^|\s)-lz(?:\s|$)/.test(line))).toBe(false);
expect(await readFile(unexpectedToolPath).catch(() => "")).toBe("");
} finally {
if (oldPath === undefined) delete process.env["PATH"];
+267 -44
View File
@@ -66,44 +66,107 @@ static void scr_http_oom(void) {
abort();
}
/* Node's STATUS_CODES reason phrases (the slice's subset; everything the
* fixtures and portless's handlers send, plus the common neighbors). */
static bool scr_http_timeout_valid(double ms) {
if (isfinite(ms) && ms >= 0) return true;
char recv[48], msg[176];
scr_num_received(ms, recv);
int len = snprintf(msg, sizeof msg,
"The value of \"msecs\" is out of range. It must be a non-negative finite number. Received %s", recv);
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
return false;
}
/* Node v24.15.0's STATUS_CODES table, shared by the module export and the
* default ServerResponse reason phrase. */
static const struct { int code; const char *reason; } scr_http_status_codes_table[] = {
{100, "Continue"}, {101, "Switching Protocols"}, {102, "Processing"},
{103, "Early Hints"}, {200, "OK"}, {201, "Created"}, {202, "Accepted"},
{203, "Non-Authoritative Information"}, {204, "No Content"},
{205, "Reset Content"}, {206, "Partial Content"}, {207, "Multi-Status"},
{208, "Already Reported"}, {226, "IM Used"}, {300, "Multiple Choices"},
{301, "Moved Permanently"}, {302, "Found"}, {303, "See Other"},
{304, "Not Modified"}, {305, "Use Proxy"}, {307, "Temporary Redirect"},
{308, "Permanent Redirect"}, {400, "Bad Request"}, {401, "Unauthorized"},
{402, "Payment Required"}, {403, "Forbidden"}, {404, "Not Found"},
{405, "Method Not Allowed"}, {406, "Not Acceptable"},
{407, "Proxy Authentication Required"}, {408, "Request Timeout"},
{409, "Conflict"}, {410, "Gone"}, {411, "Length Required"},
{412, "Precondition Failed"}, {413, "Payload Too Large"},
{414, "URI Too Long"}, {415, "Unsupported Media Type"},
{416, "Range Not Satisfiable"}, {417, "Expectation Failed"},
{418, "I'm a Teapot"}, {421, "Misdirected Request"},
{422, "Unprocessable Entity"}, {423, "Locked"},
{424, "Failed Dependency"}, {425, "Too Early"},
{426, "Upgrade Required"}, {428, "Precondition Required"},
{429, "Too Many Requests"}, {431, "Request Header Fields Too Large"},
{451, "Unavailable For Legal Reasons"}, {500, "Internal Server Error"},
{501, "Not Implemented"}, {502, "Bad Gateway"},
{503, "Service Unavailable"}, {504, "Gateway Timeout"},
{505, "HTTP Version Not Supported"}, {506, "Variant Also Negotiates"},
{507, "Insufficient Storage"}, {508, "Loop Detected"},
{509, "Bandwidth Limit Exceeded"}, {510, "Not Extended"},
{511, "Network Authentication Required"},
};
static const char *scr_http_reason(int code) {
switch (code) {
case 100: return "Continue";
case 101: return "Switching Protocols";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 204: return "No Content";
case 206: return "Partial Content";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 307: return "Temporary Redirect";
case 308: return "Permanent Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 413: return "Payload Too Large";
case 414: return "URI Too Long";
case 415: return "Unsupported Media Type";
case 429: return "Too Many Requests";
case 431: return "Request Header Fields Too Large";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 508: return "Loop Detected";
default: return "unknown";
for (size_t i = 0; i < sizeof scr_http_status_codes_table / sizeof scr_http_status_codes_table[0]; i++) {
if (scr_http_status_codes_table[i].code == code) return scr_http_status_codes_table[i].reason;
}
return "unknown";
}
static ScrDyn *scr_http_status_codes_value;
static void scr_http_status_codes_cleanup(void) {
scr_dyn_release(scr_http_status_codes_value);
scr_http_status_codes_value = NULL;
}
ScrDyn *scr_http_status_codes(void) {
if (!scr_http_status_codes_value) {
ScrDyn *codes = scr_dyn_new_obj();
for (size_t i = 0; i < sizeof scr_http_status_codes_table / sizeof scr_http_status_codes_table[0]; i++) {
const int code = scr_http_status_codes_table[i].code;
const char *reason = scr_http_status_codes_table[i].reason;
char key[4];
const int key_len = snprintf(key, sizeof key, "%d", code);
ScrStr *value = scr_str_new(reason, strlen(reason));
scr_dyn_obj_set(codes, key, (size_t)key_len, scr_dyn_new_str(value));
scr_str_release(value);
}
scr_http_status_codes_value = codes;
atexit(scr_http_status_codes_cleanup);
}
return scr_dyn_retain(scr_http_status_codes_value);
}
static const char *const scr_http_methods_table[] = {
"ACL", "BIND", "CHECKOUT", "CONNECT", "COPY", "DELETE", "GET", "HEAD",
"LINK", "LOCK", "M-SEARCH", "MERGE", "MKACTIVITY", "MKCALENDAR",
"MKCOL", "MOVE", "NOTIFY", "OPTIONS", "PATCH", "POST", "PROPFIND",
"PROPPATCH", "PURGE", "PUT", "QUERY", "REBIND", "REPORT", "SEARCH",
"SOURCE", "SUBSCRIBE", "TRACE", "UNBIND", "UNLINK", "UNLOCK", "UNSUBSCRIBE",
};
static ScrArr *scr_http_methods_value;
static void scr_http_methods_cleanup(void) {
scr_arr_release(scr_http_methods_value);
scr_http_methods_value = NULL;
}
ScrArr *scr_http_methods(void) {
if (!scr_http_methods_value) {
const size_t n = sizeof scr_http_methods_table / sizeof scr_http_methods_table[0];
ScrArr *methods = scr_arr_new(SCR_ELEM_STR, n);
for (size_t i = 0; i < n; i++) {
const char *name = scr_http_methods_table[i];
scr_arr_push_ref(methods, scr_str_new(name, strlen(name)));
}
scr_http_methods_value = methods;
atexit(scr_http_methods_cleanup);
}
return scr_arr_retain(scr_http_methods_value);
}
/* ── the h2 compat transport seam ────────────────────────────────────────
@@ -577,6 +640,10 @@ void scr_http_req_pause(ScrHttpReq *r) {
* registers once('timeout') there, Node's delegation. Finished/destroyed
* messages skip (Node no-ops once the stream is done). */
void scr_http_req_set_timeout(ScrHttpReq *r, double ms, ScrClosure *cb /*moves, nullable*/) {
if (!scr_http_timeout_valid(ms)) {
if (cb) scr_closure_release(cb);
return;
}
if (r->ended || r->destroyed || r->close_emitted || !r->sock) {
/* the message is done — Node no-ops there (never arms, never fires) */
if (cb) scr_closure_release(cb);
@@ -586,6 +653,10 @@ void scr_http_req_set_timeout(ScrHttpReq *r, double ms, ScrClosure *cb /*moves,
if (cb) scr_net_sock_on_timeout(r->sock, cb, true);
}
void scr_http_req_set_timeout_plain(ScrHttpReq *r, double ms) {
scr_http_req_set_timeout(r, ms, NULL);
}
bool scr_http_req_destroyed_flag(ScrHttpReq *r) { return r->destroyed; }
bool scr_http_req_readable(ScrHttpReq *r) { return !r->ended && !r->destroyed; }
@@ -1699,7 +1770,10 @@ enum {
SCR_HTTP_EMIT_CLIENT_CLOSE = 4, /* ScrHttpClientReq */
SCR_HTTP_EMIT_RES_FINISH = 5, /* ScrHttpRes: the end(cb) callbacks */
SCR_HTTP_EMIT_REQ_DRAIN = SCR_HTTP_EMIT_REQ_DRAIN_K, /* ScrHttpReq: resume()'s drain */
SCR_HTTP_EMIT_RES_WCB = 7 /* ScrHttpRes: write(chunk, cb) callbacks */
SCR_HTTP_EMIT_RES_WCB = 7, /* ScrHttpRes: write(chunk, cb) callbacks */
SCR_HTTP_EMIT_CLIENT_SOCKET = 8, /* ScrHttpClientReq: assigned socket */
SCR_HTTP_EMIT_CLIENT_FINISH = 9, /* ScrHttpClientReq: outgoing body flushed */
SCR_HTTP_EMIT_CLIENT_ABORT = 10 /* ScrHttpClientReq: abort() notification */
};
typedef struct {
@@ -1712,6 +1786,9 @@ static size_t scr_http_emits_head = 0, scr_http_emits_len = 0, scr_http_emits_ca
static void scr_http_client_release_internal(struct ScrHttpClientReq *c);
static void scr_http_client_settle(struct ScrHttpClientReq *c);
static void scr_http_client_emit_socket(struct ScrHttpClientReq *c);
static void scr_http_client_emit_finish(struct ScrHttpClientReq *c);
static void scr_http_client_emit_abort(struct ScrHttpClientReq *c);
/* Exit-time flag (set by the atexit cleanup, which runs BEFORE the net
* unit's — atexit LIFO, http installs after net): once the emits queue
@@ -1756,6 +1833,9 @@ static void scr_http_emit_release(ScrHttpEmit *e) {
case SCR_HTTP_EMIT_REQ_DRAIN:
case SCR_HTTP_EMIT_REQ_ABORTED: scr_http_req_release((ScrHttpReq *)e->h); break;
case SCR_HTTP_EMIT_CLIENT_CLOSE:
case SCR_HTTP_EMIT_CLIENT_SOCKET:
case SCR_HTTP_EMIT_CLIENT_FINISH:
case SCR_HTTP_EMIT_CLIENT_ABORT:
scr_http_client_release_internal((struct ScrHttpClientReq *)e->h);
break;
}
@@ -1874,6 +1954,15 @@ static void scr_http_proto_sweep(void) {
case SCR_HTTP_EMIT_CLIENT_CLOSE:
scr_http_client_settle((struct ScrHttpClientReq *)e.h);
break;
case SCR_HTTP_EMIT_CLIENT_SOCKET:
scr_http_client_emit_socket((struct ScrHttpClientReq *)e.h);
break;
case SCR_HTTP_EMIT_CLIENT_FINISH:
scr_http_client_emit_finish((struct ScrHttpClientReq *)e.h);
break;
case SCR_HTTP_EMIT_CLIENT_ABORT:
scr_http_client_emit_abort((struct ScrHttpClientReq *)e.h);
break;
case SCR_HTTP_EMIT_RES_FINISH: {
ScrHttpRes *res = (ScrHttpRes *)e.h;
res->finish_queued = false; /* a later end(cb) on the finished res re-queues */
@@ -2736,6 +2825,12 @@ static void scr_http_conn_free(void *ctx) {
free(conn);
}
static bool scr_http_conn_is_idle(void *ctx) {
ScrHttpConn *conn = (ScrHttpConn *)ctx;
return !conn->client_mode && conn->state == SCR_HTTP_HEAD &&
conn->req == NULL && conn->len == 0;
}
/* The server's native connection hook: one parser per accepted socket. */
static void scr_http_on_connection(void *ctx, ScrNetSocket *sock) {
ScrHttpSrvCtx *srv = (ScrHttpSrvCtx *)ctx;
@@ -2745,6 +2840,7 @@ static void scr_http_on_connection(void *ctx, ScrNetSocket *sock) {
conn->srv = scr_http_srv_ctx_retain(srv);
scr_net_sock_set_native_reader(sock, &scr_http_conn_data, &scr_http_conn_eof,
&scr_http_conn_closed, conn, &scr_http_conn_free);
scr_net_sock_set_native_idle_checker(sock, &scr_http_conn_is_idle);
scr_net_sock_set_native_events(sock, NULL, &scr_http_conn_err);
}
@@ -2886,7 +2982,7 @@ ScrStr *scr_http_req_http_version(ScrHttpReq *r) {
double scr_http_req_http_version_major(ScrHttpReq *r) { return r->http2 ? 2 : 1; }
double scr_http_req_http_version_minor(ScrHttpReq *r) { return r->http2 || r->http10 ? 0 : 1; }
bool scr_http_req_aborted_flag(ScrHttpReq *r) { return r->aborted; }
bool scr_http_req_complete(ScrHttpReq *r) { return r->ended; }
bool scr_http_req_complete(ScrHttpReq *r) { return r->ended && !r->aborted; }
/* The stream died with the response side open: 'aborted' fires NOW (the
* teardown macrotask, Node's position), then the close rides the sweep. */
@@ -3033,13 +3129,19 @@ struct ScrHttpClientReq {
size_t cork_len, cork_cap;
bool ended;
bool destroyed;
bool aborted;
bool abort_emitted;
bool sock_ready;
bool socket_emitted;
bool finish_queued;
bool writable_finished;
bool response_started; /* head parsed, res exists */
bool response_done;
bool had_error;
bool close_queued;
bool close_emitted; /* settled: listeners dropped, off the registry */
ScrHttpReq *res; /* +1 once the head parses */
ScrNetLs resp_ls, err_ls, timeout_ls, close_ls, upgrade_ls;
ScrNetLs resp_ls, err_ls, timeout_ls, close_ls, upgrade_ls, socket_ls, finish_ls, abort_ls;
/* the owning Agent (+1; the agent's entry holds this client +1 too —
* the cycle breaks at settle, or at the atexit agent sweep) */
struct ScrHttpAgent *agent;
@@ -3062,6 +3164,9 @@ void scr_http_client_release(ScrHttpClientReq *c) {
scr_net_ls_drop(&c->timeout_ls);
scr_net_ls_drop(&c->close_ls);
scr_net_ls_drop(&c->upgrade_ls);
scr_net_ls_drop(&c->socket_ls);
scr_net_ls_drop(&c->finish_ls);
scr_net_ls_drop(&c->abort_ls);
scr_str_release(c->host);
scr_str_release(c->path);
scr_str_release(c->method);
@@ -3128,6 +3233,8 @@ static void scr_http_client_settle(struct ScrHttpClientReq *c) {
scr_net_ls_drop(&c->err_ls);
scr_net_ls_drop(&c->timeout_ls);
scr_net_ls_drop(&c->close_ls);
scr_net_ls_drop(&c->socket_ls);
scr_net_ls_drop(&c->finish_ls);
scr_http_client_unregister(c);
}
@@ -3137,6 +3244,45 @@ static void scr_http_client_queue_close(ScrHttpClientReq *c) {
scr_http_emit_push(SCR_HTTP_EMIT_CLIENT_CLOSE, scr_http_client_retain(c));
}
static void scr_http_client_queue_finish(ScrHttpClientReq *c) {
if (!c->ended || !c->sock_ready || c->destroyed || c->close_emitted || c->finish_queued || c->writable_finished) return;
c->finish_queued = true;
scr_http_emit_push(SCR_HTTP_EMIT_CLIENT_FINISH, scr_http_client_retain(c));
}
static void scr_http_client_emit_socket(ScrHttpClientReq *c) {
if (c->socket_emitted) return;
if (c->destroyed) {
scr_net_ls_drop(&c->socket_ls);
return;
}
c->socket_emitted = true;
ScrNetL *snap;
size_t count = scr_net_ls_snapshot(&c->socket_ls, &snap);
scr_dyn_this_push(c, SCR_DYNH_HTTP_CLIENT);
for (size_t i = 0; i < count; i++) {
if (!scr_exc_pending()) ((ScrNetConnFn)snap[i].fn)(snap[i].cb, scr_net_sock_retain(c->sock));
scr_closure_release(snap[i].cb);
}
scr_dyn_this_pop();
free(snap);
scr_net_ls_drop(&c->socket_ls);
}
static void scr_http_client_emit_finish(ScrHttpClientReq *c) {
c->finish_queued = false;
if (c->destroyed || c->close_emitted || !c->ended || !c->sock_ready || c->writable_finished) return;
c->writable_finished = true;
scr_net_fire0_this(&c->finish_ls, c, SCR_DYNH_HTTP_CLIENT);
scr_net_ls_drop(&c->finish_ls);
}
static void scr_http_client_emit_abort(ScrHttpClientReq *c) {
c->abort_emitted = true;
scr_net_fire0_this(&c->abort_ls, c, SCR_DYNH_HTTP_CLIENT);
scr_net_ls_drop(&c->abort_ls);
}
/* 'error' on the request handle: fires NOW (the callers sit in the net
* sweep / dispatch, Node's own emit spots); no listener exits 1, the
* unhandled-'error' story. The close follows through the queue. */
@@ -3202,6 +3348,17 @@ ScrStr *scr_http_client_protocol(ScrHttpClientReq *c) {
}
bool scr_http_client_headers_sent(ScrHttpClientReq *c) { return c->head_sent; }
bool scr_http_client_writable_ended(ScrHttpClientReq *c) { return c->ended; }
bool scr_http_client_writable_finished(ScrHttpClientReq *c) { return c->writable_finished; }
ScrNetSocket *scr_http_client_socket(ScrHttpClientReq *c) { return scr_net_sock_retain(c->sock); }
bool scr_http_client_reused_socket(ScrHttpClientReq *c) { (void)c; return false; }
void scr_http_client_set_nodelay(ScrHttpClientReq *c, bool enable) {
if (!c->sock) return;
scr_net_sock_release(scr_net_sock_set_nodelay(c->sock, enable));
}
void scr_http_client_set_socket_keepalive(ScrHttpClientReq *c, bool enable, double delay_ms) {
if (!c->sock) return;
scr_net_sock_set_keepalive(c->sock, enable, delay_ms);
}
static bool scr_http_client_header_mutable(ScrHttpClientReq *c, const char *op) {
if (!c->head_sent) return true;
@@ -3404,6 +3561,7 @@ static void scr_http_client_end_raw(ScrHttpClientReq *c, const char *data, size_
if (c->chunked && c->sock) scr_http_send_chunk_end(c->sock, &c->trailers);
}
c->ended = true;
scr_http_client_queue_finish(c);
}
void scr_http_client_end(ScrHttpClientReq *c) { scr_http_client_end_raw(c, "", 0); }
@@ -3428,9 +3586,19 @@ void scr_http_client_add_trailers(ScrHttpClientReq *c, ScrArr *pairs /*borrowed*
/* req.setTimeout(ms) after construction (the island bridge's late arm —
* the constructor's timeout_ms is the static lane's route). */
void scr_http_client_set_timeout(ScrHttpClientReq *c, double ms) {
if (!scr_http_timeout_valid(ms)) return;
if (c->sock) scr_net_sock_set_timeout(c->sock, ms);
}
void scr_http_client_set_timeout_cb(ScrHttpClientReq *c, double ms, ScrClosure *cb /*moves*/) {
scr_http_client_set_timeout(c, ms);
if (scr_exc_pending()) {
scr_closure_release(cb);
return;
}
scr_http_client_on_timeout(c, cb, false);
}
/* destroy(): tears the connection down NOW. A destroy after the response
* completed is a quiet no-op past the socket close (Node: just 'close');
* before it, the teardown surfaces as 'socket hang up' — both flow
@@ -3441,6 +3609,24 @@ void scr_http_client_destroy(ScrHttpClientReq *c) {
if (c->sock) scr_net_sock_destroy(c->sock);
}
void scr_http_client_abort(ScrHttpClientReq *c) {
if (c->aborted) return;
c->aborted = true;
bool early_or_complete = !c->socket_emitted || c->response_done;
/* A request whose socket was already handed to JS emits abort before
* teardown; an early abort reports close first. The queued emits keep
* both paths off the caller's stack. */
if (!early_or_complete)
scr_http_emit_push(SCR_HTTP_EMIT_CLIENT_ABORT, scr_http_client_retain(c));
scr_http_client_destroy(c);
if (early_or_complete) {
scr_http_client_queue_close(c);
scr_http_emit_push(SCR_HTTP_EMIT_CLIENT_ABORT, scr_http_client_retain(c));
}
}
bool scr_http_client_aborted(ScrHttpClientReq *c) { return c->aborted; }
bool scr_http_client_destroyed(ScrHttpClientReq *c) {
return c->destroyed || c->close_emitted;
}
@@ -3454,6 +3640,30 @@ void scr_http_client_on_response(ScrHttpClientReq *c, ScrClosure *cb /*moves*/,
scr_net_ls_add(&c->resp_ls, cb, (void *)fn, once);
}
void scr_http_client_on_socket(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrNetConnFn fn, bool once) {
if (c->socket_emitted || c->close_emitted) {
scr_closure_release(cb);
return;
}
scr_net_ls_add(&c->socket_ls, cb, (void *)fn, once);
}
void scr_http_client_on_finish(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once) {
if (c->writable_finished || c->close_emitted) {
scr_closure_release(cb);
return;
}
scr_net_ls_add(&c->finish_ls, cb, NULL, once);
}
void scr_http_client_on_abort(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once) {
if (c->abort_emitted) {
scr_closure_release(cb);
return;
}
scr_net_ls_add(&c->abort_ls, cb, NULL, once);
}
void scr_http_client_on_error(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once) {
if (c->close_emitted) {
scr_closure_release(cb);
@@ -3713,7 +3923,7 @@ static void scr_http_client_premature(ScrHttpConn *conn) {
if (!c || c->response_done || c->close_emitted) return;
c->response_done = true; /* the exchange is over, one way or another */
if (!c->response_started) {
if (!c->had_error) {
if (!c->had_error && (!c->aborted || c->socket_emitted)) {
ScrStr *msg = scr_str_new("socket hang up", 14);
scr_http_client_error(c, msg);
scr_str_release(msg);
@@ -3721,12 +3931,14 @@ static void scr_http_client_premature(ScrHttpConn *conn) {
}
scr_http_client_queue_close(c);
} else {
/* mid-head-to-body death: req close, then 'aborted' on the res, then
* res close (the queue preserves push order) */
/* An incomplete client response fires 'aborted' before 'close'. A
* caller-initiated response.destroy() does not emit an 'error'; a
* premature peer close still does. */
scr_http_client_queue_close(c);
if (conn->req) {
ScrHttpReq *res = conn->req;
scr_http_queue_req_aborted(res);
scr_http_h2_req_aborted(res);
if (!res->destroyed) scr_http_queue_req_aborted(res);
scr_http_queue_req_close(res);
scr_http_req_finish(res, false); /* body never completes */
conn->req = NULL; /* break the res→sock→ctx→res cycle */
@@ -3772,7 +3984,7 @@ static void scr_http_client_closed(ScrHttpConn *conn) {
static bool scr_http_client_sock_err(ScrHttpConn *conn, ScrStr *msg) {
ScrHttpClientReq *c = conn->client;
if (!c) return true;
if (c->response_done || c->close_emitted) return true; /* teardown noise */
if (c->response_done || c->close_emitted || (c->aborted && !c->socket_emitted)) return true; /* teardown noise */
if (!c->response_started) {
/* connect/read failure before any response: the socket's message IS
* Node's ('connect ECONNREFUSED ip:port') — fire it on the request */
@@ -3803,6 +4015,14 @@ static void scr_http_client_sock_timeout(void *ctx) {
scr_net_fire0(&c->timeout_ls);
}
static void scr_http_client_sock_established(void *ctx) {
ScrHttpConn *conn = (ScrHttpConn *)ctx;
ScrHttpClientReq *c = conn->client;
if (!c) return;
c->sock_ready = true;
scr_http_client_queue_finish(c);
}
static void scr_http_client_conn_free(ScrHttpConn *conn) {
if (conn->req) {
scr_http_req_finish(conn->req, false);
@@ -3929,8 +4149,11 @@ static ScrHttpClientReq *scr_http_request_impl(ScrStr *host /*borrowed*/, double
/* the transport (scr_tls.c's https) wraps the dialed socket before any
* bytes go out — everything below buffers until its handshake ends */
if (wrap) wrap(c->sock, wrap_ctx);
scr_net_sock_set_native_established(c->sock, &scr_http_client_sock_established);
c->sock_ready = scr_net_sock_established(c->sock);
if (timeout_ms > 0) scr_net_sock_set_timeout(c->sock, timeout_ms);
scr_http_client_register(c);
scr_http_emit_push(SCR_HTTP_EMIT_CLIENT_SOCKET, scr_http_client_retain(c));
if (auto_end) scr_http_client_end(c);
return c;
}
+92 -1
View File
@@ -92,6 +92,7 @@
#include "scr_runtime.h"
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <fcntl.h>
#include <stdio.h>
@@ -472,7 +473,7 @@ struct ScrNetServer {
* address().address answers) */
ScrStr *pending_err; /* deferred listen failure */
size_t nconns;
ScrNetLs conn_ls, err_ls, close_ls, listening_cbs;
ScrNetLs conn_ls, err_ls, close_ls, listening_cbs, timeout_ls;
/* protocol layer (scr_http.c): a C-level connection consumer + its
* context, freed with the handle */
ScrNetNativeConnFn native_conn;
@@ -591,6 +592,7 @@ struct ScrNetSocket {
ScrNetNativeErrFn native_err; /* true = consumed (the protocol layer owns
* the error story); false falls through to
* this socket's own err_ls */
bool (*native_idle)(void *); /* HTTP parser: no active request or partial head */
void *native_ctx;
void (*native_ctx_free)(void *);
/* transport layer (scr_tls.c): reads/writes redirect through the ops
@@ -631,6 +633,7 @@ void scr_net_server_release(ScrNetServer *s) {
scr_net_ls_drop(&s->err_ls);
scr_net_ls_drop(&s->close_ls);
scr_net_ls_drop(&s->listening_cbs);
scr_net_ls_drop(&s->timeout_ls);
scr_closure_release(s->close_override);
for (size_t i = 0; i < 5; i++) scr_dyn_release(s->http_timeout_dyn[i]);
scr_str_release(s->bound_host);
@@ -1549,6 +1552,16 @@ void scr_net_server_timeout_set(ScrNetServer *s, double field, double value) {
s->http_timeouts[i] = value;
}
void scr_net_server_set_timeout_plain(ScrNetServer *s, double ms) {
scr_net_server_timeout_set(s, 0, ms);
}
void scr_net_server_set_timeout_cb(ScrNetServer *s, double ms, ScrClosure *cb /*moves*/,
ScrNetConnFn fn) {
scr_net_server_timeout_set(s, 0, ms);
scr_net_server_on_timeout(s, cb, fn, false);
}
void scr_net_server_enable_http_timeout_surface(ScrNetServer *s) {
s->http_timeout_surface = true;
}
@@ -1655,6 +1668,28 @@ void scr_net_server_close(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/) {
scr_net_server_close_direct(s, cb);
}
static void scr_net_server_close_http_connections(ScrNetServer *s, bool idle_only) {
ScrNetSocket *sock = scr_net_socks;
while (sock) {
ScrNetSocket *next = sock->next;
if (sock->server == s && sock->native_idle != NULL && sock->fd >= 0 &&
(!idle_only || sock->native_idle(sock->native_ctx))) {
scr_net_sock_retain(sock);
scr_net_sock_destroy(sock);
scr_net_sock_release(sock);
}
sock = next;
}
}
void scr_net_server_close_all_connections(ScrNetServer *s) {
scr_net_server_close_http_connections(s, false);
}
void scr_net_server_close_idle_connections(ScrNetServer *s) {
scr_net_server_close_http_connections(s, true);
}
/* wrapper.close = fn: the override MOVES in (a compiler-emitted zero-arg
* wrapper — see the struct comment); reassignment releases the old one,
* matching JS's last-write-wins. */
@@ -1715,6 +1750,15 @@ void scr_net_server_on_connection(ScrNetServer *s, ScrClosure *cb /*moves*/, Scr
scr_net_ls_add(&s->conn_ls, cb, (void *)fn, once);
}
void scr_net_server_on_timeout(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrNetConnFn fn,
bool once) {
if (s->close_emitted) {
scr_closure_release(cb);
return;
}
scr_net_ls_add(&s->timeout_ls, cb, (void *)fn, once);
}
/* Accept until EAGAIN; each connection fires the 'connection' listeners
* directly (the accepted socket passes +1 per listener via the adapter). */
static void scr_net_server_accept(ScrNetServer *srv) {
@@ -1733,6 +1777,8 @@ static void scr_net_server_accept(ScrNetServer *srv) {
sock->server = scr_net_server_retain(srv);
srv->nconns++;
scr_net_sock_register(sock);
if (srv->http_timeout_surface && srv->http_timeouts[0] > 0)
scr_net_sock_set_timeout(sock, srv->http_timeouts[0]);
if (srv->native_conn) {
/* the protocol layer (scr_http.c / scr_tls.c) claims the connection */
srv->native_conn(srv->native_ctx, sock);
@@ -2229,6 +2275,19 @@ ScrNetSocket *scr_net_sock_set_nodelay(ScrNetSocket *s, bool enable) {
return scr_net_sock_retain(s);
}
void scr_net_sock_set_keepalive(ScrNetSocket *s, bool enable, double delay_ms) {
if (s->fd < 0) return;
int value = enable ? 1 : 0;
setsockopt(s->fd, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof value);
if (!enable || !(delay_ms > 0) || !isfinite(delay_ms)) return;
int seconds = (int)fmin((double)INT_MAX, fmax(1.0, floor(delay_ms / 1000.0)));
#if defined(TCP_KEEPIDLE)
setsockopt(s->fd, IPPROTO_TCP, TCP_KEEPIDLE, &seconds, sizeof seconds);
#elif defined(TCP_KEEPALIVE)
setsockopt(s->fd, IPPROTO_TCP, TCP_KEEPALIVE, &seconds, sizeof seconds);
#endif
}
/* socket.destroySoon(): end the write half now, destroy once the FIN is
* actually out (buffered bytes flush first — Node's 'finish'-then-destroy). */
void scr_net_sock_destroy_soon(ScrNetSocket *s) {
@@ -2575,11 +2634,16 @@ void scr_net_sock_set_native_reader(ScrNetSocket *s, ScrNetNativeDataFn data,
s->native_data = data;
s->native_eof = eof;
s->native_closed = closed;
s->native_idle = NULL;
s->native_ctx = ctx;
s->native_ctx_free = ctx_free;
scr_net_sock_update_read(s);
}
void scr_net_sock_set_native_idle_checker(ScrNetSocket *s, bool (*fn)(void *)) {
s->native_idle = fn;
}
/* The upgrade handover: clear the native reader's FN POINTERS but keep
* the ctx (still freed at socket death as registered) — the protocol
* parser steps aside mid-call and 'data'/pipe consumers take the raw
@@ -2588,6 +2652,7 @@ void scr_net_sock_clear_native_reader(ScrNetSocket *s) {
s->native_data = NULL;
s->native_eof = NULL;
s->native_closed = NULL;
s->native_idle = NULL; /* upgraded and CONNECT sockets are outside HTTP close sweeps */
scr_net_sock_update_read(s);
}
@@ -2602,6 +2667,10 @@ bool scr_net_sock_writable(ScrNetSocket *s) {
return s->fd >= 0 && !s->wr_ending && !s->wr_done;
}
bool scr_net_sock_established(ScrNetSocket *s) {
return s->fd >= 0 && !s->connecting && (!s->tops || s->t_est);
}
/* The protocol layer's timeout/error hooks (scr_http.c's client and the
* server parser's error swallowing). */
void scr_net_sock_set_native_events(ScrNetSocket *s, ScrNetNativeEventFn timeout,
@@ -2771,6 +2840,7 @@ static void scr_net_server_settle(ScrNetServer *srv) {
scr_net_ls_drop(&srv->err_ls);
scr_net_ls_drop(&srv->close_ls);
scr_net_ls_drop(&srv->listening_cbs);
scr_net_ls_drop(&srv->timeout_ls);
if (srv->proto_settle && srv->http_ctx) srv->proto_settle(srv->http_ctx);
scr_net_server_unregister(srv);
}
@@ -2913,6 +2983,7 @@ static void scr_net_sweep(void) {
scr_net_ls_drop(&srv->err_ls);
scr_net_ls_drop(&srv->close_ls);
scr_net_ls_drop(&srv->listening_cbs);
scr_net_ls_drop(&srv->timeout_ls);
scr_net_server_unregister(srv);
}
if (scr_exc_pending()) {
@@ -2985,6 +3056,26 @@ static void scr_net_dispatch(void) {
if (scr_exc_pending()) return;
}
scr_net_fire0_this(&s->timeout_ls, s, SCR_DYNH_NET_SOCKET);
if (scr_exc_pending()) return;
if (s->server && s->server->http_timeout_surface &&
s->server->http_timeouts[0] > 0) {
ScrNetServer *srv = scr_net_server_retain(s->server);
if (srv->timeout_ls.n == 0) {
scr_net_sock_destroy(s); /* Node's unhandled server timeout */
} else {
ScrNetL *snap;
size_t count = scr_net_ls_snapshot(&srv->timeout_ls, &snap);
scr_dyn_this_push(srv, SCR_DYNH_NET_SERVER);
for (size_t j = 0; j < count; j++) {
if (!scr_exc_pending()) ((ScrNetConnFn)snap[j].fn)(snap[j].cb, scr_net_sock_retain(s));
scr_closure_release(snap[j].cb);
}
scr_dyn_this_pop();
free(snap);
}
scr_net_server_release(srv);
if (scr_exc_pending()) return;
}
}
/* kqueue delivers one direction per event (today's exact order);
* epoll may coalesce both into one — writes first (connect
+22
View File
@@ -5607,6 +5607,7 @@ bool scr_net_server_settled(ScrNetServer *s); /* 'close' already emitted */
* event the http layer does not model (the caller fences loudly). */
void scr_net_set_dynh_http_on(bool (*fn)(ScrNetServer *, const char *, const ScrDyn *, bool));
void scr_net_sock_set_native_reader(ScrNetSocket *s, ScrNetNativeDataFn data, ScrNetNativeEventFn eof, ScrNetNativeEventFn closed, void *ctx, void (*ctx_free)(void *));
void scr_net_sock_set_native_idle_checker(ScrNetSocket *s, bool (*fn)(void *));
/* The upgrade handover: clear the reader's fn pointers, keep the ctx. */
void scr_net_sock_clear_native_reader(ScrNetSocket *s);
/* The accepting server (BORROWED; NULL on client sockets) — the protocol
@@ -5618,6 +5619,7 @@ ScrNetServer *scr_net_sock_server(ScrNetSocket *s);
void scr_net_sock_set_encoding(ScrNetSocket *s, ScrStr *enc /*borrowed*/);
bool scr_net_sock_destroyed(ScrNetSocket *s); /* socket.destroyed */
bool scr_net_sock_writable(ScrNetSocket *s); /* socket.writable */
bool scr_net_sock_established(ScrNetSocket *s); /* connected, including TLS handshake */
void scr_net_sock_set_native_events(ScrNetSocket *s, ScrNetNativeEventFn timeout, ScrNetNativeErrFn err);
void scr_net_sock_write_native(ScrNetSocket *s, const char *buf, size_t n);
/* The protocol layer's deferred-emit hook: `pending` joins the loop's
@@ -5656,6 +5658,9 @@ bool scr_net_server_listening(ScrNetServer *s);
* a non-number in the ordinary JS property slot. */
double scr_net_server_timeout_get(ScrNetServer *s, double field);
void scr_net_server_timeout_set(ScrNetServer *s, double field, double value);
void scr_net_server_set_timeout_plain(ScrNetServer *s, double ms);
void scr_net_server_set_timeout_cb(ScrNetServer *s, double ms, ScrClosure *cb /*moves*/, ScrNetConnFn fn);
void scr_net_server_on_timeout(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrNetConnFn fn, bool once);
/* Marks the shared net-server handle as an HTTP/1 or HTTPS server. The
* dynamic handle uses this to keep HTTP-only fields off net/TLS/H2. */
void scr_net_server_enable_http_timeout_surface(ScrNetServer *s);
@@ -5667,6 +5672,8 @@ void scr_net_server_timeout_option_set(ScrNetServer *s, double field, const stru
ScrStr *scr_net_server_addr_ip(ScrNetServer *s); /* +1 — address().address */
ScrStr *scr_net_server_addr_family(ScrNetServer *s); /* +1 — address().family */
void scr_net_server_close(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/);
void scr_net_server_close_all_connections(ScrNetServer *s);
void scr_net_server_close_idle_connections(ScrNetServer *s);
/* The REAL close behind `wrapper.close.bind(wrapper)` — never consults
* the override (the proxy-through idiom cannot recurse). */
void scr_net_server_close_direct(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/);
@@ -5727,6 +5734,7 @@ void scr_net_sock_destroy(ScrNetSocket *s);
ScrNetSocket *scr_net_sock_pause(ScrNetSocket *s); /* +1: chaining */
ScrNetSocket *scr_net_sock_resume(ScrNetSocket *s); /* +1: chaining */
ScrNetSocket *scr_net_sock_set_nodelay(ScrNetSocket *s, bool enable); /* +1: chaining */
void scr_net_sock_set_keepalive(ScrNetSocket *s, bool enable, double delay_ms);
void scr_net_sock_destroy_soon(ScrNetSocket *s);
void scr_net_sock_on_finish(ScrNetSocket *s, ScrClosure *cb /*moves*/);
void scr_net_sock_on_write_flush(ScrNetSocket *s, ScrClosure *cb /*moves*/);
@@ -6061,6 +6069,8 @@ bool scr_http_req_complete(ScrHttpReq *r);
void scr_http_dyn_install(void);
void scr_http_validate_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*borrowed*/);
void scr_http_validate_header_value(ScrStr *name /*borrowed*/, const ScrDyn *value /*borrowed*/);
ScrDyn *scr_http_status_codes(void); /* +1, shared mutable Node v24 table */
ScrArr *scr_http_methods(void); /* +1, shared mutable Node v24 array */
ScrNetServer *scr_http_create_server(ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn); /* +1 */
/* The unguarded h2-only stream call: throws Node's exact catchable
@@ -6173,6 +6183,7 @@ void scr_http_req_resume(ScrHttpReq *r);
* socket's idle timer; the flags back req.destroyed/req.readable. */
void scr_http_req_pause(ScrHttpReq *r);
void scr_http_req_set_timeout(ScrHttpReq *r, double ms, ScrClosure *cb /*moves, nullable*/);
void scr_http_req_set_timeout_plain(ScrHttpReq *r, double ms);
bool scr_http_req_destroyed_flag(ScrHttpReq *r);
bool scr_http_req_readable(ScrHttpReq *r);
/* flushHeaders/cork/uncork/writableCorked, the res.req backref, the
@@ -6231,6 +6242,12 @@ ScrStr *scr_http_client_host(ScrHttpClientReq *c); /* +1 */
ScrStr *scr_http_client_protocol(ScrHttpClientReq *c); /* +1 */
bool scr_http_client_headers_sent(ScrHttpClientReq *c);
bool scr_http_client_writable_ended(ScrHttpClientReq *c);
bool scr_http_client_writable_finished(ScrHttpClientReq *c);
ScrNetSocket *scr_http_client_socket(ScrHttpClientReq *c); /* +1 */
bool scr_http_client_reused_socket(ScrHttpClientReq *c);
void scr_http_client_set_nodelay(ScrHttpClientReq *c, bool enable);
void scr_http_client_set_socket_keepalive(ScrHttpClientReq *c, bool enable, double delay_ms);
void scr_http_client_set_timeout_cb(ScrHttpClientReq *c, double ms, ScrClosure *cb /*moves*/);
void scr_http_client_add_trailers(ScrHttpClientReq *c, ScrArr *pairs /*borrowed*/);
void scr_http_client_cork(ScrHttpClientReq *c);
void scr_http_client_uncork(ScrHttpClientReq *c);
@@ -6240,8 +6257,13 @@ void scr_http_client_write_dynv(ScrHttpClientReq *c, const ScrDyn *d /*borrowed*
void scr_http_client_end_dynv(ScrHttpClientReq *c, const ScrDyn *d /*borrowed*/);
void scr_http_client_set_timeout(ScrHttpClientReq *c, double ms);
void scr_http_client_destroy(ScrHttpClientReq *c);
void scr_http_client_abort(ScrHttpClientReq *c);
bool scr_http_client_aborted(ScrHttpClientReq *c);
bool scr_http_client_destroyed(ScrHttpClientReq *c);
void scr_http_client_on_response(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrHttpRespFn fn, bool once);
void scr_http_client_on_socket(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrNetConnFn fn, bool once);
void scr_http_client_on_finish(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once);
void scr_http_client_on_abort(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once);
void scr_http_client_on_error(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, ScrChildErrFn fn, bool once);
void scr_http_client_on_timeout(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once);
void scr_http_client_on_close(ScrHttpClientReq *c, ScrClosure *cb /*moves*/, bool once);
+35 -20
View File
@@ -383,6 +383,7 @@ const execIn = async (
workdir = "/workspace",
idleTimeoutMs = 90_000,
) => {
const deadline = Date.now() + wallTimeoutMs;
const envArgs = Object.entries(env).flatMap(([key, value]) => ["--env", `${key}=${value}`]);
const exitMarker = `__SCRIPTC_REMOTE_EXIT_${randomBytes(12).toString("hex")}__`;
const prepared = sandboxCommand(command, args, exitMarker);
@@ -426,26 +427,40 @@ const execIn = async (
`scriptc_status=125; test ! -f ${shellQuote(statusPath)} || ` +
`scriptc_status=$(cat ${shellQuote(statusPath)}); ` +
`printf '\\n${probeMarker}%s\\n' "$scriptc_status"`;
await vercel(
[
"sandbox",
"exec",
"--timeout",
"1m",
"--workdir",
workdir,
worker.name,
"sh",
"-c",
probeScript,
],
{
exitMarker: probeMarker,
idleTimeoutMs: 30_000,
label: `${label} status`,
timeoutMs: 60_000,
},
);
let probes = 0;
for (;;) {
try {
await vercel(
[
"sandbox",
"exec",
"--timeout",
"1m",
"--workdir",
workdir,
worker.name,
"sh",
"-c",
probeScript,
],
{
exitMarker: probeMarker,
idleTimeoutMs: 30_000,
label: `${label} status`,
timeoutMs: 60_000,
},
);
return;
} catch (probeError) {
const status = /remote command exited (\d+)$/.exec(String(probeError?.message ?? probeError));
if (status && Number(status[1]) !== 125) throw probeError;
if (Date.now() >= deadline) {
throw new Error(`${label} did not report a remote exit status before its timeout`, { cause: error });
}
if (probes++ % 6 === 0) console.warn(`[${label}] waiting for the remote exit marker...`);
await new Promise((resolve) => setTimeout(resolve, Math.min(5_000, deadline - Date.now())));
}
}
}
};
+22
View File
@@ -0,0 +1,22 @@
import { Agent, createServer, request } from "node:http";
const agent = new Agent({ maxSockets: 1 });
console.log("agent defaults", agent.getName(), agent.maxSockets, agent.maxFreeSockets, agent.keepAlive, agent.protocol);
console.log("agent name", agent.getName({ host: "example.test", port: 8080, family: 4 }));
const server = createServer((req, res) => { res.end(req.url); });
server.listen(0, () => {
const port = server.address().port;
const name = agent.getName({ host: "127.0.0.1", port });
const outbound = request({ hostname: "127.0.0.1", port, path: "/agent", agent }, (response) => {
console.log("agent tables", agent.sockets[name].length, name in agent.requests, Object.keys(agent.freeSockets).length);
let body = "";
response.on("data", (chunk: Buffer) => { body += chunk.toString("utf8"); });
response.on("end", () => {
console.log("response", response.statusCode, body, agent.totalSocketCount);
agent.destroy();
server.close();
});
});
outbound.end();
});
@@ -0,0 +1,14 @@
import * as http from "node:http";
import { METHODS, STATUS_CODES, maxHeaderSize } from "node:http";
console.log("methods", METHODS.join("|"));
console.log("namespace", http.METHODS[0], http.METHODS[http.METHODS.length - 1]);
console.log("header limit", maxHeaderSize, http.maxHeaderSize);
console.log("status", STATUS_CODES[200], STATUS_CODES[418], http.STATUS_CODES[511], STATUS_CODES[299] === undefined);
console.log("shared", METHODS === http.METHODS, STATUS_CODES === http.STATUS_CODES);
const originalMethod = METHODS[0]!;
METHODS[0] = "CUSTOM";
console.log("method mutation", http.METHODS[0]);
METHODS[0] = originalMethod;
STATUS_CODES[299] = "Custom";
console.log("status mutation", http.STATUS_CODES[299]);
@@ -0,0 +1,21 @@
import { createServer, request } from "node:http";
const server = createServer((req, res) => {
res.statusCode = 418;
res.end(req.method + " " + req.url);
});
server.listen(0, () => {
const outbound = request({ port: server.address().port, path: "/controls", method: "POST" }, (response) => {
let body = "";
response.on("data", (chunk: Buffer) => { body += chunk.toString("utf8"); });
response.on("end", () => {
console.log("reply", response.statusCode, response.statusMessage, body, outbound.finished, outbound.reusedSocket);
server.close();
});
});
console.log("initial", outbound.finished, outbound.reusedSocket);
outbound.setNoDelay(false);
outbound.setNoDelay();
outbound.end();
console.log("ended", outbound.finished, outbound.reusedSocket);
});
@@ -0,0 +1,29 @@
import { createServer, request } from "node:http";
const server = createServer((incoming, outgoing) => {
outgoing.on("finish", () => { console.log("response finish", outgoing.finished); });
outgoing.on("close", () => { console.log("response close", outgoing.finished); });
const headers = incoming.headers;
console.log("request", incoming.aborted, incoming.connection === incoming.socket, headers["x-test"], Object.keys(headers).includes("host"));
incoming.setTimeout(500);
let body = "";
incoming.on("data", (chunk: Buffer) => { body += chunk.toString("utf8"); });
incoming.on("end", () => {
console.log("request body", body, incoming.complete, incoming.aborted);
outgoing.end("ok");
console.log("response finished", outgoing.finished);
});
incoming.on("close", () => { console.log("request close", incoming.complete); });
});
server.listen(0, () => {
const outbound = request({ port: server.address().port, method: "POST", headers: { "X-Test": "one" } }, (response) => {
const headers = response.headers;
console.log("reply", response.statusMessage, response.connection === response.socket, response.aborted, headers["content-length"]);
response.setTimeout(500);
response.on("close", () => { console.log("reply close", response.complete); });
response.resume();
response.on("end", () => { server.close(); });
});
outbound.end("ping");
});
@@ -0,0 +1,18 @@
import { createServer, get } from "node:http";
const server = createServer((_request, response) => {
response.flushHeaders();
setTimeout(() => { response.end("late"); }, 120);
});
server.listen(0, () => {
get({ port: server.address().port }, (response) => {
response.setTimeout(15, () => { console.log("timed out", response.complete); });
let body = "";
response.on("data", (chunk: Buffer) => { body += chunk.toString("utf8"); });
response.on("end", () => {
console.log("body", body, response.complete);
server.close();
});
});
});
@@ -0,0 +1,21 @@
import * as http from "node:http";
const server = http.createServer((_incoming, outgoing) => {
setTimeout(() => outgoing.end("late"), 120);
});
server.listen(0, "127.0.0.1", () => {
const client = http.request({ host: "127.0.0.1", port: server.address().port }, (response) => {
console.log("response", response.statusCode);
response.on("data", (chunk) => console.log("body", chunk.toString()));
response.on("end", () => server.close());
});
client.setSocketKeepAlive(false);
client.setSocketKeepAlive(true, 1000);
client.setSocketKeepAlive();
console.log("destroyed initially", client.destroyed);
client.on("close", () => console.log("destroyed on close", client.destroyed));
client.setTimeout(1000);
console.log("same request", client.setTimeout(20, () => console.log("timeout")) === client);
client.end();
});
@@ -0,0 +1,14 @@
import * as http from "node:http";
const server = http.createServer(() => {});
server.on("timeout", (socket) => console.log("listener", socket.destroyed));
console.log("same server", server.setTimeout(20, (socket) => {
console.log("callback", socket.destroyed);
socket.destroy();
server.close();
}) === server);
server.listen(0, "127.0.0.1", () => {
const client = http.request({ host: "127.0.0.1", port: server.address().port });
client.on("error", () => console.log("client error"));
client.end();
});
@@ -0,0 +1,13 @@
import * as http from "node:http";
const server = http.createServer(() => {});
server.setTimeout(20);
console.log("timeout", server.timeout);
server.listen(0, "127.0.0.1", () => {
const client = http.request({ host: "127.0.0.1", port: server.address().port });
client.on("error", () => {
console.log("closed on timeout");
server.close();
});
client.end();
});
+19
View File
@@ -0,0 +1,19 @@
import * as http from "node:http";
const server = http.createServer();
server.on("connection", (socket) => console.log("connection", socket.destroyed));
server.on("request", (incoming, outgoing) => {
console.log("request", incoming.method);
outgoing.end("ok");
});
server.on("close", () => console.log("server close"));
server.listen(0, "127.0.0.1", () => {
const client = http.request({ host: "127.0.0.1", port: server.address().port });
client.on("response", (incoming) => {
console.log("response", incoming.statusCode);
incoming.on("data", (chunk: Buffer) => console.log("data", chunk.toString()));
incoming.on("end", () => server.close());
});
client.on("close", () => console.log("client close"));
client.end();
});
@@ -0,0 +1,14 @@
import * as http from "node:http";
const server = http.createServer(() => {
console.log("request started");
server.closeAllConnections();
server.close();
});
server.listen(0, "127.0.0.1", () => {
const client = http.request({ host: "127.0.0.1", port: server.address().port });
client.on("error", () => console.log("client error"));
client.on("close", () => console.log("client close"));
client.end();
});
@@ -0,0 +1,20 @@
import * as http from "node:http";
import * as net from "node:net";
const server = http.createServer((_incoming, outgoing) => outgoing.end("ok"));
server.listen(0, "127.0.0.1", () => {
const socket = net.connect(server.address().port, "127.0.0.1");
let wire = "";
socket.on("connect", () => socket.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n"));
socket.on("data", (chunk: Buffer) => {
wire += chunk.toString();
if (wire.includes("\r\n\r\nok")) {
console.log("response received");
server.closeIdleConnections();
}
});
socket.on("close", () => {
console.log("idle socket closed");
server.close();
});
});
@@ -0,0 +1,17 @@
import * as http from "node:http";
import * as net from "node:net";
const server = http.createServer();
server.on("upgrade", (_request, socket) => {
server.closeAllConnections();
console.log("upgraded socket open", !socket.destroyed);
socket.end("ALIVE");
server.close();
});
server.listen(0, "127.0.0.1", () => {
const client = net.connect(server.address().port, "127.0.0.1");
let body = "";
client.on("connect", () => client.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade\r\nUpgrade: scr\r\n\r\n"));
client.on("data", (chunk: Buffer) => { body += chunk.toString(); });
client.on("end", () => console.log("received", body));
});
@@ -0,0 +1,23 @@
import * as http from "node:http";
const server = http.createServer((_incoming, outgoing) => {
outgoing.writeHead(200);
outgoing.write("first");
});
server.listen(0, "127.0.0.1", () => {
const client = http.request({ host: "127.0.0.1", port: server.address().port }, (response) => {
response.on("data", (chunk: Buffer) => {
console.log("body", chunk.toString());
response.destroy();
});
response.on("error", () => console.log("error"));
response.on("aborted", () => console.log("aborted", response.aborted));
response.on("close", () => {
console.log("closed", response.complete);
server.close();
});
});
client.on("error", () => console.log("client error"));
client.end();
});
@@ -0,0 +1,26 @@
import * as http from "node:http";
const server = http.createServer((incoming, outgoing) => {
incoming.resume();
incoming.on("end", () => outgoing.end("ok"));
});
server.listen(0, "127.0.0.1", () => {
const outbound = http.request({ host: "127.0.0.1", port: server.address().port, method: "POST" }, (response) => {
response.resume();
response.on("end", () => server.close());
});
console.log("initial", outbound.socket === outbound.connection, outbound.writableFinished, outbound.destroyed);
outbound.on("socket", (socket) => {
console.log("socket", socket === outbound.socket, socket.destroyed, outbound.writableFinished);
});
outbound.once("socket", () => console.log("socket once"));
outbound.once("finish", () => {
console.log("finish", outbound.writableEnded, outbound.writableFinished, outbound.destroyed);
});
outbound.on("close", () => {
console.log("close", outbound.writableFinished, outbound.destroyed);
});
outbound.end("hi");
console.log("ended", outbound.writableEnded, outbound.writableFinished);
});
@@ -0,0 +1,28 @@
import * as http from "node:http";
function result(action: () => void): string {
try {
action();
return "ok";
} catch (error) {
if (error instanceof Error) return `${error.name}:${(error as NodeJS.ErrnoException).code}:${error.message}`;
return "unexpected error";
}
}
const server = http.createServer((incoming, outgoing) => {
console.log("message negative", result(() => incoming.setTimeout(-1)));
console.log("message infinite", result(() => incoming.setTimeout(Infinity, () => {})));
incoming.resume();
outgoing.end("ok");
});
server.listen(0, "127.0.0.1", () => {
const client = http.request({ host: "127.0.0.1", port: server.address().port, method: "POST" }, (response) => {
response.resume();
response.on("end", () => server.close());
});
console.log("client negative", result(() => client.setTimeout(-1)));
console.log("client infinite", result(() => client.setTimeout(Infinity, () => {})));
client.end("ping");
});
@@ -0,0 +1,32 @@
import * as http from "node:http";
const server = http.createServer((incoming, outgoing) => {
incoming.resume();
outgoing.end("ok");
});
server.listen(0, "127.0.0.1", () => {
const port = server.address().port;
const early = http.request({ host: "127.0.0.1", port });
console.log("early before", early.aborted, early.destroyed);
early.on("socket", () => console.log("early socket"));
early.on("error", (error) => console.log("early error", error.message));
early.on("close", () => console.log("early close", early.aborted, early.destroyed));
early.on("abort", () => {
console.log("early abort", early.aborted, early.destroyed);
const late = http.get({ host: "127.0.0.1", port });
late.on("socket", () => {
console.log("late socket", late.aborted, late.destroyed);
late.abort();
console.log("late after", late.aborted, late.destroyed);
});
late.on("abort", () => console.log("late abort", late.aborted, late.destroyed));
late.on("error", (error) => console.log("late error", error.message));
late.on("close", () => {
console.log("late close", late.aborted, late.destroyed);
server.close();
});
});
early.abort();
console.log("early after", early.aborted, early.destroyed);
});