egg
Version:
A web application framework for Node.js
210 lines (209 loc) • 6.45 kB
JavaScript
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 };