Last active
June 7, 2021 17:01
-
-
Save olee/1f26996c1d573c9b9938cebe2b3dabf5 to your computer and use it in GitHub Desktop.
Deno hyperbole server (workshop)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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