mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-19 05:29:46 +08:00
136 lines
4.8 KiB
JavaScript
136 lines
4.8 KiB
JavaScript
// 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();
|
|
}
|