import type { Application, State } from "./application.ts";import { NativeRequest } from "./http_server_native_request.ts";import type { HttpConn, Listener, Server } from "./types.d.ts";import { assert, isListenTlsOptions } from "./util.ts";
declare global { interface ReadableStream<R = any> { [Symbol.asyncIterator](options?: { preventCancel?: boolean; }): AsyncIterableIterator<R>; }}
export type Respond = (r: Response | Promise<Response>) => void;
interface ReadableStreamDefaultControllerCallback<R> { (controller: ReadableStreamDefaultController<R>): void | PromiseLike<void>;}
const serveHttp: (conn: Deno.Conn) => HttpConn = "serveHttp" in Deno ? (Deno as any).serveHttp.bind(Deno) : undefined;
export class HttpServer<AS extends State = Record<string, any>> implements Server<NativeRequest> { #app: Application<AS>; #closed = false; #listener?: Deno.Listener; #httpConnections: Set<HttpConn> = new Set(); #options: Deno.ListenOptions | Deno.ListenTlsOptions;
constructor( app: Application<AS>, options: Deno.ListenOptions | Deno.ListenTlsOptions, ) { if (!("serveHttp" in Deno)) { throw new Error( "The native bindings for serving HTTP are not available.", ); } this.#app = app; this.#options = options; }
get app(): Application<AS> { return this.#app; }
get closed(): boolean { return this.#closed; }
close(): void { this.#closed = true;
if (this.#listener) { this.#listener.close(); this.#listener = undefined; }
for (const httpConn of this.#httpConnections) { try { httpConn.close(); } catch (error) { if (!(error instanceof Deno.errors.BadResource)) { throw error; } } }
this.#httpConnections.clear(); }
listen(): Listener { return (this.#listener = isListenTlsOptions(this.#options) ? Deno.listenTls(this.#options) : Deno.listen(this.#options)) as Listener; }
#trackHttpConnection(httpConn: HttpConn): void { this.#httpConnections.add(httpConn); }
#untrackHttpConnection(httpConn: HttpConn): void { this.#httpConnections.delete(httpConn); }
[Symbol.asyncIterator](): AsyncIterableIterator<NativeRequest> { const start: ReadableStreamDefaultControllerCallback<NativeRequest> = ( controller, ) => { const server = this; async function serve(conn: Deno.Conn) { const httpConn = serveHttp(conn); server.#trackHttpConnection(httpConn);
while (true) { try { const requestEvent = await httpConn.nextRequest();
if (requestEvent === null) { return; }
const nativeRequest = new NativeRequest(requestEvent, { conn }); controller.enqueue(nativeRequest); nativeRequest.donePromise.catch((error) => { server.app.dispatchEvent(new ErrorEvent("error", { error })); }); } catch (error) { server.app.dispatchEvent(new ErrorEvent("error", { error })); }
if (server.closed) { server.#untrackHttpConnection(httpConn); httpConn.close(); controller.close(); } } }
const listener = this.#listener; assert(listener); async function accept() { while (true) { try { const conn = await listener!.accept(); serve(conn); } catch (error) { if (!server.closed) { server.app.dispatchEvent(new ErrorEvent("error", { error })); } } if (server.closed) { controller.close(); return; } } }
accept(); }; const stream = new ReadableStream<NativeRequest>({ start });
return stream[Symbol.asyncIterator](); }}