Refactor server stuff
This commit is contained in:
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function assertNever(): never {
|
||||||
|
throw new Error("never");
|
||||||
|
}
|
||||||
+5
-98
@@ -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);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
@@ -1,3 +1,4 @@
|
|||||||
import { startServer } from "./deno.ts";
|
import handler from "./handler.ts";
|
||||||
|
import { startServer } from "./server.ts";
|
||||||
|
|
||||||
startServer();
|
startServer(handler);
|
||||||
|
|||||||
@@ -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()),
|
||||||
|
);
|
||||||
@@ -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;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user