mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-19 05:29:46 +08:00
110 lines
3.7 KiB
JavaScript
110 lines
3.7 KiB
JavaScript
// The http2-hello driver: three legs.
|
|
//
|
|
// 1. Plain https/1.1 requests (node:https offers ALPN http/1.1): both
|
|
// lanes serve HTTP/1.1 — status, headers, and body are byte-identical.
|
|
// 2. The h2-OFFERING leg: a TLS connection offering ["h2", "http/1.1"].
|
|
// Node's allowHTTP1 server picks h2; the compiled server advertises
|
|
// http/1.1 only, so the client negotiates down (SEMANTICS.md
|
|
// divergence 57). The comparison here is STRUCTURAL by necessity: the
|
|
// negotiated protocol name is NOT printed (it differs by design) —
|
|
// the driver speaks whichever protocol was negotiated and prints what
|
|
// the application sees (status + body), which must be identical.
|
|
// 3. /quit over https/1.1 to shut the server down.
|
|
//
|
|
// Connection: close on every 1.1 request so connections drain.
|
|
import { readFileSync } from "node:fs";
|
|
import { request as httpsRequest } from "node:https";
|
|
import { connect as tlsConnect } from "node:tls";
|
|
import { connect as h2Connect } from "node:http2";
|
|
|
|
const port = Number(process.argv[2]);
|
|
const ca = readFileSync(new URL("../../certs/ca.pem", import.meta.url));
|
|
|
|
function get(path, extraHeaders = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const req = httpsRequest(
|
|
{
|
|
host: "127.0.0.1",
|
|
servername: "localhost",
|
|
port,
|
|
path,
|
|
method: "GET",
|
|
ca,
|
|
headers: { connection: "close", ...extraHeaders },
|
|
},
|
|
(res) => {
|
|
const chunks = [];
|
|
res.on("data", (c) => chunks.push(c));
|
|
res.on("end", () => {
|
|
const body = Buffer.concat(chunks).toString("utf8");
|
|
console.log(
|
|
`${path} -> ${res.statusCode} ct=${res.headers["content-type"]} xp=${res.headers["x-portless"] ?? "-"} body=${body}`,
|
|
);
|
|
resolve();
|
|
});
|
|
},
|
|
);
|
|
req.on("error", reject);
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
function alpnGet(path) {
|
|
return new Promise((resolve, reject) => {
|
|
const sock = tlsConnect(
|
|
{ host: "127.0.0.1", servername: "localhost", port, ca, ALPNProtocols: ["h2", "http/1.1"] },
|
|
() => {
|
|
if (sock.alpnProtocol === "h2") {
|
|
// Node lane: h2 accepted — speak HTTP/2 over this socket.
|
|
const session = h2Connect(`https://localhost:${port}`, {
|
|
createConnection: () => sock,
|
|
});
|
|
session.on("error", reject);
|
|
const req = session.request({ ":path": path });
|
|
let status = 0;
|
|
const chunks = [];
|
|
req.on("response", (headers) => {
|
|
status = headers[":status"];
|
|
});
|
|
req.on("data", (c) => chunks.push(c));
|
|
req.on("end", () => {
|
|
console.log(`alpn ${path} -> ${status} body=${Buffer.concat(chunks).toString("utf8")}`);
|
|
session.close(resolve);
|
|
});
|
|
req.end();
|
|
} else {
|
|
// Compiled lane: negotiated down to http/1.1 — speak it over
|
|
// the same socket (node:https parses the response).
|
|
const req = httpsRequest(
|
|
{
|
|
createConnection: () => sock,
|
|
path,
|
|
method: "GET",
|
|
headers: { host: "localhost", connection: "close" },
|
|
},
|
|
(res) => {
|
|
const chunks = [];
|
|
res.on("data", (c) => chunks.push(c));
|
|
res.on("end", () => {
|
|
console.log(
|
|
`alpn ${path} -> ${res.statusCode} body=${Buffer.concat(chunks).toString("utf8")}`,
|
|
);
|
|
resolve();
|
|
});
|
|
},
|
|
);
|
|
req.on("error", reject);
|
|
req.end();
|
|
}
|
|
},
|
|
);
|
|
sock.on("error", reject);
|
|
});
|
|
}
|
|
|
|
await get("/");
|
|
await get("/echo-host", { host: "myapp.localhost" });
|
|
await alpnGet("/");
|
|
await get("/quit");
|
|
console.log("driver done");
|