Skip to content

Instantly share code, notes, and snippets.

@olee
Last active June 7, 2021 17:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save olee/1f26996c1d573c9b9938cebe2b3dabf5 to your computer and use it in GitHub Desktop.
Save olee/1f26996c1d573c9b9938cebe2b3dabf5 to your computer and use it in GitHub Desktop.
Deno hyperbole server (workshop)
export interface HyperboleRequest {
url: URL;
body?: unknown;
method: string;
pathname: string;
}
async function hyperboleRequest(request: Request): Promise<HyperboleRequest> {
const decoder = new TextDecoder();
const url = new URL(request.url);
let body;
if (request.method !== "GET") {
const raw = await request.body?.getReader().read();
body = decoder.decode(raw?.value);
try {
body = JSON.parse(body);
} catch (_e) {
//ignore
}
}
return {
url,
body,
method: request.method,
pathname: url.pathname,
};
}
export type HyperboleNext = () => Promise<unknown>;
export class HyperboleResponse {
public status = 200;
public isHandled = false;
constructor(
private respondWith: Deno.RequestEvent['respondWith']
) {
}
public send(body?: BodyInit, status?: number, headers?: HeadersInit) {
this.status = status ?? 200;
this.respondWith(new Response(body, {
status: this.status,
headers,
}));
this.isHandled = true;
}
public json(data: unknown, status?: number) {
this.send(JSON.stringify(data), status, {
'Content-Type': 'application/json',
});
}
}
export type HyperboleHandlerFunction = (req: HyperboleRequest, res: HyperboleResponse, next: HyperboleNext) => void | Promise<void>;
export interface HyperboleHandler {
path: string;
handle: HyperboleHandlerFunction;
}
export default class HyperboleServer {
private handlers: HyperboleHandler[] = [];
public all(path: string, handle: HyperboleHandlerFunction) {
this.handlers.push({ path, handle });
return this;
}
public use(handle: HyperboleHandlerFunction) {
this.all('*', handle);
return this;
}
private async handleHttp(conn: Deno.Conn) {
for await (const { request, respondWith } of Deno.serveHttp(conn)) {
const req = await hyperboleRequest(request);
const res = new HyperboleResponse(respondWith);
this.handleRequest(req, res);
}
}
private async handleRequest(req: HyperboleRequest, res: HyperboleResponse) {
const handlers = this.handlers.filter(rh => rh.path === '*' || rh.path === req.url.pathname);
try {
await this.handleNext(req, res, handlers);
} catch (error) {
console.log(error);
res.send('Error:\r\n' + JSON.stringify(error, undefined, 2), 500);
}
if (!res.isHandled) {
res.send('404', 404);
}
}
private async handleNext(req: HyperboleRequest, res: HyperboleResponse, handlers: HyperboleHandler[]) {
if (res.isHandled || handlers.length === 0) {
return;
}
const [handler, ...rest] = handlers;
await handler.handle(req, res, () => this.handleNext(req, res, rest));
}
private async waitForConnection(port: number) {
const listener = Deno.listen({ port });
for await (const c of listener) {
this.handleHttp(c);
}
}
public listen(port: number) {
this.waitForConnection(port);
return this;
}
}
console.log('Starting server');
const _testServer = new HyperboleServer()
.use(async (_req, _res, next) => {
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log(`Request to ${_req.pathname} took ${duration} ms`);
})
.all('/ping', (_req, res, _next) => {
res.send('pong');
})
.all('/', (_req, res, _next) => {
res.send('Hello World!');
})
.use((_req, res, _next) => {
res.send('Hello catchall');
})
.listen(3000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment