Skip to content

Commit

Permalink
Application
Browse files Browse the repository at this point in the history
  • Loading branch information
fengmk2 committed Jul 7, 2024
1 parent 37abca9 commit 4225a16
Show file tree
Hide file tree
Showing 17 changed files with 120 additions and 138 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ site/dist
.vercel
package-lock.json
.tshy*
dist
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
"koa-is-json": "^1.0.0",
"koa-override": "^4.0.0",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"onelogger": "^1.0.0",
"sendmessage": "^3.0.0",
"urllib": "^4.0.0",
Expand Down Expand Up @@ -129,7 +128,6 @@
}
},
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"source": "./src/index.ts",
Expand All @@ -141,8 +139,10 @@
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
}
},
"./package.json": "./package.json"
},
"types": "./dist/commonjs/index.d.ts",
"main": "./dist/commonjs/index.js"
"main": "./dist/commonjs/index.js",
"module": "./dist/esm/index.js"
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { BaseContextClass } from './lib/core/base_context_class.js';
* Start egg application with cluster mode
* @since 1.0.0
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
export { startCluster } from 'egg-cluster';

/**
Expand Down
2 changes: 1 addition & 1 deletion src/lib/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class Agent extends EggApplication {
* @class
* @param {Object} options - see {@link EggApplication}
*/
constructor(options?: EggApplicationOptions) {
constructor(options?: Omit<EggApplicationOptions, 'type'>) {
super({
...options,
type: 'agent',
Expand Down
173 changes: 70 additions & 103 deletions src/lib/application.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import path from 'node:path';
import fs from 'node:fs';
import http from 'node:http';
import ms from 'ms';
import is from 'is-type-of';
import { Socket } from 'node:net';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import graceful from 'graceful';
import cluster from 'cluster-client';
import onFinished from 'on-finished';
import { assign } from 'utility';
const eggUtils = require('egg-core').utils;
import { EggApplication } from './egg.js';
import { AppWorkerLoader } from './loader';
import { utils as eggUtils } from '@eggjs/core';
import { EggApplication, EggApplicationContext, EggApplicationOptions } from './egg.js';
import { AppWorkerLoader } from './loader/index.js';
import { BaseContextClass } from './core/base_context_class.js';

const KEYS = Symbol('Application#keys');
const HELPER = Symbol('Application#Helper');
const LOCALS = Symbol('Application#locals');
const BIND_EVENTS = Symbol('Application#bindEvents');
const WARN_CONFUSED_CONFIG = Symbol('Application#warnConfusedConfig');
const EGG_LOADER = Symbol.for('egg#loader');
const CLUSTER_CLIENTS = Symbol.for('egg#clusterClients');
const RESPONSE_RAW = Symbol('Application#responseRaw');

// client error => 400 Bad Request
// Refs: https://nodejs.org/dist/latest-v8.x/docs/api/http.html#http_event_clienterror
Expand All @@ -35,55 +28,60 @@ const DEFAULT_BAD_REQUEST_RESPONSE =
`\r\n\r\n${DEFAULT_BAD_REQUEST_HTML}`;

// Refs: https://github.com/nodejs/node/blob/b38c81/lib/_http_outgoing.js#L706-L710
function escapeHeaderValue(value) {
function escapeHeaderValue(value: string) {
// Protect against response splitting. The regex test is there to
// minimize the performance impact in the common case.
return /[\r\n]/.test(value) ? value.replace(/[\r\n]+[ \t]*/g, '') : value;
}

// Refs: https://github.com/nodejs/node/blob/b38c81/lib/_http_outgoing.js#L706-L710
/**
* The Helper class which can be used as utility function.
* We support developers to extend Helper through ${baseDir}/app/extend/helper.js ,
* then you can use all method on `ctx.helper` that is a instance of Helper.
*/
class HelperClass extends BaseContextClass {}

/**
* Singleton instance in App Worker, extend {@link EggApplication}
* @augments EggApplication
*/
export class Application extends EggApplication {
// will auto set after 'server' event emit
server?: http.Server;
#locals: Record<string, any> = {};
/**
* reference to {@link Helper}
* @member {Helper} Application#Helper
*/
Helper = HelperClass;

/**
* @class
* @param {Object} options - see {@link EggApplication}
*/
constructor(options = {}) {
options.type = 'application';
super(options);

// will auto set after 'server' event emit
this.server = null;

try {
this.loader.load();
} catch (e) {
// close gracefully
this[CLUSTER_CLIENTS].forEach(cluster.close);
throw e;
}

// dump config after loaded, ensure all the dynamic modifications will be recorded
const dumpStartTime = Date.now();
this.dumpConfig();
this.coreLogger.info('[egg:core] dump config after load, %s', ms(Date.now() - dumpStartTime));
constructor(options?: Omit<EggApplicationOptions, 'type'>) {
super({
...options,
type: 'application',
});
}

this[WARN_CONFUSED_CONFIG]();
this[BIND_EVENTS]();
protected async load() {
await super.load();
this.#warnConfusedConfig();
this.#bindEvents();
}

get [EGG_LOADER]() {
return AppWorkerLoader;
}

[RESPONSE_RAW](socket, raw) {
#responseRaw(socket: Socket, raw?: any) {
/* istanbul ignore next */
if (!socket.writable) return;
if (!raw) return socket.end(DEFAULT_BAD_REQUEST_RESPONSE);
if (!raw) {
return socket.end(DEFAULT_BAD_REQUEST_RESPONSE);
}

const body = (raw.body == null) ? DEFAULT_BAD_REQUEST_HTML : raw.body;
const headers = raw.headers || {};
Expand All @@ -107,10 +105,10 @@ export class Application extends EggApplication {
socket.end(`${firstLine}\r\n${responseHeaderLines}\r\n${body.toString()}`);
}

onClientError(err, socket) {
onClientError(err: any, socket: Socket) {
// ignore when there is no http body, it almost like an ECONNRESET
if (err.rawPacket) {
this.logger.warn('A client (%s:%d) error [%s] occurred: %s',
this.logger.warn('[egg:application] A client (%s:%d) error [%s] occurred: %s',
socket.remoteAddress,
socket.remotePort,
err.code,
Expand All @@ -136,19 +134,19 @@ export class Application extends EggApplication {
// + headers: {}
// + status: 400
p.then(ret => {
this[RESPONSE_RAW](socket, ret || {});
this.#responseRaw(socket, ret || {});
}).catch(err => {
this.logger.error(err);
this[RESPONSE_RAW](socket);
this.#responseRaw(socket);
});
} else {
// because it's a raw socket object, we should return the raw HTTP response
// packet.
this[RESPONSE_RAW](socket);
this.#responseRaw(socket);
}
}

onServer(server) {
onServer(server: http.Server) {
// expose app.server
this.server = server;
// set ignore code
Expand All @@ -157,14 +155,14 @@ export class Application extends EggApplication {
/* istanbul ignore next */
graceful({
server: [ server ],
error: (err, throwErrorCount) => {
error: (err: Error, throwErrorCount: number) => {
const originMessage = err.message;
if (originMessage) {
// shouldjs will override error property but only getter
// https://github.com/shouldjs/should.js/blob/889e22ebf19a06bc2747d24cf34b25cc00b37464/lib/assertion-error.js#L26
Object.defineProperty(err, 'message', {
get() {
return originMessage + ' (uncaughtException throw ' + throwErrorCount + ' times on pid:' + process.pid + ')';
return `${originMessage} (uncaughtException throw ${throwErrorCount} times on pid: ${process.pid})`;
},
configurable: true,
enumerable: false,
Expand All @@ -175,10 +173,12 @@ export class Application extends EggApplication {
ignoreCode: serverGracefulIgnoreCode,
});

server.on('clientError', (err, socket) => this.onClientError(err, socket));
server.on('clientError', (err, socket) => this.onClientError(err, socket as Socket));

// server timeout
if (is.number(this.config.serverTimeout)) server.setTimeout(this.config.serverTimeout);
if (typeof this.config.serverTimeout === 'number') {
server.setTimeout(this.config.serverTimeout);
}
}

/**
Expand All @@ -187,24 +187,11 @@ export class Application extends EggApplication {
* @see Context#locals
*/
get locals() {
if (!this[LOCALS]) {
this[LOCALS] = {};
}
return this[LOCALS];
}

set locals(val) {
if (!this[LOCALS]) {
this[LOCALS] = {};
}

assign(this[LOCALS], val);
return this.#locals;
}

handleRequest(ctx, fnMiddleware) {
this.emit('request', ctx);
onFinished(ctx.res, () => this.emit('response', ctx));
return super.handleRequest(ctx, fnMiddleware);
set locals(val: Record<string, any>) {
assign(this.#locals, val);
}

/**
Expand All @@ -227,11 +214,11 @@ export class Application extends EggApplication {
paramNames: layer.paramNames,
path: layer.path,
regexp: layer.regexp.toString(),
stack: layer.stack.map(stack => stack[FULLPATH] || stack._name || stack.name || 'anonymous'),
stack: layer.stack.map((stack: any) => stack[FULLPATH] || stack._name || stack.name || 'anonymous'),
});
}
fs.writeFileSync(dumpRouterFile, JSON.stringify(routers, null, 2));
} catch (err) {
} catch (err: any) {
this.coreLogger.warn(`dumpConfig router.json error: ${err.message}`);
}
}
Expand All @@ -241,9 +228,11 @@ export class Application extends EggApplication {
* @see Context#runInBackground
* @param {Function} scope - the first args is an anonymous ctx
*/
runInBackground(scope) {
runInBackground(scope: (ctx: EggApplicationContext) => void) {
const ctx = this.createAnonymousContext();
if (!scope.name) scope._name = eggUtils.getCalleeFromStack(true);
if (!scope.name) {
Reflect.set(scope, '_name', eggUtils.getCalleeFromStack(true));
}
this.ctxStorage.run(ctx, () => {
ctx.runInBackground(scope);
});
Expand All @@ -255,11 +244,13 @@ export class Application extends EggApplication {
* @param {Function} scope - the first args is an anonymous ctx, scope should be async function
* @param {Request} [req] - if you want to mock request like querystring, you can pass an object to this function.
*/
async runInAnonymousContextScope(scope, req) {
async runInAnonymousContextScope(scope: (ctx: EggApplicationContext) => Promise<void>, req?: unknown) {
const ctx = this.createAnonymousContext(req);
if (!scope.name) scope._name = eggUtils.getCalleeFromStack(true);
if (!scope.name) {
Reflect.set(scope, '_name', eggUtils.getCalleeFromStack(true));
}
return await this.ctxStorage.run(ctx, async () => {
return await scope(ctx);
return await scope(ctx as EggApplicationContext);
});
}

Expand All @@ -268,7 +259,7 @@ export class Application extends EggApplication {
* @member {String} Application#keys
*/
get keys() {
if (!this[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');
Expand All @@ -277,61 +268,37 @@ export class Application extends EggApplication {
}
throw new Error('Please set config.keys first');
}

this[KEYS] = this.config.keys.split(',').map(s => s.trim());
}
return this[KEYS];
}

set keys(_) {
// ignore
}

/**
* reference to {@link Helper}
* @member {Helper} Application#Helper
*/
get Helper() {
if (!this[HELPER]) {
/**
* The Helper class which can be used as utility function.
* We support developers to extend Helper through ${baseDir}/app/extend/helper.js ,
* then you can use all method on `ctx.helper` that is a instance of Helper.
*/
class Helper extends this.BaseContextClass {}
this[HELPER] = Helper;
this._keys = this.config.keys.split(',').map(s => s.trim());
}
return this[HELPER];
return this._keys;
}

/**
* bind app's events
*
* @private
*/
[BIND_EVENTS]() {
#bindEvents() {
// Browser Cookie Limits: http://browsercookielimits.squawky.net/
this.on('cookieLimitExceed', ({ name, value, ctx }) => {
const err = new Error(`cookie ${name}'s length(${value.length}) exceed the limit(4093)`);
err.name = 'CookieLimitExceedError';
err.key = name;
err.cookie = value;
ctx.coreLogger.error(err);
});
// expose server to support websocket
this.once('server', server => this.onServer(server));
this.once('server', (server: http.Server) => this.onServer(server));
}

/**
* warn when confused configurations are present
*
* @private
*/
[WARN_CONFUSED_CONFIG]() {
#warnConfusedConfig() {
const confusedConfigurations = this.config.confusedConfigurations;
Object.keys(confusedConfigurations).forEach(key => {
if (this.config[key] !== undefined) {
this.logger.warn('Unexpected config key `%s` exists, Please use `%s` instead.',
this.logger.warn('[egg:application] Unexpected config key `%o` exists, Please use `%o` instead.',
key, confusedConfigurations[key]);
}
});
Expand Down
2 changes: 1 addition & 1 deletion src/lib/core/base_context_class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { BaseContextLogger } from './base_context_logger.js';
export class BaseContextClass extends EggCoreBaseContextClass {
declare ctx: EggApplicationContext;
protected pathName?: string;
#logger: BaseContextLogger;
#logger?: BaseContextLogger;

get logger() {
if (!this.#logger) {
Expand Down
Loading

0 comments on commit 4225a16

Please sign in to comment.