Refactor server stuff

This commit is contained in:
Schluffe
2026-09-18 11:41:18 +02:00
parent f062753f8f
commit 1c6f2368a1
9 changed files with 169 additions and 120 deletions
Generated
+4
View File
@@ -2,6 +2,7 @@
"version": "5", "version": "5",
"specifiers": { "specifiers": {
"jsr:@std/assert@*": "1.0.19", "jsr:@std/assert@*": "1.0.19",
"jsr:@std/http@*": "1.1.3",
"jsr:@std/internal@^1.0.12": "1.0.14" "jsr:@std/internal@^1.0.12": "1.0.14"
}, },
"jsr": { "jsr": {
@@ -11,6 +12,9 @@
"jsr:@std/internal" "jsr:@std/internal"
] ]
}, },
"@std/http@1.1.3": {
"integrity": "73a98e05ff58aa18bea562d286172e6c815b1eddd4fcab194beda19e3683b88c"
},
"@std/internal@1.0.14": { "@std/internal@1.0.14": {
"integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7" "integrity": "291516b3d4c35024d6ffbc0a9df5bf4c64116e05b50012cf846710152d2ffdf7"
} }
+3
View File
@@ -0,0 +1,3 @@
export function assertNever(): never {
throw new Error("never");
}
+5 -98
View File
@@ -1,107 +1,14 @@
/// <reference lib="deno.ns" /> /// <reference lib="deno.ns" />
import { assertEquals } from "jsr:@std/assert"; import { assertEquals } from "jsr:@std/assert";
import { startServer } from "./deno.ts"; import handler from "./handler.ts";
import { close, connect, flush, href, setup } from "./testSetup.ts";
const hostname = "0.0.0.0";
const port = 8042;
const href = `ws://${hostname}:${port}`;
function connect(url: string): Promise<[WebSocket, string[]]> {
return new Promise((resolve, reject) => {
const messages: string[] = [];
const socket = new WebSocket(url);
socket.onopen = () => resolve([socket, messages]);
socket.onerror = (error) => reject(error);
socket.onmessage = ({ data }) => {
messages.push(data);
};
});
}
function close(socket: WebSocket): Promise<void> {
return new Promise((resolve) => {
if (socket.readyState === WebSocket.CLOSED) {
return resolve();
}
socket.onclose = () => resolve();
socket.close();
});
}
function setup(
fn: (ctx: Deno.TestContext) => Promise<void>,
): (ctx: Deno.TestContext) => Promise<void> {
return async (ctx) => {
const shutdown = startServer(hostname, port);
try {
await fn(ctx);
} finally {
await shutdown();
}
};
}
function flush(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, 0));
}
Deno.test( Deno.test(
"Welcome all clients on connect.", "test",
setup(async () => { setup(async () => {
const [s1, m1] = await connect(href); const [s1, m1] = await connect(href);
const [s2, m2] = await connect(href);
await flush(); await flush();
assertEquals(m1, []);
assertEquals(m1, ["Welcome 1, type /join <room>."]);
assertEquals(m2, ["Welcome 2, type /join <room>."]);
await close(s2);
await close(s1); await close(s1);
}), }, handler),
);
Deno.test(
"Messages all clients in a room on joining.",
setup(async () => {
const [s1, m1] = await connect(href);
const [s2, m2] = await connect(href);
await flush();
m1.length = 0;
m2.length = 0;
s1.send("/join meh");
s2.send("/join meh");
await flush();
assertEquals(m1, ["1 has joined!", "2 has joined!"]);
assertEquals(m2, ["2 has joined!"]);
await close(s2);
await close(s1);
}),
);
Deno.test(
"Messages remaining members if one left the room.",
setup(async () => {
const [s1, m1] = await connect(href);
const [s2, m2] = await connect(href);
s1.send("/join meh");
s2.send("/join meh");
await flush();
m1.length = 0;
m2.length = 0;
await close(s2);
await flush();
assertEquals(m1, ["2 has left!"]);
assertEquals(m2, []);
await close(s1);
}),
); );
+21
View File
@@ -0,0 +1,21 @@
import { getCookies } from "jsr:@std/http/cookie";
function getSessionId(request: Request): null | string {
getCookies(request.headers);
return null;
}
export default function (request: Request): Response {
if (request.headers.get("upgrade") !== "websocket") {
return new Response("Upgrade required.", { status: 426 });
}
const sessonId = getSessionId(request);
if (!sessonId) {
return new Response("Session requierd.", { status: 401 });
}
const { socket, response } = Deno.upgradeWebSocket(request);
return response;
}
+3 -2
View File
@@ -1,3 +1,4 @@
import { startServer } from "./deno.ts"; import handler from "./handler.ts";
import { startServer } from "./server.ts";
startServer(); startServer(handler);
+64
View File
@@ -0,0 +1,64 @@
/// <reference lib="deno.ns" />
import { assertEquals } from "jsr:@std/assert";
import { close, connect, flush, href, setup } from "../testSetup.ts";
import { getHandler } from "./deno.ts";
Deno.test(
"Welcome all clients on connect.",
setup(async () => {
const [s1, m1] = await connect(href);
const [s2, m2] = await connect(href);
await flush();
assertEquals(m1, ["Welcome 1, type /join <room>."]);
assertEquals(m2, ["Welcome 2, type /join <room>."]);
await close(s2);
await close(s1);
}, getHandler()),
);
Deno.test(
"Messages all clients in a room on joining.",
setup(async () => {
const [s1, m1] = await connect(href);
const [s2, m2] = await connect(href);
await flush();
m1.length = 0;
m2.length = 0;
s1.send("/join meh");
s2.send("/join meh");
await flush();
assertEquals(m1, ["1 has joined!", "2 has joined!"]);
assertEquals(m2, ["2 has joined!"]);
await close(s2);
await close(s1);
}, getHandler()),
);
Deno.test(
"Messages remaining members if one left the room.",
setup(async () => {
const [s1, m1] = await connect(href);
const [s2, m2] = await connect(href);
s1.send("/join meh");
s2.send("/join meh");
await flush();
m1.length = 0;
m2.length = 0;
await close(s2);
await flush();
assertEquals(m1, ["2 has left!"]);
assertEquals(m2, []);
await close(s1);
}, getHandler()),
);
+8 -20
View File
@@ -1,9 +1,6 @@
/// <reference lib="deno.ns" /> /// <reference lib="deno.ns" />
export function startServer( export function getHandler(): Deno.ServeHandler {
hostname: string = "localhost",
port: number = 8042,
): () => Promise<void> {
let nextId = 1; let nextId = 1;
const clientsMap = new Map<number, WebSocket>(); const clientsMap = new Map<number, WebSocket>();
@@ -70,23 +67,14 @@ export function startServer(
socket.onerror = console.error; socket.onerror = console.error;
}; };
const ac = new AbortController(); return (request) => {
const server = Deno.serve( if (request.headers.get("upgrade") !== "websocket") {
{ hostname, port, signal: ac.signal }, return new Response("Upgrade Required", { status: 426 });
(request) => { }
if (request.headers.get("upgrade") !== "websocket") {
return new Response("Upgrade Required", { status: 426 });
}
const { socket, response } = Deno.upgradeWebSocket(request); const { socket, response } = Deno.upgradeWebSocket(request);
handleConnection(socket); handleConnection(socket);
return response; return response;
},
);
return () => {
ac.abort();
return server.finished;
}; };
} }
+15
View File
@@ -0,0 +1,15 @@
/// <reference lib="deno.ns" />
export function startServer(
handler: Deno.ServeHandler,
hostname: string = "localhost",
port: number = 8042,
): () => Promise<void> {
const ac = new AbortController();
const server = Deno.serve({ hostname, port, signal: ac.signal }, handler);
return () => {
ac.abort();
return server.finished;
};
}
+46
View File
@@ -0,0 +1,46 @@
import { startServer } from "./server.ts";
export const hostname = "0.0.0.0";
export const port = 8042;
export const href = `ws://${hostname}:${port}`;
export function setup(
fn: (ctx: Deno.TestContext) => Promise<void>,
handler: Deno.ServeHandler,
): (ctx: Deno.TestContext) => Promise<void> {
return async (ctx) => {
const shutdown = startServer(handler, hostname, port);
try {
await fn(ctx);
} finally {
await shutdown();
}
};
}
export function connect(url: string): Promise<[WebSocket, string[]]> {
return new Promise((resolve, reject) => {
const messages: string[] = [];
const socket = new WebSocket(url);
socket.onopen = () => resolve([socket, messages]);
socket.onerror = (error) => reject(error);
socket.onmessage = ({ data }) => {
messages.push(data);
};
});
}
export function close(socket: WebSocket): Promise<void> {
return new Promise((resolve) => {
if (socket.readyState === WebSocket.CLOSED) {
return resolve();
}
socket.onclose = () => resolve();
socket.close();
});
}
export function flush(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, 0));
}