mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-08 08:44:37 +08:00
http.request, https.request and get now accept options the compiler
cannot see as an object literal: a RequestOptions variable, a literal
that spreads one, or a literal that sets socketPath. The runtime reads
the record in the ClientRequest constructor's order with Node's errors;
documented options it does not implement throw the runtime fence when
they carry a value. https adds rejectUnauthorized, ca and servername.
socketPath dials a Unix domain socket in the net unit; the agent keys
its sockets by the path as Node's getName does, and the Host header
defaults to localhost. server.listen(path[, cb]) binds a Unix socket and
removes the socket file when the server closes; a numeric string is
still a port. Errors name the path ("connect ENOENT /path", "listen
EADDRINUSE: address already in use /path"). Windows reports ENOTSUP.
A TypeScript object literal with no contextual type that spreads a
checked-dynamic value maps to the checked dynamic, so
`const options = { ...location, path, timeout }` compiles.
A socket's 'lookup' event reaches its listeners with (err, address,
family, host) for each address a host name resolves to, or once with
the lookup error, before 'connect'. Only programs that listen for
'lookup' install the lookup records; elsewhere the hook sites are a NULL
check.
server.close((err) => ...) runs the callback with undefined at 'close',
or with ERR_SERVER_NOT_RUNNING when the server was not listening; a
server that never listened now emits 'close'. IncomingMessage 'data'
listeners may take string chunks after setEncoding, and res.end()
accepts a string-or-undefined chunk.
fetch's BodyInit and HeadersInit unions, as RequestInit-derived option
types spell them under @types/node, map to the checked dynamic, and a
URLSearchParams body is sent form-encoded, as by Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
// HTTP over a Unix domain socket: http.createServer().listen(path) and
|
|
// http.request / http.get with a socketPath option, from an object literal,
|
|
// from a RequestOptions variable, and from a literal that spreads one. The
|
|
// Host header defaults to localhost. A missing socket fails the request
|
|
// with ENOENT, a second listen on a bound path fails with EADDRINUSE, and
|
|
// closing the server removes the socket file.
|
|
import * as http from "node:http";
|
|
import { existsSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const sock = join(tmpdir(), `scriptc-http-${process.pid}.sock`);
|
|
const shown = (text: string): string => text.split(sock).join("<sock>");
|
|
|
|
const server = http.createServer((req, res) => {
|
|
let body = "";
|
|
req.on("data", (chunk: Buffer) => {
|
|
body += chunk.toString();
|
|
});
|
|
req.on("end", () => {
|
|
res.setHeader("content-type", "text/plain");
|
|
res.end(`${req.method} ${req.url} host=${req.headers.host} x=${req.headers["x-n"]} body=${body}`);
|
|
});
|
|
});
|
|
|
|
function collect(res: http.IncomingMessage, label: string, done: () => void): void {
|
|
let text = "";
|
|
res.on("data", (chunk: Buffer) => {
|
|
text += chunk.toString();
|
|
});
|
|
res.on("end", () => {
|
|
console.log(label, res.statusCode, res.headers["content-type"], text);
|
|
done();
|
|
});
|
|
}
|
|
|
|
function literal(): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
const req = http.request(
|
|
{ socketPath: sock, path: "/literal", method: "POST", headers: { "x-n": "1" } },
|
|
(res) => collect(res, "literal", resolve),
|
|
);
|
|
req.end("one");
|
|
});
|
|
}
|
|
|
|
const base: http.RequestOptions = { socketPath: sock };
|
|
|
|
function fromVariable(): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
const headers: Record<string, string | number> = { "x-n": 2, "content-type": "text/plain" };
|
|
const options: http.RequestOptions = { ...base, path: "/variable", method: "PUT", headers, timeout: 5000 };
|
|
const req = http.request(options, (res) => collect(res, "variable", resolve));
|
|
req.write("two");
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
function spread(): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
const req = http.request({ ...base, path: "/spread", method: "POST", headers: { "x-n": "3" } }, (res) =>
|
|
collect(res, "spread", resolve),
|
|
);
|
|
req.end("three");
|
|
});
|
|
}
|
|
|
|
function get(): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
const options: http.RequestOptions = { socketPath: sock, path: "/get?q=4" };
|
|
http.get(options, (res) => collect(res, "get", resolve));
|
|
});
|
|
}
|
|
|
|
function missing(): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
const options: http.RequestOptions = { socketPath: `${sock}.missing`, path: "/" };
|
|
const req = http.request(options, () => console.log("unexpected response"));
|
|
req.on("error", (err: NodeJS.ErrnoException) => {
|
|
console.log("missing", err.code, shown(err.message));
|
|
resolve();
|
|
});
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
function busy(): Promise<void> {
|
|
return new Promise<void>((resolve) => {
|
|
const other = http.createServer();
|
|
other.on("error", (err: NodeJS.ErrnoException) => {
|
|
console.log("busy", err.code, shown(err.message));
|
|
resolve();
|
|
});
|
|
other.listen(sock);
|
|
});
|
|
}
|
|
|
|
server.listen(sock, async () => {
|
|
console.log("listening", existsSync(sock));
|
|
await literal();
|
|
await fromVariable();
|
|
await spread();
|
|
await get();
|
|
await missing();
|
|
await busy();
|
|
server.close(() => {
|
|
console.log("closed", existsSync(sock));
|
|
});
|
|
});
|