import { BufReader, BufWriter } from "../io/bufio.ts";import { assert } from "../testing/asserts.ts";import { deferred, Deferred, MuxAsyncIterator } from "../util/async.ts";import { bodyReader, chunkedBodyReader, emptyReader, writeResponse, readRequest,} from "./io.ts";import Listener = Deno.Listener;import Conn = Deno.Conn;import Reader = Deno.Reader;const { listen, listenTLS } = Deno;
export class ServerRequest { url!: string; method!: string; proto!: string; protoMinor!: number; protoMajor!: number; headers!: Headers; conn!: Conn; r!: BufReader; w!: BufWriter; done: Deferred<Error | undefined> = deferred();
private _contentLength: number | undefined | null = undefined; get contentLength(): number | null { if (this._contentLength === undefined) { const cl = this.headers.get("content-length"); if (cl) { this._contentLength = parseInt(cl); if (Number.isNaN(this._contentLength)) { this._contentLength = null; } } else { this._contentLength = null; } } return this._contentLength; }
private _body: Deno.Reader | null = null;
get body(): Deno.Reader { if (!this._body) { if (this.contentLength != null) { this._body = bodyReader(this.contentLength, this.r); } else { const transferEncoding = this.headers.get("transfer-encoding"); if (transferEncoding != null) { const parts = transferEncoding .split(",") .map((e): string => e.trim().toLowerCase()); assert( parts.includes("chunked"), 'transfer-encoding must include "chunked" if content-length is not set' ); this._body = chunkedBodyReader(this.headers, this.r); } else { this._body = emptyReader(); } } } return this._body; }
async respond(r: Response): Promise<void> { let err: Error | undefined; try { await writeResponse(this.w, r); } catch (e) { try { this.conn.close(); } catch {} err = e; } this.done.resolve(err); if (err) { throw err; } }
private finalized = false; async finalize(): Promise<void> { if (this.finalized) return; const body = this.body; const buf = new Uint8Array(1024); while ((await body.read(buf)) !== Deno.EOF) {} this.finalized = true; }}
export class Server implements AsyncIterable<ServerRequest> { private closing = false; private connections: Conn[] = [];
constructor(public listener: Listener) {}
close(): void { this.closing = true; this.listener.close(); for (const conn of this.connections) { try { conn.close(); } catch (e) { if (!(e instanceof Deno.errors.BadResource)) { throw e; } } } }
private async *iterateHttpRequests( conn: Conn ): AsyncIterableIterator<ServerRequest> { const bufr = new BufReader(conn); const w = new BufWriter(conn); let req: ServerRequest | Deno.EOF = Deno.EOF; let err: Error | undefined;
while (!this.closing) { try { req = await readRequest(conn, bufr); } catch (e) { err = e; } if (err != null || req === Deno.EOF) { break; }
req.w = w; yield req;
const procError = await req.done; if (procError) { this.untrackConnection(req.conn); return; } await req.finalize(); }
this.untrackConnection(conn); try { conn.close(); } catch (e) { } }
private trackConnection(conn: Conn): void { this.connections.push(conn); }
private untrackConnection(conn: Conn): void { const index = this.connections.indexOf(conn); if (index !== -1) { this.connections.splice(index, 1); } }
private async *acceptConnAndIterateHttpRequests( mux: MuxAsyncIterator<ServerRequest> ): AsyncIterableIterator<ServerRequest> { if (this.closing) return; let conn: Conn; try { conn = await this.listener.accept(); } catch (error) { if (error instanceof Deno.errors.BadResource) { return; } throw error; } this.trackConnection(conn); mux.add(this.acceptConnAndIterateHttpRequests(mux)); yield* this.iterateHttpRequests(conn); }
[Symbol.asyncIterator](): AsyncIterableIterator<ServerRequest> { const mux: MuxAsyncIterator<ServerRequest> = new MuxAsyncIterator(); mux.add(this.acceptConnAndIterateHttpRequests(mux)); return mux.iterate(); }}
export type HTTPOptions = Omit<Deno.ListenOptions, "transport">;
export function serve(addr: string | HTTPOptions): Server { if (typeof addr === "string") { const [hostname, port] = addr.split(":"); addr = { hostname, port: Number(port) }; }
const listener = listen(addr); return new Server(listener);}
export async function listenAndServe( addr: string | HTTPOptions, handler: (req: ServerRequest) => void): Promise<void> { const server = serve(addr);
for await (const request of server) { handler(request); }}
export type HTTPSOptions = Omit<Deno.ListenTLSOptions, "transport">;
export function serveTLS(options: HTTPSOptions): Server { const tlsOptions: Deno.ListenTLSOptions = { ...options, transport: "tcp", }; const listener = listenTLS(tlsOptions); return new Server(listener);}
export async function listenAndServeTLS( options: HTTPSOptions, handler: (req: ServerRequest) => void): Promise<void> { const server = serveTLS(options);
for await (const request of server) { handler(request); }}
export interface Response { status?: number; headers?: Headers; body?: Uint8Array | Reader | string; trailers?: () => Promise<Headers> | Headers;}