Started to implement server
This commit is contained in:
1 parent
1f2ea8b7e5
commit
17a1b4bed3
177 files changed
+29894
-74
No files matched your search
+699
@@ -0,0 +1,699 @@
|
||||
// Copyright 2018-2022 the oak authors. All rights reserved. MIT license.
|
||||
|
||||
import { Context } from "./context.ts";
|
||||
import { KeyStack, Status, STATUS_TEXT } from "./deps.ts";
|
||||
import { FlashServer } from "./http_server_flash.ts";
|
||||
import { HttpServer } from "./http_server_native.ts";
|
||||
import { NativeRequest } from "./http_server_native_request.ts";
|
||||
import { compose, Middleware } from "./middleware.ts";
|
||||
import { cloneState } from "./structured_clone.ts";
|
||||
import {
|
||||
Key,
|
||||
Listener,
|
||||
Server,
|
||||
ServerConstructor,
|
||||
ServerRequest,
|
||||
} from "./types.d.ts";
|
||||
import { assert, isConn } from "./util.ts";
|
||||
|
||||
export interface ListenOptionsBase {
|
||||
/** The port to listen on. If not specified, defaults to `0`, which allows the
|
||||
* operating system to determine the value. */
|
||||
port?: number;
|
||||
/** A literal IP address or host name that can be resolved to an IP address.
|
||||
* If not specified, defaults to `0.0.0.0`.
|
||||
*
|
||||
* __Note about `0.0.0.0`__ While listening `0.0.0.0` works on all platforms,
|
||||
* the browsers on Windows don't work with the address `0.0.0.0`.
|
||||
* You should show the message like `server running on localhost:8080` instead of
|
||||
* `server running on 0.0.0.0:8080` if your program supports Windows. */
|
||||
hostname?: string;
|
||||
secure?: false;
|
||||
/** An optional abort signal which can be used to close the listener. */
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface ListenOptionsTls extends Deno.ListenTlsOptions {
|
||||
/** Application-Layer Protocol Negotiation (ALPN) protocols to announce to
|
||||
* the client. If not specified, no ALPN extension will be included in the
|
||||
* TLS handshake.
|
||||
*
|
||||
* **NOTE** this is part of the native HTTP server in Deno 1.9 or later,
|
||||
* which requires the `--unstable` flag to be available.
|
||||
*/
|
||||
alpnProtocols?: string[];
|
||||
secure: true;
|
||||
/** An optional abort signal which can be used to close the listener. */
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface HandleMethod {
|
||||
/** Handle an individual server request, returning the server response. This
|
||||
* is similar to `.listen()`, but opening the connection and retrieving
|
||||
* requests are not the responsibility of the application. If the generated
|
||||
* context gets set to not to respond, then the method resolves with
|
||||
* `undefined`, otherwise it resolves with a DOM `Response` object. */
|
||||
(
|
||||
request: Request,
|
||||
conn?: Deno.Conn,
|
||||
secure?: boolean,
|
||||
): Promise<Response | undefined>;
|
||||
}
|
||||
|
||||
export type ListenOptions = ListenOptionsTls | ListenOptionsBase;
|
||||
|
||||
interface ApplicationErrorEventListener<S extends AS, AS extends State> {
|
||||
(evt: ApplicationErrorEvent<S, AS>): void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ApplicationErrorEventListenerObject<S extends AS, AS extends State> {
|
||||
handleEvent(evt: ApplicationErrorEvent<S, AS>): void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ApplicationErrorEventInit<S extends AS, AS extends State>
|
||||
extends ErrorEventInit {
|
||||
context?: Context<S, AS>;
|
||||
}
|
||||
|
||||
type ApplicationErrorEventListenerOrEventListenerObject<
|
||||
S extends AS,
|
||||
AS extends State,
|
||||
> =
|
||||
| ApplicationErrorEventListener<S, AS>
|
||||
| ApplicationErrorEventListenerObject<S, AS>;
|
||||
|
||||
interface ApplicationListenEventListener {
|
||||
(evt: ApplicationListenEvent): void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ApplicationListenEventListenerObject {
|
||||
handleEvent(evt: ApplicationListenEvent): void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ApplicationListenEventInit extends EventInit {
|
||||
hostname: string;
|
||||
listener: Listener;
|
||||
port: number;
|
||||
secure: boolean;
|
||||
serverType: "native" | "flash" | "custom";
|
||||
}
|
||||
|
||||
type ApplicationListenEventListenerOrEventListenerObject =
|
||||
| ApplicationListenEventListener
|
||||
| ApplicationListenEventListenerObject;
|
||||
|
||||
/** Available options that are used when creating a new instance of
|
||||
* {@linkcode Application}. */
|
||||
export interface ApplicationOptions<S extends State, R extends ServerRequest> {
|
||||
/** Determine how when creating a new context, the state from the application
|
||||
* should be applied. A value of `"clone"` will set the state as a clone of
|
||||
* the app state. Any non-cloneable or non-enumerable properties will not be
|
||||
* copied. A value of `"prototype"` means that the application's state will be
|
||||
* used as the prototype of the the context's state, meaning shallow
|
||||
* properties on the context's state will not be reflected in the
|
||||
* application's state. A value of `"alias"` means that application's `.state`
|
||||
* and the context's `.state` will be a reference to the same object. A value
|
||||
* of `"empty"` will initialize the context's `.state` with an empty object.
|
||||
*
|
||||
* The default value is `"clone"`.
|
||||
*/
|
||||
contextState?: "clone" | "prototype" | "alias" | "empty";
|
||||
|
||||
/** An optional replacer function to be used when serializing a JSON
|
||||
* response. The replacer will be used with `JSON.stringify()` to encode any
|
||||
* response bodies that need to be converted before sending the response.
|
||||
*
|
||||
* This is intended to allow responses to contain bigints and circular
|
||||
* references and encoding other values which JSON does not support directly.
|
||||
*
|
||||
* This can be used in conjunction with `jsonBodyReviver` to handle decoding
|
||||
* of request bodies if the same semantics are used for client requests.
|
||||
*
|
||||
* If more detailed or conditional usage is required, then serialization
|
||||
* should be implemented directly in middleware. */
|
||||
jsonBodyReplacer?: (
|
||||
key: string,
|
||||
value: unknown,
|
||||
context: Context<S>,
|
||||
) => unknown;
|
||||
|
||||
/** An optional reviver function to be used when parsing a JSON request. The
|
||||
* reviver will be used with `JSON.parse()` to decode any response bodies that
|
||||
* are being converted as JSON.
|
||||
*
|
||||
* This is intended to allow requests to deserialize to bigints, circular
|
||||
* references, or other values which JSON does not support directly.
|
||||
*
|
||||
* This can be used in conjunction with `jsonBodyReplacer` to handle decoding
|
||||
* of response bodies if the same semantics are used for responses.
|
||||
*
|
||||
* If more detailed or conditional usage is required, then deserialization
|
||||
* should be implemented directly in the middleware.
|
||||
*/
|
||||
jsonBodyReviver?: (
|
||||
key: string,
|
||||
value: unknown,
|
||||
context: Context<S>,
|
||||
) => unknown;
|
||||
|
||||
/** An initial set of keys (or instance of {@linkcode KeyStack}) to be used for signing
|
||||
* cookies produced by the application. */
|
||||
keys?: KeyStack | Key[];
|
||||
|
||||
/** If `true`, any errors handled by the application will be logged to the
|
||||
* stderr. If `false` nothing will be logged. The default is `true`.
|
||||
*
|
||||
* All errors are available as events on the application of type `"error"` and
|
||||
* can be accessed for custom logging/application management via adding an
|
||||
* event listener to the application:
|
||||
*
|
||||
* ```ts
|
||||
* const app = new Application({ logErrors: false });
|
||||
* app.addEventListener("error", (evt) => {
|
||||
* // evt.error will contain what error was thrown
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
logErrors?: boolean;
|
||||
|
||||
/** If set to `true`, proxy headers will be trusted when processing requests.
|
||||
* This defaults to `false`. */
|
||||
proxy?: boolean;
|
||||
|
||||
/** A server constructor to use instead of the default server for receiving
|
||||
* requests.
|
||||
*
|
||||
* Generally this is only used for testing. */
|
||||
serverConstructor?: ServerConstructor<R>;
|
||||
|
||||
/** The initial state object for the application, of which the type can be
|
||||
* used to infer the type of the state for both the application and any of the
|
||||
* application's context. */
|
||||
state?: S;
|
||||
}
|
||||
|
||||
interface RequestState {
|
||||
handling: Set<Promise<void>>;
|
||||
closing: boolean;
|
||||
closed: boolean;
|
||||
server: Server<ServerRequest>;
|
||||
}
|
||||
|
||||
// deno-lint-ignore no-explicit-any
|
||||
export type State = Record<string | number | symbol, any>;
|
||||
|
||||
const ADDR_REGEXP = /^\[?([^\]]*)\]?:([0-9]{1,5})$/;
|
||||
|
||||
const DEFAULT_SERVER: ServerConstructor<ServerRequest> = HttpServer;
|
||||
|
||||
export class ApplicationErrorEvent<S extends AS, AS extends State>
|
||||
extends ErrorEvent {
|
||||
context?: Context<S, AS>;
|
||||
|
||||
constructor(eventInitDict: ApplicationErrorEventInit<S, AS>) {
|
||||
super("error", eventInitDict);
|
||||
this.context = eventInitDict.context;
|
||||
}
|
||||
}
|
||||
|
||||
function logErrorListener<S extends AS, AS extends State>(
|
||||
{ error, context }: ApplicationErrorEvent<S, AS>,
|
||||
) {
|
||||
if (error instanceof Error) {
|
||||
console.error(
|
||||
`[uncaught application error]: ${error.name} - ${error.message}`,
|
||||
);
|
||||
} else {
|
||||
console.error(`[uncaught application error]\n`, error);
|
||||
}
|
||||
if (context) {
|
||||
let url: string;
|
||||
try {
|
||||
url = context.request.url.toString();
|
||||
} catch {
|
||||
url = "[malformed url]";
|
||||
}
|
||||
console.error(`\nrequest:`, {
|
||||
url,
|
||||
method: context.request.method,
|
||||
hasBody: context.request.hasBody,
|
||||
});
|
||||
console.error(`response:`, {
|
||||
status: context.response.status,
|
||||
type: context.response.type,
|
||||
hasBody: !!context.response.body,
|
||||
writable: context.response.writable,
|
||||
});
|
||||
}
|
||||
if (error instanceof Error && error.stack) {
|
||||
console.error(`\n${error.stack.split("\n").slice(1).join("\n")}`);
|
||||
}
|
||||
}
|
||||
|
||||
export class ApplicationListenEvent extends Event {
|
||||
hostname: string;
|
||||
listener: Listener;
|
||||
port: number;
|
||||
secure: boolean;
|
||||
serverType: "native" | "flash" | "custom";
|
||||
|
||||
constructor(eventInitDict: ApplicationListenEventInit) {
|
||||
super("listen", eventInitDict);
|
||||
this.hostname = eventInitDict.hostname;
|
||||
this.listener = eventInitDict.listener;
|
||||
this.port = eventInitDict.port;
|
||||
this.secure = eventInitDict.secure;
|
||||
this.serverType = eventInitDict.serverType;
|
||||
}
|
||||
}
|
||||
|
||||
/** A class which registers middleware (via `.use()`) and then processes
|
||||
* inbound requests against that middleware (via `.listen()`).
|
||||
*
|
||||
* The `context.state` can be typed via passing a generic argument when
|
||||
* constructing an instance of `Application`. It can also be inferred by setting
|
||||
* the {@linkcode ApplicationOptions.state} option when constructing the
|
||||
* application.
|
||||
*
|
||||
* ### Basic example
|
||||
*
|
||||
* ```ts
|
||||
* import { Application } from "https://deno.land/x/oak/mod.ts";
|
||||
*
|
||||
* const app = new Application();
|
||||
*
|
||||
* app.use((ctx, next) => {
|
||||
* // called on each request with the context (`ctx`) of the request,
|
||||
* // response, and other data.
|
||||
* // `next()` is use to modify the flow control of the middleware stack.
|
||||
* });
|
||||
*
|
||||
* app.listen({ port: 8080 });
|
||||
* ```
|
||||
*
|
||||
* @template AS the type of the application state which extends
|
||||
* {@linkcode State} and defaults to a simple string record.
|
||||
*/
|
||||
// deno-lint-ignore no-explicit-any
|
||||
export class Application<AS extends State = Record<string, any>>
|
||||
extends EventTarget {
|
||||
#composedMiddleware?: (context: Context<AS, AS>) => Promise<unknown>;
|
||||
#contextOptions: Pick<
|
||||
ApplicationOptions<AS, ServerRequest>,
|
||||
"jsonBodyReplacer" | "jsonBodyReviver"
|
||||
>;
|
||||
#contextState: "clone" | "prototype" | "alias" | "empty";
|
||||
#keys?: KeyStack;
|
||||
#middleware: Middleware<State, Context<State, AS>>[] = [];
|
||||
#serverConstructor: ServerConstructor<ServerRequest>;
|
||||
|
||||
/** A set of keys, or an instance of `KeyStack` which will be used to sign
|
||||
* cookies read and set by the application to avoid tampering with the
|
||||
* cookies. */
|
||||
get keys(): KeyStack | Key[] | undefined {
|
||||
return this.#keys;
|
||||
}
|
||||
|
||||
set keys(keys: KeyStack | Key[] | undefined) {
|
||||
if (!keys) {
|
||||
this.#keys = undefined;
|
||||
return;
|
||||
} else if (Array.isArray(keys)) {
|
||||
this.#keys = new KeyStack(keys);
|
||||
} else {
|
||||
this.#keys = keys;
|
||||
}
|
||||
}
|
||||
|
||||
/** If `true`, proxy headers will be trusted when processing requests. This
|
||||
* defaults to `false`. */
|
||||
proxy: boolean;
|
||||
|
||||
/** Generic state of the application, which can be specified by passing the
|
||||
* generic argument when constructing:
|
||||
*
|
||||
* const app = new Application<{ foo: string }>();
|
||||
*
|
||||
* Or can be contextually inferred based on setting an initial state object:
|
||||
*
|
||||
* const app = new Application({ state: { foo: "bar" } });
|
||||
*
|
||||
* When a new context is created, the application's state is cloned and the
|
||||
* state is unique to that request/response. Changes can be made to the
|
||||
* application state that will be shared with all contexts.
|
||||
*/
|
||||
state: AS;
|
||||
|
||||
constructor(options: ApplicationOptions<AS, ServerRequest> = {}) {
|
||||
super();
|
||||
const {
|
||||
state,
|
||||
keys,
|
||||
proxy,
|
||||
serverConstructor = DEFAULT_SERVER,
|
||||
contextState = "clone",
|
||||
logErrors = true,
|
||||
...contextOptions
|
||||
} = options;
|
||||
|
||||
this.proxy = proxy ?? false;
|
||||
this.keys = keys;
|
||||
this.state = state ?? {} as AS;
|
||||
this.#serverConstructor = serverConstructor;
|
||||
this.#contextOptions = contextOptions;
|
||||
this.#contextState = contextState;
|
||||
|
||||
if (logErrors) {
|
||||
this.addEventListener("error", logErrorListener);
|
||||
}
|
||||
}
|
||||
|
||||
#getComposed(): (context: Context<AS, AS>) => Promise<unknown> {
|
||||
if (!this.#composedMiddleware) {
|
||||
this.#composedMiddleware = compose(this.#middleware);
|
||||
}
|
||||
return this.#composedMiddleware;
|
||||
}
|
||||
|
||||
#getContextState(): AS {
|
||||
switch (this.#contextState) {
|
||||
case "alias":
|
||||
return this.state;
|
||||
case "clone":
|
||||
return cloneState(this.state);
|
||||
case "empty":
|
||||
return {} as AS;
|
||||
case "prototype":
|
||||
return Object.create(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
/** Deal with uncaught errors in either the middleware or sending the
|
||||
* response. */
|
||||
// deno-lint-ignore no-explicit-any
|
||||
#handleError(context: Context<AS>, error: any): void {
|
||||
if (!(error instanceof Error)) {
|
||||
error = new Error(`non-error thrown: ${JSON.stringify(error)}`);
|
||||
}
|
||||
const { message } = error;
|
||||
this.dispatchEvent(new ApplicationErrorEvent({ context, message, error }));
|
||||
if (!context.response.writable) {
|
||||
return;
|
||||
}
|
||||
for (const key of [...context.response.headers.keys()]) {
|
||||
context.response.headers.delete(key);
|
||||
}
|
||||
if (error.headers && error.headers instanceof Headers) {
|
||||
for (const [key, value] of error.headers) {
|
||||
context.response.headers.set(key, value);
|
||||
}
|
||||
}
|
||||
context.response.type = "text";
|
||||
const status: Status = context.response.status =
|
||||
Deno.errors && error instanceof Deno.errors.NotFound
|
||||
? 404
|
||||
: error.status && typeof error.status === "number"
|
||||
? error.status
|
||||
: 500;
|
||||
context.response.body = error.expose ? error.message : STATUS_TEXT[status];
|
||||
}
|
||||
|
||||
/** Processing registered middleware on each request. */
|
||||
async #handleRequest(
|
||||
request: ServerRequest,
|
||||
secure: boolean,
|
||||
state: RequestState,
|
||||
): Promise<void> {
|
||||
let context: Context<AS, AS> | undefined;
|
||||
try {
|
||||
context = new Context(
|
||||
this,
|
||||
request,
|
||||
this.#getContextState(),
|
||||
{ secure, ...this.#contextOptions },
|
||||
);
|
||||
} catch (e) {
|
||||
const error = e instanceof Error
|
||||
? e
|
||||
: new Error(`non-error thrown: ${JSON.stringify(e)}`);
|
||||
const { message } = error;
|
||||
this.dispatchEvent(new ApplicationErrorEvent({ message, error }));
|
||||
return;
|
||||
}
|
||||
assert(context, "Context was not created.");
|
||||
let resolve: () => void;
|
||||
const handlingPromise = new Promise<void>((res) => resolve = res);
|
||||
state.handling.add(handlingPromise);
|
||||
if (!state.closing && !state.closed) {
|
||||
try {
|
||||
await this.#getComposed()(context);
|
||||
} catch (err) {
|
||||
this.#handleError(context, err);
|
||||
}
|
||||
}
|
||||
if (context.respond === false) {
|
||||
context.response.destroy();
|
||||
resolve!();
|
||||
state.handling.delete(handlingPromise);
|
||||
return;
|
||||
}
|
||||
let closeResources = true;
|
||||
let response: Response;
|
||||
try {
|
||||
closeResources = false;
|
||||
response = await context.response.toDomResponse();
|
||||
} catch (err) {
|
||||
this.#handleError(context, err);
|
||||
response = await context.response.toDomResponse();
|
||||
}
|
||||
assert(response);
|
||||
try {
|
||||
await request.respond(response);
|
||||
} catch (err) {
|
||||
this.#handleError(context, err);
|
||||
} finally {
|
||||
context.response.destroy(closeResources);
|
||||
resolve!();
|
||||
state.handling.delete(handlingPromise);
|
||||
if (state.closing) {
|
||||
await state.server.close();
|
||||
state.closed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Add an event listener for an `"error"` event which occurs when an
|
||||
* un-caught error occurs when processing the middleware or during processing
|
||||
* of the response. */
|
||||
addEventListener<S extends AS>(
|
||||
type: "error",
|
||||
listener: ApplicationErrorEventListenerOrEventListenerObject<S, AS> | null,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
/** Add an event listener for a `"listen"` event which occurs when the server
|
||||
* has successfully opened but before any requests start being processed. */
|
||||
addEventListener(
|
||||
type: "listen",
|
||||
listener: ApplicationListenEventListenerOrEventListenerObject | null,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void;
|
||||
/** Add an event listener for an event. Currently valid event types are
|
||||
* `"error"` and `"listen"`. */
|
||||
addEventListener(
|
||||
type: "error" | "listen",
|
||||
listener: EventListenerOrEventListenerObject | null,
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
): void {
|
||||
super.addEventListener(type, listener, options);
|
||||
}
|
||||
|
||||
/** Handle an individual server request, returning the server response. This
|
||||
* is similar to `.listen()`, but opening the connection and retrieving
|
||||
* requests are not the responsibility of the application. If the generated
|
||||
* context gets set to not to respond, then the method resolves with
|
||||
* `undefined`, otherwise it resolves with a request that is compatible with
|
||||
* `std/http/server`. */
|
||||
handle = (async (
|
||||
request: Request,
|
||||
secureOrConn: Deno.Conn | boolean | undefined,
|
||||
secure: boolean | undefined = false,
|
||||
): Promise<Response | undefined> => {
|
||||
if (!this.#middleware.length) {
|
||||
throw new TypeError("There is no middleware to process requests.");
|
||||
}
|
||||
assert(isConn(secureOrConn) || typeof secureOrConn === "undefined");
|
||||
const contextRequest = new NativeRequest({
|
||||
request,
|
||||
respondWith() {
|
||||
return Promise.resolve(undefined);
|
||||
},
|
||||
}, { conn: secureOrConn });
|
||||
const context = new Context(
|
||||
this,
|
||||
contextRequest,
|
||||
this.#getContextState(),
|
||||
{ secure, ...this.#contextOptions },
|
||||
);
|
||||
try {
|
||||
await this.#getComposed()(context);
|
||||
} catch (err) {
|
||||
this.#handleError(context, err);
|
||||
}
|
||||
if (context.respond === false) {
|
||||
context.response.destroy();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await context.response.toDomResponse();
|
||||
context.response.destroy(false);
|
||||
return response;
|
||||
} catch (err) {
|
||||
this.#handleError(context, err);
|
||||
throw err;
|
||||
}
|
||||
}) as HandleMethod;
|
||||
|
||||
/** Start listening for requests, processing registered middleware on each
|
||||
* request. If the options `.secure` is undefined or `false`, the listening
|
||||
* will be over HTTP. If the options `.secure` property is `true`, a
|
||||
* `.certFile` and a `.keyFile` property need to be supplied and requests
|
||||
* will be processed over HTTPS. */
|
||||
async listen(addr: string): Promise<void>;
|
||||
/** Start listening for requests, processing registered middleware on each
|
||||
* request. If the options `.secure` is undefined or `false`, the listening
|
||||
* will be over HTTP. If the options `.secure` property is `true`, a
|
||||
* `.certFile` and a `.keyFile` property need to be supplied and requests
|
||||
* will be processed over HTTPS.
|
||||
*
|
||||
* Omitting options will default to `{ port: 0 }` which allows the operating
|
||||
* system to select the port. */
|
||||
async listen(options?: ListenOptions): Promise<void>;
|
||||
async listen(options: string | ListenOptions = { port: 0 }): Promise<void> {
|
||||
if (!this.#middleware.length) {
|
||||
throw new TypeError("There is no middleware to process requests.");
|
||||
}
|
||||
if (typeof options === "string") {
|
||||
const match = ADDR_REGEXP.exec(options);
|
||||
if (!match) {
|
||||
throw TypeError(`Invalid address passed: "${options}"`);
|
||||
}
|
||||
const [, hostname, portStr] = match;
|
||||
options = { hostname, port: parseInt(portStr, 10) };
|
||||
}
|
||||
options = Object.assign({ port: 0 }, options);
|
||||
const server = new this.#serverConstructor(
|
||||
this,
|
||||
options as Deno.ListenOptions,
|
||||
);
|
||||
const { signal } = options;
|
||||
const state = {
|
||||
closed: false,
|
||||
closing: false,
|
||||
handling: new Set<Promise<void>>(),
|
||||
server,
|
||||
};
|
||||
if (signal) {
|
||||
signal.addEventListener("abort", () => {
|
||||
if (!state.handling.size) {
|
||||
server.close();
|
||||
state.closed = true;
|
||||
}
|
||||
state.closing = true;
|
||||
});
|
||||
}
|
||||
const { secure = false } = options;
|
||||
const serverType = server instanceof HttpServer
|
||||
? "native"
|
||||
: server instanceof FlashServer
|
||||
? "flash"
|
||||
: "custom";
|
||||
const listener = await server.listen();
|
||||
const { hostname, port } = listener.addr as Deno.NetAddr;
|
||||
this.dispatchEvent(
|
||||
new ApplicationListenEvent({
|
||||
hostname,
|
||||
listener,
|
||||
port,
|
||||
secure,
|
||||
serverType,
|
||||
}),
|
||||
);
|
||||
try {
|
||||
for await (const request of server) {
|
||||
this.#handleRequest(request, secure, state);
|
||||
}
|
||||
await Promise.all(state.handling);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error
|
||||
? error.message
|
||||
: "Application Error";
|
||||
this.dispatchEvent(
|
||||
new ApplicationErrorEvent({ message, error }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Register middleware to be used with the application. Middleware will
|
||||
* be processed in the order it is added, but middleware can control the flow
|
||||
* of execution via the use of the `next()` function that the middleware
|
||||
* function will be called with. The `context` object provides information
|
||||
* about the current state of the application.
|
||||
*
|
||||
* Basic usage:
|
||||
*
|
||||
* ```ts
|
||||
* const import { Application } from "https://deno.land/x/oak/mod.ts";
|
||||
*
|
||||
* const app = new Application();
|
||||
*
|
||||
* app.use((ctx, next) => {
|
||||
* ctx.request; // contains request information
|
||||
* ctx.response; // setups up information to use in the response;
|
||||
* await next(); // manages the flow control of the middleware execution
|
||||
* });
|
||||
*
|
||||
* await app.listen({ port: 80 });
|
||||
* ```
|
||||
*/
|
||||
use<S extends State = AS>(
|
||||
middleware: Middleware<S, Context<S, AS>>,
|
||||
...middlewares: Middleware<S, Context<S, AS>>[]
|
||||
): Application<S extends AS ? S : (S & AS)>;
|
||||
use<S extends State = AS>(
|
||||
...middleware: Middleware<S, Context<S, AS>>[]
|
||||
): Application<S extends AS ? S : (S & AS)> {
|
||||
this.#middleware.push(...middleware);
|
||||
this.#composedMiddleware = undefined;
|
||||
// deno-lint-ignore no-explicit-any
|
||||
return this as Application<any>;
|
||||
}
|
||||
|
||||
[Symbol.for("Deno.customInspect")](inspect: (value: unknown) => string) {
|
||||
const { keys, proxy, state } = this;
|
||||
return `${this.constructor.name} ${
|
||||
inspect({ "#middleware": this.#middleware, keys, proxy, state })
|
||||
}`;
|
||||
}
|
||||
|
||||
[Symbol.for("nodejs.util.inspect.custom")](
|
||||
depth: number,
|
||||
// deno-lint-ignore no-explicit-any
|
||||
options: any,
|
||||
inspect: (value: unknown, options?: unknown) => string,
|
||||
) {
|
||||
if (depth < 0) {
|
||||
return options.stylize(`[${this.constructor.name}]`, "special");
|
||||
}
|
||||
|
||||
const newOptions = Object.assign({}, options, {
|
||||
depth: options.depth === null ? null : options.depth - 1,
|
||||
});
|
||||
const { keys, proxy, state } = this;
|
||||
return `${options.stylize(this.constructor.name, "special")} ${
|
||||
inspect(
|
||||
{ "#middleware": this.#middleware, keys, proxy, state },
|
||||
newOptions,
|
||||
)
|
||||
}`;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user