// The island's node:http CLIENT, differentially against Node's real http: // request/get drive real sockets (scr_net + scr_http's parser under // scriptc; core http under Node) against the harness's local server — // status/headers/body round trips, POST bodies with explicit framing, // redirects NOT followed (node:http semantics, not fetch's), the // multi-value header fold, Node's error shapes on a refused port, the // res-end → req-close → res-close ordering, quiet destroy, and the // socket timeout event. Byte-exact output both sides. import http from "node:http"; const out = (line) => process.stdout.write(line + "\n"); export function run(base, refusedBase) { const steps = [ // GET a string URL: status/statusMessage/headers, utf8 body (next) => http.get(`${base}/text`, (res) => { res.setEncoding("utf8"); let body = ""; res.on("data", (c) => { body += c; }); res.on("end", () => { out(`get ${res.statusCode} ${JSON.stringify(res.statusMessage)} ${res.headers["x-kind"]} ${res.headers["content-type"]}`); out(`get-body ${JSON.stringify(body)}`); next(); }); }), // POST via the options form: write + end, content-type header (next) => { const u = new URL(base); const req = http.request( { hostname: u.hostname, port: u.port, path: "/post-echo", method: "POST", headers: { "content-type": "application/json" } }, (res) => { res.setEncoding("utf8"); let body = ""; res.on("data", (c) => { body += c; }); res.on("end", () => { out(`post ${res.statusCode} ${body}`); next(); }); }, ); req.write('{"n":'); req.end("7}"); }, // 404 resolves with the server's status line and body (next) => http.get(`${base}/missing`, (res) => { res.setEncoding("utf8"); let body = ""; res.on("data", (c) => { body += c; }); res.on("end", () => { out(`missing ${res.statusCode} ${JSON.stringify(res.statusMessage)} ${JSON.stringify(body)}`); next(); }); }), // redirects are NOT followed (node:http, not fetch): 302 + location (next) => http.get(`${base}/redirect`, (res) => { res.resume(); res.on("end", () => { out(`redirect ${res.statusCode} ${res.headers.location}`); next(); }); }), // request headers via options + setHeader; multi-value response fold (next) => { const u = new URL(base); const req = http.request( { hostname: u.hostname, port: u.port, path: "/header-echo", headers: { "x-echo-one": "v1" } }, (res) => { res.setEncoding("utf8"); let body = ""; res.on("data", (c) => { body += c; }); res.on("end", () => { out(`headers ${body} multi=${res.headers["x-multi"]}`); next(); }); }, ); req.setHeader("x-echo-two", "v2"); req.end(); }, // event ORDER: res 'end', then req 'close', then res 'close' (next) => { const order = []; const req = http.get(`${base}/text`, (res) => { res.resume(); res.on("end", () => order.push("res-end")); res.on("close", () => { order.push("res-close"); out(`order ${order.join(",")}`); next(); }); }); req.on("close", () => order.push("req-close")); }, // a refused port: Node's error shape on the request (next) => { const u = new URL(refusedBase); const req = http.get({ hostname: u.hostname, port: u.port, path: "/x" }, () => { out("refused unexpectedly responded"); next(); }); req.on("error", (e) => { out(`refused ${e.message} code=${e.code} syscall=${e.syscall}`); next(); }); }, // destroy() mid-flight: 'socket hang up' speaks, then 'close' — Node's // teardown shape for a request whose response never arrived (next) => { const req = http.get(`${base}/slow`, () => out("destroyed unexpectedly responded")); req.on("error", (e) => out(`destroyed error ${e.message} code=${e.code}`)); req.on("close", () => { out("destroyed close"); next(); }); req.destroy(); }, // the socket timeout event fires; the listener destroys, like Node docs (next) => { const req = http.get(`${base}/slow`, () => out("timeout unexpectedly responded")); req.setTimeout(100, () => { out("timeout fired"); req.destroy(); }); req.on("error", (e) => out(`timeout error ${e.message} code=${e.code}`)); req.on("close", () => next()); }, () => out("done"), ]; let i = 0; const next = () => { const s = steps[i++]; if (s) s(next); }; next(); }