import { fromFileUrl, resolve, Server } from "../deps.ts";import { methods } from "./methods.ts";import { Router } from "./router/index.ts";import { init } from "./middleware/init.ts";import { query } from "./middleware/query.ts";import { finalHandler } from "./utils/finalHandler.ts";import { compileETag } from "./utils/compileETag.ts";import { compileQueryParser } from "./utils/compileQueryParser.ts";import { compileTrust } from "./utils/compileTrust.ts";import { merge } from "./utils/merge.ts";import { View } from "./view.ts";import { WrappedRequest } from "./request.ts";import type { Application, HTTPOptions, HTTPSOptions, IRoute, NextFunction, Opine, OpineRequest, OpineResponse, PathParams,} from "../src/types.ts";
const create = Object.create;const setPrototypeOf = Object.setPrototypeOf;const slice = Array.prototype.slice;
const trustProxyDefaultSymbol = "@@symbol:trust_proxy_default";
export const app: Application = {} as Application;
app.init = function init(): void { this.cache = {}; this.engines = {}; this.settings = {};
this.defaultConfiguration();};
app.defaultConfiguration = function defaultConfiguration(): void { this.enable("x-powered-by"); this.set("etag", "weak"); this.set("query parser", "extended"); this.set("subdomain offset", 2); this.set("trust proxy", false);
Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: true, });
const self: Opine = this as Opine; this.on("mount", function onmount(parent: Opine) { if ( self.settings[trustProxyDefaultSymbol] === true && typeof parent.settings["trust proxy fn"] === "function" ) { delete self.settings["trust proxy"]; delete self.settings["trust proxy fn"]; }
setPrototypeOf(self.request, parent.request); setPrototypeOf(self.response, parent.response); setPrototypeOf(self.engines, parent.engines); setPrototypeOf(self.settings, parent.settings); });
this.locals = create(null);
this.mountpath = "/";
this.locals.settings = this.settings;
this.set("view", View); this.set("views", resolve("views")); this.set("jsonp callback name", "callback"); this.enable("view cache");};
app.lazyrouter = function lazyrouter(): void { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled("case sensitive routing"), strict: this.enabled("strict routing"), });
this._router.use(query(this.get("query parser fn"))); this._router.use(init(this as Opine)); }};
app.handle = function handle( req: OpineRequest, res: OpineResponse, next: NextFunction,): void { const router = this._router;
next = next || finalHandler(req, res);
if (!router) { return next(); }
router.handle(req, res, next);};
const isPath = (thing: unknown): thing is string | RegExp => typeof thing === "string" || thing instanceof RegExp;
app.use = function use(...args: any[]): Application { const firstArg = args[0]; const [path, ...nonPathArgs] = (Array.isArray(firstArg) ? isPath(firstArg[0]) : isPath(firstArg)) ? args : ["/", ...args]; const fns = nonPathArgs.flat(Infinity);
if (fns.length === 0) { throw new TypeError("app.use() requires a middleware function"); }
this.lazyrouter(); const router = this._router;
fns.forEach(function (this: Application, fn: any) { if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); }
fn.mountpath = path; fn.parent = this;
router.use( path, function mounted_app( req: OpineRequest, res: OpineResponse, next: NextFunction, ): void { const orig = req.app as Opine;
fn.handle(req, res, (err?: Error) => { setPrototypeOf(req, orig.request); setPrototypeOf(res, orig.response); next(err); }); }, );
fn.emit("mount", this); }, this);
return this;};
app.route = function route(prefix: PathParams): IRoute { this.lazyrouter(); return this._router.route(prefix);};
app.engine = function engine(ext: string, fn: Function) { const extension = ext[0] !== "." ? `.${ext}` : ext; this.engines[extension] = fn;
return this;};
app.param = function param(name, fn) { this.lazyrouter(); if (Array.isArray(name)) { for (var i = 0; i < name.length; i++) { this.param(name[i], fn); } return this; } this._router.param(name, fn); return this;};
app.set = function set(setting: string, value?: any): Application { if (arguments.length === 1) { return this.settings[setting]; }
this.settings[setting] = value;
switch (setting) { case "etag": this.set("etag fn", compileETag(value)); break; case "query parser": this.set("query parser fn", compileQueryParser(value)); break; case "trust proxy": this.set("trust proxy fn", compileTrust(value));
Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: false, });
break; }
return this;};
app.path = function path(): string { return this.parent ? this.parent.path() + this.mountpath : "";};
app.enabled = function enabled(setting: string): boolean { return Boolean(this.set(setting));};
app.disabled = function disabled(setting: string): boolean { return !this.set(setting);};
app.enable = function enable(setting: string): Application { return this.set(setting, true);};
app.disable = function disable(setting: string): Application { return this.set(setting, false);};
methods.forEach((method: string): void => { (app as any)[method] = function (path: string): Application { if (method === "get" && arguments.length === 1) { return this.set(path); }
this.lazyrouter();
const route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1));
return this; };});
app.all = function all(path: PathParams): Application { this.lazyrouter();
const route = this._router.route(path); const args = slice.call(arguments, 1);
for (let i = 0; i < methods.length; i++) { (route as any)[methods[i]].apply(route, args); }
return this;};
async function tryRender(view: any, options: any, callback: Function) { try { await view.render(options, callback); } catch (err) { callback(err); }}
app.render = function render( name: string, options: any, callback: Function = () => {},) { const cache = this.cache; const engines = this.engines; const renderOptions: any = {}; let done = callback; let view;
name = name.startsWith("file:") ? fromFileUrl(name) : name;
if (typeof options === "function") { done = options; options = {}; }
merge(renderOptions, this.locals);
if (options._locals) { merge(renderOptions, options._locals); }
merge(renderOptions, options);
if (renderOptions.cache == null) { renderOptions.cache = this.enabled("view cache"); }
if (renderOptions.cache) { view = cache[name]; }
if (!view) { const View = this.get("view");
view = new View(name, { defaultEngine: this.get("view engine"), engines, root: this.get("views"), });
if (!view.path) { const dirs = Array.isArray(view.root) && view.root.length > 1 ? `directories "${view.root.slice(0, -1).join('", "')}" or "${ view.root[view.root.length - 1] }"` : `directory "${view.root}"`;
const err = new Error( `Failed to lookup view "${name}" in views ${dirs}`, );
(err as any).view = view;
return done(err); }
if (renderOptions.cache) { cache[name] = view; } }
tryRender(view, renderOptions, done);};
const isTlsOptions = ( options?: number | string | HTTPOptions | HTTPSOptions,): options is HTTPSOptions => options !== null && typeof options === "object" && "certFile" in options && "keyFile" in options;
app.listen = function listen( options?: number | string | HTTPOptions | HTTPSOptions, callback?: () => void,): Server { let port = 0; let hostname = "";
if (typeof options === "number") { port = options; } else if (typeof options === "string") { const addr = options.split(":"); hostname = addr[0]; port = parseInt(addr[1]); } else { hostname = options?.hostname ?? ""; port = options?.port ?? 0; }
const isTls = isTlsOptions(options);
const server = new Server({ port, hostname, handler: async (request, connInfo) => { const opineRequest = new WrappedRequest(request, connInfo); this(opineRequest);
return await opineRequest.finalResponse; }, });
const start = async () => { while (!server.closed) { try { if (isTls) { await server.listenAndServeTls(options.certFile, options.keyFile); } else { await server.listenAndServe(); } } catch (e) { if ( !(e instanceof Deno.errors.BadResource || e instanceof Deno.errors.BrokenPipe) ) { this.emit("error", e); } } } };
start();
if (callback && typeof callback === "function") { callback(); }
return server;};