UNPKG

egg

Version:

A web application framework for Node.js

210 lines (209 loc) • 6.45 kB
import Helper from "../app/extend/helper.js"; import { CookieLimitExceedError } from "./error/CookieLimitExceedError.js"; import { EggApplicationCore } from "./egg.js"; import { AppWorkerLoader } from "./loader/AppWorkerLoader.js"; import "./loader/index.js"; import { utils } from "@eggjs/core"; import path from "node:path"; import { assign } from "utility"; import fs from "node:fs"; import http from "node:http"; import { isGeneratorFunction } from "is-type-of"; import "node:net"; import { graceful } from "graceful"; //#region src/lib/application.ts const DEFAULT_BAD_REQUEST_HTML = `<html> <head><title>400 Bad Request</title></head> <body bgcolor="white"> <center><h1>400 Bad Request</h1></center> <hr><center>❤</center> </body> </html>`; const DEFAULT_BAD_REQUEST_RESPONSE = `HTTP/1.1 400 Bad Request\r\nContent-Length: ${Buffer.byteLength(DEFAULT_BAD_REQUEST_HTML)}\r\n\r\n${DEFAULT_BAD_REQUEST_HTML}`; function escapeHeaderValue(value) { return /[\r\n]/.test(value) ? value.replace(/[\r\n]+[ \t]*/g, "") : value; } /** * Singleton instance in App Worker, extend {@link EggApplicationCore} * @augments EggApplicationCore */ var Application = class extends EggApplicationCore { server; #locals = {}; /** * reference to {@link Helper} * @member {Helper} Application#Helper */ Helper = Helper; /** * @class * @param {Object} options - see {@link EggApplicationCore} */ constructor(options) { super({ ...options, type: "application" }); } customEggLoader() { return AppWorkerLoader; } async load() { await super.load(); this.#warnConfusedConfig(); this.#bindEvents(); } #responseRaw(socket, raw) { if (!socket?.writable) return; if (!raw) { socket.end(DEFAULT_BAD_REQUEST_RESPONSE); return; } const body = raw.body == null ? DEFAULT_BAD_REQUEST_HTML : raw.body; const headers = raw.headers || {}; const status = raw.status || 400; let responseHeaderLines = ""; const firstLine = `HTTP/1.1 ${status} ${http.STATUS_CODES[status] || "Unknown"}`; for (const key of Object.keys(headers)) { if (key.toLowerCase() === "content-length") { delete headers[key]; continue; } responseHeaderLines += `${key}: ${escapeHeaderValue(headers[key])}\r\n`; } responseHeaderLines += `Content-Length: ${Buffer.byteLength(body)}\r\n`; socket.end(`${firstLine}\r\n${responseHeaderLines}\r\n${body.toString()}`); } onClientError(err, socket) { if (err.rawPacket) this.logger.warn("[egg:application] A client (%s:%d) error [%s] occurred: %s", socket.remoteAddress, socket.remotePort, err.code, err.message); if (typeof this.config.onClientError === "function") utils.callFn(this.config.onClientError, [ err, socket, this ]).then((ret) => { this.#responseRaw(socket, ret || {}); }).catch((err) => { this.logger.error(err); this.#responseRaw(socket); }); else this.#responseRaw(socket); } onServer(server) { this.server = server; const serverGracefulIgnoreCode = this.config.serverGracefulIgnoreCode || []; graceful({ server: [server], error: (err, throwErrorCount) => { const originMessage = err.message; if (originMessage) Object.defineProperty(err, "message", { get() { return `${originMessage} (uncaughtException throw ${throwErrorCount} times on pid: ${process.pid})`; }, configurable: true, enumerable: false }); this.coreLogger.error(err); }, ignoreCode: serverGracefulIgnoreCode }); server.on("clientError", (err, socket) => this.onClientError(err, socket)); if (typeof this.config.serverTimeout === "number") server.setTimeout(this.config.serverTimeout); } /** * global locals for view * @member {Object} Application#locals * @see Context#locals */ get locals() { return this.#locals; } set locals(val) { assign(this.#locals, val); } /** * save routers to `run/router.json` * @private */ dumpConfig() { super.dumpConfig(); const rundir = this.config.rundir; const FULLPATH = this.loader.FileLoader.FULLPATH; try { const dumpRouterFile = path.join(rundir, "router.json"); const routers = []; for (const layer of this.router.stack) routers.push({ name: layer.name, methods: layer.methods, paramNames: layer.paramNames, path: layer.path, regexp: layer.regexp.toString(), stack: layer.stack.map((stack) => stack[FULLPATH] || stack._name || stack.name || "anonymous") }); fs.writeFileSync(dumpRouterFile, JSON.stringify(routers, null, 2)); } catch (err) { this.coreLogger.warn(`dumpConfig router.json error: ${err.message}`); } } /** * Run async function in the background * @see Context#runInBackground * @param {Function} scope - the first args is an anonymous ctx */ runInBackground(scope, req) { const ctx = this.createAnonymousContext(req); if (!scope.name) Reflect.set(scope, "_name", utils.getCalleeFromStack(true)); this.ctxStorage.run(ctx, () => { return ctx.runInBackground(scope); }); } /** * secret key for Application * @member {String} Application#keys */ get keys() { if (!this._keys) { if (!this.config.keys) { if (this.config.env === "local" || this.config.env === "unittest") { const configPath = path.join(this.config.baseDir, "config/config.default.js"); console.error("Cookie need secret key to sign and encrypt."); console.error("Please add `config.keys` in %s", configPath); } throw new Error("Please set config.keys first"); } this._keys = this.config.keys.split(",").map((s) => s.trim()); } return this._keys; } /** * @deprecated keep compatible with egg 3.x */ toAsyncFunction(fn) { if (isGeneratorFunction(fn)) throw new Error("Generator function is not supported"); return fn; } /** * bind app's events * * @private */ #bindEvents() { this.on("cookieLimitExceed", ({ name, value, ctx }) => { const err = new CookieLimitExceedError(name, value); ctx.coreLogger.error(err); }); this.once("server", (server) => this.onServer(server)); } /** * warn when confused configurations are present * * @private */ #warnConfusedConfig() { const confusedConfigurations = this.config.confusedConfigurations; Object.keys(confusedConfigurations).forEach((key) => { if (this.config[key] !== void 0) this.logger.warn("[egg:application] Unexpected config key `%o` exists, Please use `%o` instead.", key, confusedConfigurations[key]); }); } }; //#endregion export { Application };