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
+5 -98
View File
@@ -1,107 +1,14 @@
/// <reference lib="deno.ns" />
import { assertEquals } from "jsr:@std/assert";
import { startServer } from "./deno.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));
}
import handler from "./handler.ts";
import { close, connect, flush, href, setup } from "./testSetup.ts";
Deno.test(
"Welcome all clients on connect.",
"test",
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);
assertEquals(m1, []);
await close(s1);
}),
);
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);
}),
}, handler),
);
+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" />
export function startServer(
hostname: string = "localhost",
port: number = 8042,
): () => Promise<void> {
export function getHandler(): Deno.ServeHandler {
let nextId = 1;
const clientsMap = new Map<number, WebSocket>();
@@ -70,23 +67,14 @@ export function startServer(
socket.onerror = console.error;
};
const ac = new AbortController();
const server = Deno.serve(
{ hostname, port, signal: ac.signal },
(request) => {
if (request.headers.get("upgrade") !== "websocket") {
return new Response("Upgrade Required", { status: 426 });
}
return (request) => {
if (request.headers.get("upgrade") !== "websocket") {
return new Response("Upgrade Required", { status: 426 });
}
const { socket, response } = Deno.upgradeWebSocket(request);
handleConnection(socket);
const { socket, response } = Deno.upgradeWebSocket(request);
handleConnection(socket);
return response;
},
);
return () => {
ac.abort();
return server.finished;
return response;
};
}
+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));
}