mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Deflake http2 tests (#10682)
This commit is contained in:
36
test/js/node/http2/http2-helpers.cjs
Normal file
36
test/js/node/http2/http2-helpers.cjs
Normal file
@@ -0,0 +1,36 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports.TLS_CERT = require("./tls-cert.cjs").TLS_CERT;
|
||||
module.exports.TLS_OPTIONS = require("./tls-cert.cjs").TLS_OPTIONS;
|
||||
const nodeExecutable = typeof Bun !== "undefined" ? Bun.which("node") : "node";
|
||||
|
||||
exports.nodeEchoServer = async function nodeEchoServer() {
|
||||
if (!nodeExecutable) throw new Error("node executable not found");
|
||||
const subprocess = require("child_process").spawn(
|
||||
nodeExecutable,
|
||||
[path.join(__dirname, "node-echo-server.fixture.js")],
|
||||
{
|
||||
stdout: "pipe",
|
||||
stderr: "inherit",
|
||||
stdin: "inherit",
|
||||
},
|
||||
);
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
subprocess.unref();
|
||||
subprocess.stdout.setEncoding("utf8");
|
||||
var data = "";
|
||||
function readData(chunk) {
|
||||
data += chunk;
|
||||
|
||||
try {
|
||||
const address = JSON.parse(data);
|
||||
const url = `https://${address.family === "IPv6" ? `[${address.address}]` : address.address}:${address.port}`;
|
||||
subprocess.stdout.off("data", readData);
|
||||
resolve({ address, url, subprocess });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
subprocess.stdout.on("data", readData);
|
||||
return await promise;
|
||||
};
|
||||
@@ -1,36 +1,79 @@
|
||||
const http2 = require("http2");
|
||||
const TLS_CERT = {
|
||||
key: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+7odzr3yIYewR\nNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MBKw3r\nl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwPdwVU\neR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn0oH9\nHbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOFzDpc\np1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FUIDHt\nnUsoHX3RAgMBAAECggEAAckMqkn+ER3c7YMsKRLc5bUE9ELe+ftUwfA6G+oXVorn\nE+uWCXGdNqI+TOZkQpurQBWn9IzTwv19QY+H740cxo0ozZVSPE4v4czIilv9XlVw\n3YCNa2uMxeqp76WMbz1xEhaFEgn6ASTVf3hxYJYKM0ljhPX8Vb8wWwlLONxr4w4X\nOnQAB5QE7i7LVRsQIpWKnGsALePeQjzhzUZDhz0UnTyGU6GfC+V+hN3RkC34A8oK\njR3/Wsjahev0Rpb+9Pbu3SgTrZTtQ+srlRrEsDG0wVqxkIk9ueSMOHlEtQ7zYZsk\nlX59Bb8LHNGQD5o+H1EDaC6OCsgzUAAJtDRZsPiZEQKBgQDs+YtVsc9RDMoC0x2y\nlVnP6IUDXt+2UXndZfJI3YS+wsfxiEkgK7G3AhjgB+C+DKEJzptVxP+212hHnXgr\n1gfW/x4g7OWBu4IxFmZ2J/Ojor+prhHJdCvD0VqnMzauzqLTe92aexiexXQGm+WW\nwRl3YZLmkft3rzs3ZPhc1G2X9QKBgQDOQq3rrxcvxSYaDZAb+6B/H7ZE4natMCiz\nLx/cWT8n+/CrJI2v3kDfdPl9yyXIOGrsqFgR3uhiUJnz+oeZFFHfYpslb8KvimHx\nKI+qcVDcprmYyXj2Lrf3fvj4pKorc+8TgOBDUpXIFhFDyM+0DmHLfq+7UqvjU9Hs\nkjER7baQ7QKBgQDTh508jU/FxWi9RL4Jnw9gaunwrEt9bxUc79dp+3J25V+c1k6Q\nDPDBr3mM4PtYKeXF30sBMKwiBf3rj0CpwI+W9ntqYIwtVbdNIfWsGtV8h9YWHG98\nJ9q5HLOS9EAnogPuS27walj7wL1k+NvjydJ1of+DGWQi3aQ6OkMIegap0QKBgBlR\nzCHLa5A8plG6an9U4z3Xubs5BZJ6//QHC+Uzu3IAFmob4Zy+Lr5/kITlpCyw6EdG\n3xDKiUJQXKW7kluzR92hMCRnVMHRvfYpoYEtydxcRxo/WS73SzQBjTSQmicdYzLE\ntkLtZ1+ZfeMRSpXy0gR198KKAnm0d2eQBqAJy0h9AoGBAM80zkd+LehBKq87Zoh7\ndtREVWslRD1C5HvFcAxYxBybcKzVpL89jIRGKB8SoZkF7edzhqvVzAMP0FFsEgCh\naClYGtO+uo+B91+5v2CCqowRJUGfbFOtCuSPR7+B3LDK8pkjK2SQ0mFPUfRA5z0z\nNVWtC0EYNBTRkqhYtqr3ZpUc\n-----END PRIVATE KEY-----\n",
|
||||
cert: "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIUHaenuNcUAu0tjDZGpc7fK4EX78gwDQYJKoZIhvcNAQEL\nBQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMQ0wCwYDVQQKDARPdmVuMREwDwYDVQQLDAhUZWFtIEJ1bjETMBEGA1UE\nAwwKc2VydmVyLWJ1bjAeFw0yMzA5MDYyMzI3MzRaFw0yNTA5MDUyMzI3MzRaMGkx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzENMAsGA1UECgwET3ZlbjERMA8GA1UECwwIVGVhbSBCdW4xEzARBgNVBAMMCnNl\ncnZlci1idW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+7odzr3yI\nYewRNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MB\nKw3rl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwP\ndwVUeR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn\n0oH9HbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOF\nzDpcp1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FU\nIDHtnUsoHX3RAgMBAAGjTzBNMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQ\nAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4EFgQUF3y/su4J/8ScpK+rM2LwTct6EQow\nDQYJKoZIhvcNAQELBQADggEBAGWGWp59Bmrk3Gt0bidFLEbvlOgGPWCT9ZrJUjgc\nhY44E+/t4gIBdoKOSwxo1tjtz7WsC2IYReLTXh1vTsgEitk0Bf4y7P40+pBwwZwK\naeIF9+PC6ZoAkXGFRoyEalaPVQDBg/DPOMRG9OH0lKfen9OGkZxmmjRLJzbyfAhU\noI/hExIjV8vehcvaJXmkfybJDYOYkN4BCNqPQHNf87ZNdFCb9Zgxwp/Ou+47J5k4\n5plQ+K7trfKXG3ABMbOJXNt1b0sH8jnpAsyHY4DLEQqxKYADbXsr3YX/yy6c0eOo\nX2bHGD1+zGsb7lGyNyoZrCZ0233glrEM4UxmvldBcWwOWfk=\n-----END CERTIFICATE-----\n",
|
||||
};
|
||||
const fs = require("fs");
|
||||
const { TLS_CERT, TLS_OPTIONS } = require("./tls-cert.cjs");
|
||||
const server = http2.createSecureServer({
|
||||
...TLS_CERT,
|
||||
ca: TLS_CERT.cert,
|
||||
rejectUnauthorized: false,
|
||||
});
|
||||
const setCookie = ["a=b", "c=d; Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly", "e=f"];
|
||||
|
||||
server.on("stream", (stream, headers, flags) => {
|
||||
stream.respond({
|
||||
"content-type": "text/html",
|
||||
":status": 200,
|
||||
"set-cookie": setCookie,
|
||||
});
|
||||
// errors here are not useful the test should handle on the client side
|
||||
stream.on("error", err => console.error(err));
|
||||
|
||||
if (headers["x-wait-trailer"]) {
|
||||
const response = { headers, flags };
|
||||
stream.respond({
|
||||
"content-type": "text/html",
|
||||
":status": 200,
|
||||
"set-cookie": setCookie,
|
||||
});
|
||||
stream.on("trailers", (headers, flags) => {
|
||||
stream.end(JSON.stringify({ ...response, trailers: headers }));
|
||||
});
|
||||
} else if (headers["x-no-echo"]) {
|
||||
let byteLength = 0;
|
||||
stream.on("data", chunk => {
|
||||
byteLength += chunk.length;
|
||||
});
|
||||
stream.respond({
|
||||
"content-type": "application/json",
|
||||
":status": 200,
|
||||
});
|
||||
stream.on("end", () => {
|
||||
stream.end(JSON.stringify(byteLength));
|
||||
});
|
||||
} else {
|
||||
stream.end(JSON.stringify({ headers, flags }));
|
||||
// Store the request information, excluding pseudo-headers in the header echo
|
||||
const requestData = {
|
||||
method: headers[":method"],
|
||||
path: headers[":path"],
|
||||
headers: Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value])),
|
||||
body: [],
|
||||
url: `${baseurl}${headers[":path"]}`,
|
||||
};
|
||||
|
||||
// Collect data from the stream
|
||||
stream.on("data", chunk => {
|
||||
requestData.body.push(chunk);
|
||||
});
|
||||
|
||||
// Once all data is received, echo it back
|
||||
stream.on("end", () => {
|
||||
if (requestData.body.length > 0) {
|
||||
requestData.data = Buffer.concat(requestData.body).toString();
|
||||
try {
|
||||
requestData.json = JSON.parse(requestData.data); // Convert buffer array to string
|
||||
} catch (e) {}
|
||||
}
|
||||
stream.respond({
|
||||
"content-type": "application/json",
|
||||
":status": 200,
|
||||
// Set security and cache-control headers
|
||||
"cache-control": "no-store",
|
||||
"x-content-type-options": "nosniff",
|
||||
"set-cookie": setCookie,
|
||||
});
|
||||
stream.end(JSON.stringify(requestData));
|
||||
});
|
||||
}
|
||||
});
|
||||
let baseurl = "https://localhost:";
|
||||
|
||||
server.listen(0, "localhost");
|
||||
|
||||
server.on("listening", () => {
|
||||
process.stdout.write(JSON.stringify(server.address()));
|
||||
const { port, address, family } = server.address();
|
||||
baseurl = `https://localhost:${port}`;
|
||||
process.stdout.write(JSON.stringify({ port, address: "localhost", family: "IPv4" }));
|
||||
});
|
||||
|
||||
@@ -1,107 +1,141 @@
|
||||
import { heapStats } from "bun:jsc";
|
||||
import http2 from "http2";
|
||||
import path from "path";
|
||||
// This file is meant to be able to run in node and bun
|
||||
const http2 = require("http2");
|
||||
const { TLS_OPTIONS, nodeEchoServer } = require("./http2-helpers.cjs");
|
||||
function getHeapStats() {
|
||||
return heapStats().objectTypeCounts;
|
||||
if (globalThis.Bun) {
|
||||
const heapStats = require("bun:jsc").heapStats;
|
||||
return heapStats().objectTypeCounts;
|
||||
} else {
|
||||
return {
|
||||
objectTypeCounts: {
|
||||
H2FrameParser: 0,
|
||||
TLSSocket: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
const gc = globalThis.gc || globalThis.Bun?.gc || (() => {});
|
||||
const sleep = dur => new Promise(resolve => setTimeout(resolve, dur));
|
||||
|
||||
const nodeExecutable = Bun.which("node");
|
||||
if (!nodeExecutable) {
|
||||
console.log("No node executable found");
|
||||
process.exit(99); // 99 no node executable
|
||||
}
|
||||
async function nodeEchoServer() {
|
||||
const subprocess = Bun.spawn([nodeExecutable, path.join(import.meta.dir, "node-echo-server.fixture.js")], {
|
||||
stdout: "pipe",
|
||||
});
|
||||
const reader = subprocess.stdout.getReader();
|
||||
const data = await reader.read();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
const address = JSON.parse(decoder.decode(data.value));
|
||||
const url = `https://${address.family === "IPv6" ? `[${address.address}]` : address.address}:${address.port}`;
|
||||
return { address, url, subprocess };
|
||||
}
|
||||
// X iterations should be enough to detect a leak
|
||||
const ITERATIONS = 50;
|
||||
const ITERATIONS = 20;
|
||||
// lets send a bigish payload
|
||||
const PAYLOAD = Buffer.from("a".repeat(128 * 1024));
|
||||
const PAYLOAD = Buffer.from("BUN".repeat((1024 * 128) / 3));
|
||||
const MULTIPLEX = 50;
|
||||
|
||||
const info = await nodeEchoServer();
|
||||
async function main() {
|
||||
let info;
|
||||
let tls;
|
||||
|
||||
async function runRequests(iterations) {
|
||||
for (let j = 0; j < iterations; j++) {
|
||||
let client = http2.connect(info.url, { rejectUnauthorized: false });
|
||||
let promises = [];
|
||||
// 100 multiplex POST connections per iteration
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const req = client.request({ ":path": "/post", ":method": "POST" });
|
||||
let got_response = false;
|
||||
req.on("response", () => {
|
||||
got_response = true;
|
||||
});
|
||||
if (process.env.HTTP2_SERVER_INFO) {
|
||||
info = JSON.parse(process.env.HTTP2_SERVER_INFO);
|
||||
} else {
|
||||
info = await nodeEchoServer();
|
||||
console.log("Starting server", info.url);
|
||||
}
|
||||
|
||||
req.setEncoding("utf8");
|
||||
req.on("end", () => {
|
||||
if (got_response) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error("no response"));
|
||||
}
|
||||
});
|
||||
req.write(PAYLOAD);
|
||||
req.end();
|
||||
promises.push(promise);
|
||||
if (process.env.HTTP2_SERVER_TLS) {
|
||||
tls = JSON.parse(process.env.HTTP2_SERVER_TLS);
|
||||
} else {
|
||||
tls = TLS_OPTIONS;
|
||||
}
|
||||
|
||||
async function runRequests(iterations) {
|
||||
for (let j = 0; j < iterations; j++) {
|
||||
let client = http2.connect(info.url, tls);
|
||||
let promises = [];
|
||||
for (let i = 0; i < MULTIPLEX; i++) {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const req = client.request({ ":path": "/post", ":method": "POST", "x-no-echo": "1" });
|
||||
req.setEncoding("utf8");
|
||||
req.on("response", (headers, flags) => {
|
||||
req.on("data", chunk => {
|
||||
if (JSON.parse(chunk) !== PAYLOAD.length) {
|
||||
console.log("Got wrong data", chunk);
|
||||
reject(new Error("wrong data"));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
req.end(PAYLOAD, err => {
|
||||
if (err) reject(err);
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
try {
|
||||
client.close();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
client = null;
|
||||
promises = null;
|
||||
gc(true);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
client.close();
|
||||
client = null;
|
||||
promises = null;
|
||||
Bun.gc(true);
|
||||
}
|
||||
|
||||
try {
|
||||
const startStats = getHeapStats();
|
||||
|
||||
// warm up
|
||||
await runRequests(ITERATIONS);
|
||||
await sleep(10);
|
||||
gc(true);
|
||||
// take a baseline
|
||||
const baseline = process.memoryUsage.rss();
|
||||
console.error("Initial memory usage", (baseline / 1024 / 1024) | 0, "MB");
|
||||
|
||||
// run requests
|
||||
await runRequests(ITERATIONS);
|
||||
await sleep(10);
|
||||
gc(true);
|
||||
// take an end snapshot
|
||||
const end = process.memoryUsage.rss();
|
||||
|
||||
const delta = end - baseline;
|
||||
const deltaMegaBytes = (delta / 1024 / 1024) | 0;
|
||||
console.error("Memory delta", deltaMegaBytes, "MB");
|
||||
|
||||
// we executed 100 requests per iteration, memory usage should not go up by 10 MB
|
||||
if (deltaMegaBytes > 20) {
|
||||
console.log("Too many bodies leaked", deltaMegaBytes);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const endStats = getHeapStats();
|
||||
info?.subprocess?.kill?.();
|
||||
// check for H2FrameParser leaks
|
||||
const pendingH2Parsers = (endStats.H2FrameParser || 0) - (startStats.H2FrameParser || 0);
|
||||
if (pendingH2Parsers > 5) {
|
||||
console.log("Too many pending H2FrameParsers", pendingH2Parsers);
|
||||
process.exit(pendingH2Parsers);
|
||||
}
|
||||
// check for TLSSocket leaks
|
||||
const pendingTLSSockets = (endStats.TLSSocket || 0) - (startStats.TLSSocket || 0);
|
||||
if (pendingTLSSockets > 5) {
|
||||
console.log("Too many pending TLSSockets", pendingTLSSockets);
|
||||
process.exit(pendingTLSSockets);
|
||||
}
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
info?.subprocess?.kill?.();
|
||||
process.exit(99); // 99 exception
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const startStats = getHeapStats();
|
||||
|
||||
// warm up
|
||||
await runRequests(ITERATIONS);
|
||||
await Bun.sleep(10);
|
||||
Bun.gc(true);
|
||||
// take a baseline
|
||||
const baseline = process.memoryUsage.rss();
|
||||
// run requests
|
||||
await runRequests(ITERATIONS);
|
||||
await Bun.sleep(10);
|
||||
Bun.gc(true);
|
||||
// take an end snapshot
|
||||
const end = process.memoryUsage.rss();
|
||||
|
||||
const delta = end - baseline;
|
||||
const bodiesLeaked = delta / PAYLOAD.length;
|
||||
// we executed 10 requests per iteration
|
||||
if (bodiesLeaked > ITERATIONS) {
|
||||
console.log("Too many bodies leaked", bodiesLeaked);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const endStats = getHeapStats();
|
||||
info.subprocess.kill();
|
||||
// check for H2FrameParser leaks
|
||||
const pendingH2Parsers = (endStats.H2FrameParser || 0) - (startStats.H2FrameParser || 0);
|
||||
if (pendingH2Parsers > 5) {
|
||||
console.log("Too many pending H2FrameParsers", pendingH2Parsers);
|
||||
process.exit(pendingH2Parsers);
|
||||
}
|
||||
// check for TLSSocket leaks
|
||||
const pendingTLSSockets = (endStats.TLSSocket || 0) - (startStats.TLSSocket || 0);
|
||||
if (pendingTLSSockets > 5) {
|
||||
console.log("Too many pending TLSSockets", pendingTLSSockets);
|
||||
process.exit(pendingTLSSockets);
|
||||
}
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
info.subprocess.kill();
|
||||
process.exit(99); // 99 exception
|
||||
}
|
||||
main().then(
|
||||
() => {},
|
||||
err => {
|
||||
console.error(err);
|
||||
process.exit(99);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -8,25 +8,21 @@ import fs from "node:fs";
|
||||
import { bunExe, bunEnv } from "harness";
|
||||
import { tmpdir } from "node:os";
|
||||
import http2utils from "./helpers";
|
||||
|
||||
const TLS_CERT = {
|
||||
key: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+7odzr3yIYewR\nNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MBKw3r\nl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwPdwVU\neR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn0oH9\nHbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOFzDpc\np1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FUIDHt\nnUsoHX3RAgMBAAECggEAAckMqkn+ER3c7YMsKRLc5bUE9ELe+ftUwfA6G+oXVorn\nE+uWCXGdNqI+TOZkQpurQBWn9IzTwv19QY+H740cxo0ozZVSPE4v4czIilv9XlVw\n3YCNa2uMxeqp76WMbz1xEhaFEgn6ASTVf3hxYJYKM0ljhPX8Vb8wWwlLONxr4w4X\nOnQAB5QE7i7LVRsQIpWKnGsALePeQjzhzUZDhz0UnTyGU6GfC+V+hN3RkC34A8oK\njR3/Wsjahev0Rpb+9Pbu3SgTrZTtQ+srlRrEsDG0wVqxkIk9ueSMOHlEtQ7zYZsk\nlX59Bb8LHNGQD5o+H1EDaC6OCsgzUAAJtDRZsPiZEQKBgQDs+YtVsc9RDMoC0x2y\nlVnP6IUDXt+2UXndZfJI3YS+wsfxiEkgK7G3AhjgB+C+DKEJzptVxP+212hHnXgr\n1gfW/x4g7OWBu4IxFmZ2J/Ojor+prhHJdCvD0VqnMzauzqLTe92aexiexXQGm+WW\nwRl3YZLmkft3rzs3ZPhc1G2X9QKBgQDOQq3rrxcvxSYaDZAb+6B/H7ZE4natMCiz\nLx/cWT8n+/CrJI2v3kDfdPl9yyXIOGrsqFgR3uhiUJnz+oeZFFHfYpslb8KvimHx\nKI+qcVDcprmYyXj2Lrf3fvj4pKorc+8TgOBDUpXIFhFDyM+0DmHLfq+7UqvjU9Hs\nkjER7baQ7QKBgQDTh508jU/FxWi9RL4Jnw9gaunwrEt9bxUc79dp+3J25V+c1k6Q\nDPDBr3mM4PtYKeXF30sBMKwiBf3rj0CpwI+W9ntqYIwtVbdNIfWsGtV8h9YWHG98\nJ9q5HLOS9EAnogPuS27walj7wL1k+NvjydJ1of+DGWQi3aQ6OkMIegap0QKBgBlR\nzCHLa5A8plG6an9U4z3Xubs5BZJ6//QHC+Uzu3IAFmob4Zy+Lr5/kITlpCyw6EdG\n3xDKiUJQXKW7kluzR92hMCRnVMHRvfYpoYEtydxcRxo/WS73SzQBjTSQmicdYzLE\ntkLtZ1+ZfeMRSpXy0gR198KKAnm0d2eQBqAJy0h9AoGBAM80zkd+LehBKq87Zoh7\ndtREVWslRD1C5HvFcAxYxBybcKzVpL89jIRGKB8SoZkF7edzhqvVzAMP0FFsEgCh\naClYGtO+uo+B91+5v2CCqowRJUGfbFOtCuSPR7+B3LDK8pkjK2SQ0mFPUfRA5z0z\nNVWtC0EYNBTRkqhYtqr3ZpUc\n-----END PRIVATE KEY-----\n",
|
||||
cert: "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIUHaenuNcUAu0tjDZGpc7fK4EX78gwDQYJKoZIhvcNAQEL\nBQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMQ0wCwYDVQQKDARPdmVuMREwDwYDVQQLDAhUZWFtIEJ1bjETMBEGA1UE\nAwwKc2VydmVyLWJ1bjAeFw0yMzA5MDYyMzI3MzRaFw0yNTA5MDUyMzI3MzRaMGkx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzENMAsGA1UECgwET3ZlbjERMA8GA1UECwwIVGVhbSBCdW4xEzARBgNVBAMMCnNl\ncnZlci1idW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+7odzr3yI\nYewRNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MB\nKw3rl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwP\ndwVUeR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn\n0oH9HbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOF\nzDpcp1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FU\nIDHtnUsoHX3RAgMBAAGjTzBNMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQ\nAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4EFgQUF3y/su4J/8ScpK+rM2LwTct6EQow\nDQYJKoZIhvcNAQELBQADggEBAGWGWp59Bmrk3Gt0bidFLEbvlOgGPWCT9ZrJUjgc\nhY44E+/t4gIBdoKOSwxo1tjtz7WsC2IYReLTXh1vTsgEitk0Bf4y7P40+pBwwZwK\naeIF9+PC6ZoAkXGFRoyEalaPVQDBg/DPOMRG9OH0lKfen9OGkZxmmjRLJzbyfAhU\noI/hExIjV8vehcvaJXmkfybJDYOYkN4BCNqPQHNf87ZNdFCb9Zgxwp/Ou+47J5k4\n5plQ+K7trfKXG3ABMbOJXNt1b0sH8jnpAsyHY4DLEQqxKYADbXsr3YX/yy6c0eOo\nX2bHGD1+zGsb7lGyNyoZrCZ0233glrEM4UxmvldBcWwOWfk=\n-----END CERTIFICATE-----\n",
|
||||
};
|
||||
import { afterAll, describe, beforeAll, it, test, expect } from "vitest";
|
||||
import { TLS_OPTIONS, nodeEchoServer, TLS_CERT } from "./http2-helpers";
|
||||
|
||||
const nodeExecutable = which("node");
|
||||
async function nodeEchoServer() {
|
||||
if (!nodeExecutable) throw new Error("node executable not found");
|
||||
const subprocess = Bun.spawn([nodeExecutable, path.join(import.meta.dir, "node-echo-server.fixture.js")], {
|
||||
stdout: "pipe",
|
||||
});
|
||||
const reader = subprocess.stdout.getReader();
|
||||
const data = await reader.read();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
const address = JSON.parse(decoder.decode(data.value));
|
||||
const url = `https://${address.family === "IPv6" ? `[${address.address}]` : address.address}:${address.port}`;
|
||||
return { address, url, subprocess };
|
||||
}
|
||||
let nodeEchoServer_;
|
||||
|
||||
let HTTPS_SERVER;
|
||||
beforeAll(async () => {
|
||||
nodeEchoServer_ = await nodeEchoServer();
|
||||
HTTPS_SERVER = nodeEchoServer_.url;
|
||||
});
|
||||
afterAll(async () => {
|
||||
nodeEchoServer_.subprocess?.kill?.(9);
|
||||
});
|
||||
|
||||
async function nodeDynamicServer(test_name, code) {
|
||||
if (!nodeExecutable) throw new Error("node executable not found");
|
||||
|
||||
@@ -47,7 +43,10 @@ server.on("listening", () => {
|
||||
|
||||
const subprocess = Bun.spawn([nodeExecutable, file_name], {
|
||||
stdout: "pipe",
|
||||
stdin: "inherit",
|
||||
stderr: "inherit",
|
||||
});
|
||||
subprocess.unref();
|
||||
const reader = subprocess.stdout.getReader();
|
||||
const data = await reader.read();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
@@ -58,6 +57,9 @@ server.on("listening", () => {
|
||||
|
||||
function doHttp2Request(url, headers, payload, options, request_options) {
|
||||
const { promise, resolve, reject: promiseReject } = Promise.withResolvers();
|
||||
if (url.startsWith(HTTPS_SERVER)) {
|
||||
options = { ...(options || {}), rejectUnauthorized: true, ...TLS_OPTIONS };
|
||||
}
|
||||
|
||||
const client = options ? http2.connect(url, options) : http2.connect(url);
|
||||
client.on("error", promiseReject);
|
||||
@@ -93,8 +95,7 @@ function doHttp2Request(url, headers, payload, options, request_options) {
|
||||
|
||||
function doMultiplexHttp2Request(url, requests) {
|
||||
const { promise, resolve, reject: promiseReject } = Promise.withResolvers();
|
||||
|
||||
const client = http2.connect(url);
|
||||
const client = http2.connect(url, TLS_OPTIONS);
|
||||
|
||||
client.on("error", promiseReject);
|
||||
function reject(err) {
|
||||
@@ -139,30 +140,30 @@ function doMultiplexHttp2Request(url, requests) {
|
||||
describe("Client Basics", () => {
|
||||
// we dont support server yet but we support client
|
||||
it("should be able to send a GET request", async () => {
|
||||
const result = await doHttp2Request("https://httpbin.org", { ":path": "/get", "test-header": "test-value" });
|
||||
const result = await doHttp2Request(HTTPS_SERVER, { ":path": "/get", "test-header": "test-value" });
|
||||
let parsed;
|
||||
expect(() => (parsed = JSON.parse(result.data))).not.toThrow();
|
||||
expect(parsed.url).toBe("https://httpbin.org/get");
|
||||
expect(parsed.headers["Test-Header"]).toBe("test-value");
|
||||
expect(parsed.url).toBe(`${HTTPS_SERVER}/get`);
|
||||
expect(parsed.headers["test-header"]).toBe("test-value");
|
||||
});
|
||||
it("should be able to send a POST request", async () => {
|
||||
const payload = JSON.stringify({ "hello": "bun" });
|
||||
const result = await doHttp2Request(
|
||||
"https://httpbin.org",
|
||||
HTTPS_SERVER,
|
||||
{ ":path": "/post", "test-header": "test-value", ":method": "POST" },
|
||||
payload,
|
||||
);
|
||||
let parsed;
|
||||
expect(() => (parsed = JSON.parse(result.data))).not.toThrow();
|
||||
expect(parsed.url).toBe("https://httpbin.org/post");
|
||||
expect(parsed.headers["Test-Header"]).toBe("test-value");
|
||||
expect(parsed.url).toBe(`${HTTPS_SERVER}/post`);
|
||||
expect(parsed.headers["test-header"]).toBe("test-value");
|
||||
expect(parsed.json).toEqual({ "hello": "bun" });
|
||||
expect(parsed.data).toEqual(payload);
|
||||
});
|
||||
it("should be able to send data using end", async () => {
|
||||
const payload = JSON.stringify({ "hello": "bun" });
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://httpbin.org");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
const req = client.request({ ":path": "/post", "test-header": "test-value", ":method": "POST" });
|
||||
let response_headers = null;
|
||||
@@ -182,13 +183,13 @@ describe("Client Basics", () => {
|
||||
const result = await promise;
|
||||
let parsed;
|
||||
expect(() => (parsed = JSON.parse(result.data))).not.toThrow();
|
||||
expect(parsed.url).toBe("https://httpbin.org/post");
|
||||
expect(parsed.headers["Test-Header"]).toBe("test-value");
|
||||
expect(parsed.url).toBe(`${HTTPS_SERVER}/post`);
|
||||
expect(parsed.headers["test-header"]).toBe("test-value");
|
||||
expect(parsed.json).toEqual({ "hello": "bun" });
|
||||
expect(parsed.data).toEqual(payload);
|
||||
});
|
||||
it("should be able to mutiplex GET requests", async () => {
|
||||
const results = await doMultiplexHttp2Request("https://httpbin.org", [
|
||||
const results = await doMultiplexHttp2Request(HTTPS_SERVER, [
|
||||
{ headers: { ":path": "/get" } },
|
||||
{ headers: { ":path": "/get" } },
|
||||
{ headers: { ":path": "/get" } },
|
||||
@@ -199,11 +200,11 @@ describe("Client Basics", () => {
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let parsed;
|
||||
expect(() => (parsed = JSON.parse(results[i].data))).not.toThrow();
|
||||
expect(parsed.url).toBe("https://httpbin.org/get");
|
||||
expect(parsed.url).toBe(`${HTTPS_SERVER}/get`);
|
||||
}
|
||||
});
|
||||
it("should be able to mutiplex POST requests", async () => {
|
||||
const results = await doMultiplexHttp2Request("https://httpbin.org", [
|
||||
const results = await doMultiplexHttp2Request(HTTPS_SERVER, [
|
||||
{ headers: { ":path": "/post", ":method": "POST" }, payload: JSON.stringify({ "request": 1 }) },
|
||||
{ headers: { ":path": "/post", ":method": "POST" }, payload: JSON.stringify({ "request": 2 }) },
|
||||
{ headers: { ":path": "/post", ":method": "POST" }, payload: JSON.stringify({ "request": 3 }) },
|
||||
@@ -214,7 +215,7 @@ describe("Client Basics", () => {
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let parsed;
|
||||
expect(() => (parsed = JSON.parse(results[i].data))).not.toThrow();
|
||||
expect(parsed.url).toBe("https://httpbin.org/post");
|
||||
expect(parsed.url).toBe(`${HTTPS_SERVER}/post`);
|
||||
expect([1, 2, 3, 4, 5]).toContain(parsed.json?.request);
|
||||
}
|
||||
});
|
||||
@@ -508,7 +509,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("headers cannot be bigger than 65536 bytes", async () => {
|
||||
try {
|
||||
await doHttp2Request("https://bun.sh", { ":path": "/", "test-header": "A".repeat(90000) });
|
||||
await doHttp2Request(HTTPS_SERVER, { ":path": "/", "test-header": "A".repeat(90000) });
|
||||
expect("unreachable").toBe(true);
|
||||
} catch (err) {
|
||||
expect(err.code).toBe("ERR_HTTP2_STREAM_ERROR");
|
||||
@@ -517,7 +518,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("should be destroyed after close", async () => {
|
||||
const { promise, resolve, reject: promiseReject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://httpbin.org/get");
|
||||
const client = http2.connect(`${HTTPS_SERVER}/get`, TLS_OPTIONS);
|
||||
client.on("error", promiseReject);
|
||||
client.on("close", resolve);
|
||||
function reject(err) {
|
||||
@@ -537,7 +538,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("should be destroyed after destroy", async () => {
|
||||
const { promise, resolve, reject: promiseReject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://httpbin.org/get");
|
||||
const client = http2.connect(`${HTTPS_SERVER}/get`, TLS_OPTIONS);
|
||||
client.on("error", promiseReject);
|
||||
client.on("close", resolve);
|
||||
function reject(err) {
|
||||
@@ -556,21 +557,21 @@ describe("Client Basics", () => {
|
||||
expect(client.destroyed).toBe(true);
|
||||
});
|
||||
it("should fail to connect over HTTP/1.1", async () => {
|
||||
const tls = {
|
||||
...TLS_CERT,
|
||||
ca: TLS_CERT.cert,
|
||||
};
|
||||
const tls = TLS_CERT;
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
hostname: "127.0.0.1",
|
||||
tls,
|
||||
tls: {
|
||||
...tls,
|
||||
ca: TLS_CERT.ca,
|
||||
},
|
||||
fetch() {
|
||||
return new Response("hello");
|
||||
},
|
||||
});
|
||||
const url = `https://127.0.0.1:${server.port}`;
|
||||
try {
|
||||
await doHttp2Request(url, { ":path": "/" }, null, tls);
|
||||
await doHttp2Request(url, { ":path": "/" }, null, TLS_OPTIONS);
|
||||
expect("unreachable").toBe(true);
|
||||
} catch (err) {
|
||||
expect(err.code).toBe("ERR_HTTP2_ERROR");
|
||||
@@ -599,12 +600,13 @@ describe("Client Basics", () => {
|
||||
.connect(
|
||||
{
|
||||
rejectUnauthorized: false,
|
||||
host: "httpbin.org",
|
||||
port: 443,
|
||||
host: new URL(HTTPS_SERVER).hostname,
|
||||
port: new URL(HTTPS_SERVER).port,
|
||||
ALPNProtocols: ["h2"],
|
||||
...TLS_OPTIONS,
|
||||
},
|
||||
() => {
|
||||
doHttp2Request("https://httpbin.org/get", { ":path": "/get" }, null, {
|
||||
doHttp2Request(`${HTTPS_SERVER}/get`, { ":path": "/get" }, null, {
|
||||
createConnection: () => {
|
||||
return new JSSocket(socket);
|
||||
},
|
||||
@@ -615,20 +617,20 @@ describe("Client Basics", () => {
|
||||
const result = await promise;
|
||||
let parsed;
|
||||
expect(() => (parsed = JSON.parse(result.data))).not.toThrow();
|
||||
expect(parsed.url).toBe("https://httpbin.org/get");
|
||||
expect(parsed.url).toBe(`${HTTPS_SERVER}/get`);
|
||||
socket.destroy();
|
||||
});
|
||||
it("close callback", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect(`https://httpbin.org/get`);
|
||||
const client = http2.connect(`${HTTPS_SERVER}/get`, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
client.close(resolve);
|
||||
await promise;
|
||||
expect(client.destroyed).toBe(true);
|
||||
});
|
||||
it("is possibel to abort request", async () => {
|
||||
it("is possible to abort request", async () => {
|
||||
const abortController = new AbortController();
|
||||
const promise = doHttp2Request("https://httpbin.org/get", { ":path": "/get" }, null, null, {
|
||||
const promise = doHttp2Request(`${HTTPS_SERVER}/get`, { ":path": "/get" }, null, null, {
|
||||
signal: abortController.signal,
|
||||
});
|
||||
abortController.abort();
|
||||
@@ -643,7 +645,7 @@ describe("Client Basics", () => {
|
||||
it("aborted event should work with abortController", async () => {
|
||||
const abortController = new AbortController();
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
const req = client.request({ ":path": "/" }, { signal: abortController.signal });
|
||||
req.on("aborted", resolve);
|
||||
@@ -662,7 +664,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("aborted event should work with aborted signal", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
const req = client.request({ ":path": "/" }, { signal: AbortSignal.abort() });
|
||||
req.on("aborted", resolve);
|
||||
@@ -680,7 +682,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("endAfterHeaders should work", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
const req = client.request({ ":path": "/" });
|
||||
req.endAfterHeaders = true;
|
||||
@@ -703,7 +705,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("state should work", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
const req = client.request({ ":path": "/", "test-header": "test-value" });
|
||||
{
|
||||
@@ -811,7 +813,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("ping events should work", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
client.on("connect", () => {
|
||||
client.ping(Buffer.from("12345678"), (err, duration, payload) => {
|
||||
@@ -838,7 +840,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("ping without events should work", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
client.on("connect", () => {
|
||||
client.ping((err, duration, payload) => {
|
||||
@@ -864,7 +866,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("ping with wrong payload length events should error", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", resolve);
|
||||
client.on("connect", () => {
|
||||
client.ping(Buffer.from("oops"), (err, duration, payload) => {
|
||||
@@ -882,7 +884,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("ping with wrong payload type events should throw", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", resolve);
|
||||
client.on("connect", () => {
|
||||
try {
|
||||
@@ -901,7 +903,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("stream event should work", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
client.on("stream", stream => {
|
||||
resolve(stream);
|
||||
@@ -914,7 +916,7 @@ describe("Client Basics", () => {
|
||||
});
|
||||
it("should wait request to be sent before closing", async () => {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect("https://www.example.com");
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
const req = client.request({ ":path": "/" });
|
||||
let response_headers = null;
|
||||
@@ -928,54 +930,46 @@ describe("Client Basics", () => {
|
||||
expect(response_headers[":status"]).toBe(200);
|
||||
});
|
||||
it("wantTrailers should work", async () => {
|
||||
const info = await nodeEchoServer();
|
||||
try {
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect(info.url, {
|
||||
...TLS_CERT,
|
||||
ca: TLS_CERT.cert,
|
||||
});
|
||||
client.on("error", reject);
|
||||
const headers = { ":path": "/", ":method": "POST", "x-wait-trailer": "true" };
|
||||
const req = client.request(headers, {
|
||||
waitForTrailers: true,
|
||||
});
|
||||
req.setEncoding("utf8");
|
||||
let response_headers;
|
||||
req.on("response", headers => {
|
||||
response_headers = headers;
|
||||
});
|
||||
let trailers = { "x-trailer": "hello" };
|
||||
req.on("wantTrailers", () => {
|
||||
req.sendTrailers(trailers);
|
||||
});
|
||||
let data = "";
|
||||
req.on("data", chunk => {
|
||||
data += chunk;
|
||||
client.close();
|
||||
});
|
||||
req.on("error", reject);
|
||||
req.on("end", () => {
|
||||
resolve({ data, headers: response_headers });
|
||||
client.close();
|
||||
});
|
||||
req.end("hello");
|
||||
const response = await promise;
|
||||
let parsed;
|
||||
expect(() => (parsed = JSON.parse(response.data))).not.toThrow();
|
||||
expect(parsed.headers[":method"]).toEqual(headers[":method"]);
|
||||
expect(parsed.headers[":path"]).toEqual(headers[":path"]);
|
||||
expect(parsed.headers["x-wait-trailer"]).toEqual(headers["x-wait-trailer"]);
|
||||
expect(parsed.trailers).toEqual(trailers);
|
||||
expect(response.headers[":status"]).toBe(200);
|
||||
expect(response.headers["set-cookie"]).toEqual([
|
||||
"a=b",
|
||||
"c=d; Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly",
|
||||
"e=f",
|
||||
]);
|
||||
} finally {
|
||||
info.subprocess.kill();
|
||||
}
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
const client = http2.connect(HTTPS_SERVER, TLS_OPTIONS);
|
||||
client.on("error", reject);
|
||||
const headers = { ":path": "/", ":method": "POST", "x-wait-trailer": "true" };
|
||||
const req = client.request(headers, {
|
||||
waitForTrailers: true,
|
||||
});
|
||||
req.setEncoding("utf8");
|
||||
let response_headers;
|
||||
req.on("response", headers => {
|
||||
response_headers = headers;
|
||||
});
|
||||
let trailers = { "x-trailer": "hello" };
|
||||
req.on("wantTrailers", () => {
|
||||
req.sendTrailers(trailers);
|
||||
});
|
||||
let data = "";
|
||||
req.on("data", chunk => {
|
||||
data += chunk;
|
||||
client.close();
|
||||
});
|
||||
req.on("error", reject);
|
||||
req.on("end", () => {
|
||||
resolve({ data, headers: response_headers });
|
||||
client.close();
|
||||
});
|
||||
req.end("hello");
|
||||
const response = await promise;
|
||||
let parsed;
|
||||
expect(() => (parsed = JSON.parse(response.data))).not.toThrow();
|
||||
expect(parsed.headers[":method"]).toEqual(headers[":method"]);
|
||||
expect(parsed.headers[":path"]).toEqual(headers[":path"]);
|
||||
expect(parsed.headers["x-wait-trailer"]).toEqual(headers["x-wait-trailer"]);
|
||||
expect(parsed.trailers).toEqual(trailers);
|
||||
expect(response.headers[":status"]).toBe(200);
|
||||
expect(response.headers["set-cookie"]).toEqual([
|
||||
"a=b",
|
||||
"c=d; Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly",
|
||||
"e=f",
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not leak memory", () => {
|
||||
@@ -984,10 +978,13 @@ describe("Client Basics", () => {
|
||||
env: {
|
||||
...bunEnv,
|
||||
BUN_JSC_forceRAMSize: (1024 * 1024 * 64).toString("10"),
|
||||
HTTP2_SERVER_INFO: JSON.stringify(nodeEchoServer_),
|
||||
HTTP2_SERVER_TLS: JSON.stringify(TLS_OPTIONS),
|
||||
},
|
||||
stderr: "inherit",
|
||||
stdin: "inherit",
|
||||
stdout: "inherit",
|
||||
});
|
||||
expect(stdout.toString("utf-8")).toBeEmpty();
|
||||
expect(exitCode).toBe(0);
|
||||
}, 100000);
|
||||
|
||||
@@ -1048,7 +1045,7 @@ describe("Client Basics", () => {
|
||||
}
|
||||
});
|
||||
it("should not be able to write on socket", async () => {
|
||||
const server = await nodeEchoServer();
|
||||
const server = nodeEchoServer_;
|
||||
try {
|
||||
const client = http2.connect(server.url);
|
||||
client.socket.write("hello");
|
||||
|
||||
9
test/js/node/http2/tls-cert.cjs
Normal file
9
test/js/node/http2/tls-cert.cjs
Normal file
@@ -0,0 +1,9 @@
|
||||
var TLS_CERT = {
|
||||
key: "\n-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGYP2mk8DRbJiI\nZFISwazTu3cwihJo42JROYgUkCvjHssijs86zIX0DYYSsQjw8dgU7PGFGdnEthTu\nMj9FyJpovX/o72/FPCrJtPPr2/ztlIWmOvKzxjA1f4OicKT0gVxIMca/ZjtXQ6G3\nR0fkL+dEPc7aJNvG2p+DIlGPtqN0PH9ktVtOAzGDEMtxculOmzCF2+auaukK5R1B\njTpHAJIRzwQFuwXz8LhSA4v5tSNlNkpIeIjwOM3writFBChVoDm/TPey4HeQ6Psy\nfjzBYg1EnWXJT2aDAtQJQmCcpoLt3R02HztZGlPF37UQK2JiujEruQ81WBmLTauI\nh4JVVOB7AgMBAAECggEARk31VuWiPhYYcK3tEEynLVqQwRkNsTJ0k4iqG2+EvjcZ\nkqO9+X6mMnngfBtVqd5rz+6xIZSpwrcs78XK+rY/UsNl422H1QSfvWBt2bbbCm/K\ndcEKZn/kcfFA+36kVyrJQ6SwZQCcIy8KzuNqLQp1EZA+EL1jTjQIt/afpSj7AKQY\nVlWHMXRf38WRGAo4w5jMMzM0Kw9Kn1U5Nx6WD7FcXo4uRhmxF/0aRzCXobWPcYwL\nBFbOJjEK0jEV1pNkGATUJ0NsgnHWRAuvQj7z0noSt6jXCSBZFjkf+j9AUmxuGOD+\nyargqTgINX/WYpIEd9Fr6p4vVBnA5coCVdGawFgvkQKBgQDl5CxUNeo9SRS7BeoF\njo+Ivo0VLQJWb9U8HpG1UTc0gjGitgV4dQ/6C4OFlzUF69gIGbzGf5AVIvbzCJUT\nAuV6BpGdMfexVRms8p3ktZ1dRDxN6wPICAfCLNV8aOp2p9f9ZFSWnm8M8oGMrHo+\nKgj0f19FNXOMbKcDncj+ZMLc3QKBgQDc6KE3KW3U8a+EVUcOfvnHnEi4FeeSuykA\n/KCvGww5m5QoDy+e5F4VgFWrOnobPERk9tTGPiR9P5juNxXtzecT+Ug4Bxxk6NXy\n2tK4RoR1m/NTw1Hr3xp7CodFqdE/sDeb/M253lnfnp2JSp+J6ddavb4XTj+J+PIA\neH1NW9PRNwKBgQCfbvYbVOTlqehZqElbnzoGQPjBRdzIK3j738t3ryKVJPHdgVUb\n7DuvUwrcvDgGqkDBpW/ZTiCTuBMCC+KvM6QIU8Pq+/tnHbjXy88bDaVcSHV2KFYQ\nBRm0XbmVNYHd1puh3VIYvzoPBaQ49mk08ZwSTL+61M4VBklx5Zy+aQ0HdQKBgCrD\nwgnatE9n5jF5DMNqo1IYGB/C5cyK/NobDcQ4OTqhuqGypuZckTYaXPtD28WP+jGN\ncw1ZlFjGygU7lrwtgxFjza5C+iUyydA0ulxAEn5uDUHm6uH9k7PECwHaaQ6qP2ms\nG+tidwWKQDcGwjHBmhYP60+5ryU3kymyKZejMjMrAoGBAMgk1COWDKaYDd5fWnBP\nyejYV1tPLAW83s66DMDXMZWpTbw5sKEvxERJL3HUGrxD0bRRBSG7wA2RjA9JouUN\njzHIFSCuJi9ZNJoHA9RV4UtpMTdMWg05nj/izoNXnYfW7LA4Yo3R7BqKDXIN/W3u\nR9MHcOTB0jPnKj9dcSdU5nXy\n-----END PRIVATE KEY-----",
|
||||
cert: "\n-----BEGIN CERTIFICATE-----\nMIIDCTCCAfGgAwIBAgIUAbpwqNLdxKrf8ScWim6lPc59R8gwDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDQzMDA1NDk0M1oXDTI0MDUz\nMDA1NDk0M1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEAxmD9ppPA0WyYiGRSEsGs07t3MIoSaONiUTmIFJAr4x7L\nIo7POsyF9A2GErEI8PHYFOzxhRnZxLYU7jI/RciaaL1/6O9vxTwqybTz69v87ZSF\npjrys8YwNX+DonCk9IFcSDHGv2Y7V0Oht0dH5C/nRD3O2iTbxtqfgyJRj7ajdDx/\nZLVbTgMxgxDLcXLpTpswhdvmrmrpCuUdQY06RwCSEc8EBbsF8/C4UgOL+bUjZTZK\nSHiI8DjN8K4rRQQoVaA5v0z3suB3kOj7Mn48wWINRJ1lyU9mgwLUCUJgnKaC7d0d\nNh87WRpTxd+1ECtiYroxK7kPNVgZi02riIeCVVTgewIDAQABo1MwUTAdBgNVHQ4E\nFgQUT42TrSl9k+7K3zYA32cnubSY07UwHwYDVR0jBBgwFoAUT42TrSl9k+7K3zYA\n32cnubSY07UwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAljpg\nWyKu4HIAdCwvAbplaiwKlVO1KGb8a2/FJ3zcUiipX1q2dDDrrPsjuWXQN7/NrmuT\n65zUAJSYJZZyw5GB1oQD96YsQq2B4Y2s9jx5H/e3W4ys5YJRZRU4cFJLSHD07x5O\n7E9mT5HoyF0fy1/7XnKRwfYMsUhe/kRkM2XbT8d5ZRPesNKPWVb7Pv6BYWXcewSB\n/vnEr2vtjLY6J3WxYPwY3ocC8K1vNk103zkwX37suXu65U3rWBiFiqUWCZf+8IFo\nNRKv5Gva03SnZ7I9pnJQv7igk21wxgG9/y5R8Kg7Fpkcqx3ph3CMsxbjhgX7pmYj\nV9Amy+loH/ocj6HpYQ==\n-----END CERTIFICATE-----",
|
||||
ca: "",
|
||||
};
|
||||
|
||||
var TLS_OPTIONS = { ca: TLS_CERT.cert };
|
||||
|
||||
module.exports = { TLS_CERT, TLS_OPTIONS };
|
||||
Reference in New Issue
Block a user