Latest
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164import { Middleware, MiddlewareHandler } from './middleware.ts';import { Router } from './router.ts';import { Data } from './data/data.ts';
import { getContentType } from './utils/getContentType.ts';
type AddHandlers = (...handlers: MiddlewareHandler[]) => Server;
export class Server { #middleware = new Array<Middleware>(); #listeners = new Array<Middleware>(); #server: Deno.Listener | null = null
/** * Launch the server. * * @param port - The port to listen on. * @example * server.listen(); * server.listen(8080); */ async listen(port = 8080): Promise<void> { this.#server = Deno.listen({ port });
try { for await (const conn of this.#server!) (async () => { for await (const { request, respondWith } of Deno.serveHttp(conn)) { let responded = false;
this.#run(request, (response: Response) => { if (responded) return null; respondWith(response); responded = true; }); } })(); } catch { throw new Error('Something went wrong'); } }
#run(request: Request, respond: (resonse: Response) => void) { const data = new Data(request, respond); const handlers = [...this.#middleware, ...this.#listeners]; const used = new Set<number>();
function next(index = -1): () => void { if (used.has(index) || !handlers[index + 1]) return () => undefined
return async () => { if (!used.has(index)) await handlers[index + 1](data, next(index + 1)); used.add(index); next(index + 1)(); } }
next()(); }
/** * Listen for incoming requests. * @param route - The route to listen on. * @param method - The method to listen for. * @example * server.on()(({ respond }) => respond('Hello World')); * server.on('/')(({ respond }) => respond('Hello World')); * server.on('/', 'GET')(({ respond }) => respond('Hello World')); * * @returns A function that can later be used to add handlers. */ on(route = '/*', method = 'GET'): AddHandlers { /** * A function to add handlers. * * @param handlers - The handlers to add. * @example * server.on()(({ respond }) => respond('Hello World')); * server.on('/')(({ respond }) => respond('Hello World')); * server.on('/', 'GET')(({ respond }) => respond('Hello World')); */ return (...handlers: MiddlewareHandler[]) => { handlers.forEach(handler => this.#listeners.push(Middleware(method, route, handler)));
return this; }; } /** * Add middleware. * * @param route - The route to listen on. * @param method - The method to listen for. * @example * server.use()(() => console.log('Request')); * server.use('/')(() => console.log('Request')); * server.use('/', 'ANY')(() => console.log('Request')); * * @returns A function that can later be used to add handlers. */ use(route = '/*', method = 'ANY'): AddHandlers { /** * A function to add handlers. * * @param handlers - The handlers to add. * @example * server.use()(() => console.log('Request')); * server.use('/')(() => console.log('Request')); * server.use('/', 'ANY')(() => console.log('Request')); */ return (...handlers: MiddlewareHandler[] | Router[]): Server => { handlers[0] instanceof Router ? (handlers as Router[]).forEach(router => this.#middleware.push(...router.handlers())) : (handlers as MiddlewareHandler[]).forEach(handler => this.#middleware.push(Middleware(method, route, handler))); return this; } }
/** * Add a static file server. * * @param route - The route to serve on. * @param root - The path to serve files from. * @example * server.static(); * server.static('/files'); * server.static('/files', '/path/to/files'); */ static(route = '/*', root = ''): Server { route = `/${route.replace(/(\/\*)?$/, '/*')}`.replace(/\/+/g, '/'); this.use(route, 'GET')(async ({ request, respond }) => { const { path: upath } = request.url;
const base = route.split('/').filter((item) => item != '*' && item != ''); const rest = upath.replace(/^\//, '').split('/').filter((_, index) => index >= base.length); const path = `./${[root, ...rest].join('/')}`; await Deno.readFile(path) .then((content) => { respond({ body: content, headers: { 'content-type': getContentType(path) } }); }) .catch(() => respond({ status: 404 })); });
return this; }
/** * Close the server. * * @example * server.close(); */ close(): void { this.#server?.close(); }}