Module

std/http/server_legacy.ts

Deno standard library
Go to Latest
File
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.import { BufReader, BufWriter } from "../io/buffer.ts";import { assert } from "../_util/assert.ts";import { Deferred, deferred, MuxAsyncIterator } from "../async/mod.ts";import { bodyReader, chunkedBodyReader, emptyReader, readRequest, writeResponse,} from "./_io.ts";import { _parseAddrFromStr } from "./server.ts";
/** * @deprecated */export class ServerRequest { url!: string; method!: string; proto!: string; protoMinor!: number; protoMajor!: number; headers!: Headers; conn!: Deno.Conn; r!: BufReader; w!: BufWriter;
#done: Deferred<Error | undefined> = deferred(); #contentLength?: number | null = undefined; #body?: Deno.Reader = undefined; #finalized = false;
get done(): Promise<Error | undefined> { return this.#done.then((e) => e); }
/** * Value of Content-Length header. * If null, then content length is invalid or not given (e.g. chunked encoding). */ get contentLength(): number | null { // undefined means not cached. // null means invalid or not provided. if (this.#contentLength === undefined) { const cl = this.headers.get("content-length"); if (cl) { this.#contentLength = parseInt(cl); // Convert NaN to null (as NaN harder to test) if (Number.isNaN(this.#contentLength)) { this.#contentLength = null; } } else { this.#contentLength = null; } } return this.#contentLength; }
/** * Body of the request. The easiest way to consume the body is: * * ```ts * import { ServerRequest } from "https://deno.land/std@$STD_VERSION/http/server_legacy.ts"; * import { readAll } from "https://deno.land/std@$STD_VERSION/streams/conversion.ts"; * * const req = new ServerRequest(); * const buf = await readAll(req.body); * ``` */ 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 { // Neither content-length nor transfer-encoding: chunked this.#body = emptyReader(); } } } return this.#body; }
async respond(r: Response) { let err: Error | undefined; try { // Write our response! await writeResponse(this.w, r); } catch (e) { try { // Eagerly close on error. this.conn.close(); } catch { // Pass } if (e instanceof Error) { err = e; } else { err = new Error("non-error thrown"); } } // Signal that this request has been processed and the next pipelined // request on the same connection can be accepted. this.#done.resolve(err); if (err) { // Error during responding, rethrow. throw err; } }
async finalize() { if (this.#finalized) return; // Consume unread body const body = this.body; const buf = new Uint8Array(1024); while ((await body.read(buf)) !== null) { // Pass } this.#finalized = true; }}
/** * @deprecated */export class Server implements AsyncIterable<ServerRequest> { #closing = false; #connections: Deno.Conn[] = [];
constructor(public listener: Deno.Listener) {}
close(): void { this.#closing = true; this.listener.close(); for (const conn of this.#connections) { try { conn.close(); } catch (e) { // Connection might have been already closed if (!(e instanceof Deno.errors.BadResource)) { throw e; } } } }
// Yields all HTTP requests on a single TCP connection. private async *iterateHttpRequests( conn: Deno.Conn, ): AsyncIterableIterator<ServerRequest> { const reader = new BufReader(conn); const writer = new BufWriter(conn);
while (!this.#closing) { let request: ServerRequest | null; try { request = await readRequest(conn, reader); } catch (error) { if ( error instanceof Deno.errors.InvalidData || error instanceof Deno.errors.UnexpectedEof ) { // An error was thrown while parsing request headers. // Try to send the "400 Bad Request" before closing the connection. try { await writeResponse(writer, { status: 400, body: new TextEncoder().encode(`${error.message}\r\n\r\n`), }); } catch { // The connection is broken. } } break; } if (request === null) { break; }
request.w = writer; yield request;
// Wait for the request to be processed before we accept a new request on // this connection. const responseError = await request.done; if (responseError) { // Something bad happened during response. // (likely other side closed during pipelined req) // req.done implies this connection already closed, so we can just return. this.untrackConnection(request.conn); return; }
try { // Consume unread body and trailers if receiver didn't consume those data await request.finalize(); } catch { // Invalid data was received or the connection was closed. break; } }
this.untrackConnection(conn); try { conn.close(); } catch { // might have been already closed } }
private trackConnection(conn: Deno.Conn): void { this.#connections.push(conn); }
private untrackConnection(conn: Deno.Conn): void { const index = this.#connections.indexOf(conn); if (index !== -1) { this.#connections.splice(index, 1); } }
// Accepts a new TCP connection and yields all HTTP requests that arrive on // it. When a connection is accepted, it also creates a new iterator of the // same kind and adds it to the request multiplexer so that another TCP // connection can be accepted. private async *acceptConnAndIterateHttpRequests( mux: MuxAsyncIterator<ServerRequest>, ): AsyncIterableIterator<ServerRequest> { if (this.#closing) return; // Wait for a new connection. let conn: Deno.Conn; try { conn = await this.listener.accept(); } catch (error) { if ( // The listener is closed: error instanceof Deno.errors.BadResource || // TLS handshake errors: error instanceof Deno.errors.InvalidData || error instanceof Deno.errors.UnexpectedEof || error instanceof Deno.errors.ConnectionReset || error instanceof Deno.errors.NotConnected ) { return mux.add(this.acceptConnAndIterateHttpRequests(mux)); } throw error; } this.trackConnection(conn); // Try to accept another connection and add it to the multiplexer. mux.add(this.acceptConnAndIterateHttpRequests(mux)); // Yield the requests that arrive on the just-accepted connection. yield* this.iterateHttpRequests(conn); }
[Symbol.asyncIterator](): AsyncIterableIterator<ServerRequest> { const mux: MuxAsyncIterator<ServerRequest> = new MuxAsyncIterator(); mux.add(this.acceptConnAndIterateHttpRequests(mux)); return mux.iterate(); }}
/** * @deprecated * * Options for creating an HTTP server. */export type HTTPOptions = Omit<Deno.ListenOptions, "transport">;
/** * @deprecated * * Create an HTTP server. * * ```ts * import { serve } from "https://deno.land/std@$STD_VERSION/http/server_legacy.ts"; * * const body = "Hello World\n"; * const server = serve({ port: 8000 }); * for await (const req of server) { * req.respond({ body }); * } * ``` */export function serve(addr: string | HTTPOptions): Server { if (typeof addr === "string") { addr = _parseAddrFromStr(addr); }
const listener = Deno.listen(addr); return new Server(listener);}
/** * @deprecated * * Start an HTTP server with given options and request handler. * * ```ts * import { listenAndServe, ServerRequest } from "https://deno.land/std@$STD_VERSION/http/server_legacy.ts"; * * const body = "Hello World\n"; * const options = { port: 8000 }; * listenAndServe(options, (req) => { * req.respond({ body }); * }); * ``` * * @param options Server configuration. * @param handler Request handler. */export async function listenAndServe( addr: string | HTTPOptions, handler: (req: ServerRequest) => void,) { const server = serve(addr);
for await (const request of server) { handler(request); }}
/** * Options for creating an HTTPS server. * * @deprecated */export type HTTPSOptions = Omit<Deno.ListenTlsOptions, "transport">;
/** * @deprecated * * Create an HTTPS server with given options. * * ```ts * import { serveTLS } from "https://deno.land/std@$STD_VERSION/http/server_legacy.ts"; * * const body = "Hello HTTPS"; * const options = { * hostname: "localhost", * port: 443, * certFile: "./path/to/localhost.crt", * keyFile: "./path/to/localhost.key", * }; * for await (const req of serveTLS(options)) { * req.respond({ body }); * } * ``` * * @param options Server configuration. * @return Async iterable server instance for incoming requests. */export function serveTLS(options: HTTPSOptions): Server { const tlsOptions: Deno.ListenTlsOptions = { ...options, transport: "tcp", }; const listener = Deno.listenTls(tlsOptions); return new Server(listener);}
/** * @deprecated * * Start an HTTPS server with given options and request handler. * * ```ts * import { listenAndServeTLS } from "https://deno.land/std@$STD_VERSION/http/server_legacy.ts"; * * const body = "Hello HTTPS"; * const options = { * hostname: "localhost", * port: 443, * certFile: "./path/to/localhost.crt", * keyFile: "./path/to/localhost.key", * }; * listenAndServeTLS(options, (req) => { * req.respond({ body }); * }); * ``` * * @param options Server configuration. * @param handler Request handler. */export async function listenAndServeTLS( options: HTTPSOptions, handler: (req: ServerRequest) => void,) { const server = serveTLS(options);
for await (const request of server) { handler(request); }}
/** * @deprecated * * Interface of HTTP server response. * If body is a Reader, response would be chunked. * If body is a string, it would be UTF-8 encoded by default. */export interface Response { status?: number; statusText?: string; headers?: Headers; body?: Uint8Array | Deno.Reader | string; trailers?: () => Promise<Headers> | Headers;}