~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs-webrtc

WebRTC transport for atlibs: com.fedfork.exec.webrtc.connect in the browser, and webrtc-peer, its guest side

0
0
main
72 lines · 3.6 KB Raw History
// node --test js/http-datachannel.test.mjs
// httpChannel against Node's HTTP server, a fake datachannel bridged to a TCP socket (as webrtc-peer and
// the guest agent bridge a tag): content-length, chunked and close-delimited bodies, keep-alive on one
// connection, Connection: close ending it, and the size cap.
import assert from "node:assert/strict";
import { createServer } from "node:http";
import { connect, createServer as createTcp } from "node:net";
import { after, test } from "node:test";

import { BODY_MAX, httpChannel } from "./http-datachannel.js";

const connections = [];
const http = createServer((req, res) => {
	let body = "";
	req.on("data", (d) => (body += d)).on("end", () => {
		if (req.url === "/length") return res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ got: body, host: req.headers.host }));
		if (req.url === "/chunked") return (res.write("a".repeat(20000)), res.write("é"), res.end("z"));
		if (req.url === "/close") return res.writeHead(200, { connection: "close" }).end("bye");
		if (req.url === "/empty") return res.writeHead(202).end();
		if (req.url === "/huge") return res.writeHead(200, { "content-length": BODY_MAX + 1 }).end();
	});
}).on("connection", (s) => connections.push(s)).listen(0, "127.0.0.1");
// A raw server for a response delimited by the close (HTTP/1.0 style, no length).
const raw = createTcp((s) => s.once("data", () => s.end("HTTP/1.0 200 OK\r\ncontent-type: text/plain\r\n\r\nuntil close"))).listen(0, "127.0.0.1");
await Promise.all([http, raw].map((s) => new Promise((r) => s.once("listening", r))));
after(() => (http.close(), raw.close(), connections.forEach((s) => s.destroy())));

// A datachannel whose other end is 127.0.0.1:port.
async function channel(server) {
	const socket = connect(server.address().port, "127.0.0.1");
	await new Promise((r) => socket.once("connect", r));
	const dc = {
		readyState: "open",
		send: (bytes) => socket.write(bytes),
		close: () => socket.end(),
	};
	socket.on("data", (d) => dc.onmessage?.({ data: d.buffer.slice(d.byteOffset, d.byteOffset + d.length) }));
	socket.on("close", () => ((dc.readyState = "closed"), dc.onclose?.()));
	return dc;
}

const text = (r) => new TextDecoder().decode(r.body);

test("keep-alive: several requests on one channel, content-length and chunked bodies", async () => {
	const before = connections.length;
	const conn = httpChannel(await channel(http));
	const big = "x".repeat(50000);
	const one = await conn.request({ path: "/length", headers: { "content-type": "application/json" }, body: big });
	assert.deepEqual([one.status, JSON.parse(text(one))], [200, { got: big, host: "guest" }]);
	assert.equal(conn.reusable, true);
	const two = await conn.request({ path: "/chunked" });
	assert.equal(two.headers["transfer-encoding"], "chunked");
	assert.equal(text(two), `${"a".repeat(20000)}éz`);
	const three = await conn.request({ path: "/empty" });
	assert.deepEqual([three.status, three.body.length, conn.reusable], [202, 0, true]);
	assert.equal(connections.length - before, 1, "one connection");
});

test("Connection: close and a close-delimited body end the channel", async () => {
	const conn = httpChannel(await channel(http));
	assert.equal(text(await conn.request({ path: "/close" })), "bye");
	assert.equal(conn.reusable, false);
	const old = httpChannel(await channel(raw));
	assert.equal(text(await old.request({ method: "GET", path: "/" })), "until close");
	assert.equal(old.reusable, false);
});

test("a body over BODY_MAX is refused and the channel closed", async () => {
	const conn = httpChannel(await channel(http));
	await assert.rejects(conn.request({ path: "/huge" }), /over 4194304 bytes/);
	assert.equal(conn.reusable, false);
});