egg框架启动分析
egg框架目前已经使用了大半年。框架因koa特色、强约束的feel、“为了毫无保留榨干服务器性能”的slogan,脱颖而出。框架使用大半年挺舒适,入手坡度不算太高。学而不思则罔,对这阶段的也就和工作内容做简要总结,如果有任何错误,还请积极指正,感谢。
有不少文章已经对相关内容作了阐述,感谢相关作者。值得深读
所谓万事开头难,对于egg的分析我也从其启动上着手,egg框架生态十分完整,致使阅读代码又爱又恨,爱其结构恨其调用关系链(懂了就好了),从启动方式切入分析,主体引用链为
egg-bin -> egg-cluster -> egg -> egg-core
文章按照启动链的顺序对egg-bin和egg-cluster做分析。
写在前面
文章着重对egg的启动构建做梳理总结,建议把相关源码下载下来,在node_modules下看难度有点大,时常进去后出不来了。应用层的使用脱离不了对架构层的理解。有任何问题还请告知。
egg-bin
egg-bin基于common-bin(相关介绍源tao_npm,是node_modules中对于bin相关操作的共用组件库),common-bin其重要地位可想而知。发现大师的写法也着实厉害,两个文件足以覆盖意蕴,command.js + helper.js完成任务。
egg-bin框架作为启动指令包,需要对指令参数进行有效解读,包内使用yargs解析参数,官方文档对egg-bin的使用作了有效讲解,相关地址,我们在开发的时候经常会用到的指令均来自与此依赖库。“egg-bin dev”作为开发启动指令重重之中,做详细分析。
egg-bin dev
egg-bin cov
egg-bin test
...
package.json文件中对egg-bin指令作了文件指向
"bin": {
"egg-bin": "bin/egg-bin.js",
...
},
相关文件也写的相当完美,挂载了所有index.js
文件的暴露文件。
bin/egg-bin.js
文件内容
const Command = require('..');
new Command().start();
./index.js
文件内容
class EggBin extends Command {
constructor(rawArgv) {
super(rawArgv);
this.usage = 'Usage: egg-bin [command] [options]';
// load directory
this.load(path.join(__dirname, 'lib/cmd'));
}
}
在依赖库的index下,有相关exports.当执行指令时,‘dev’参数将作为process.argv进入到common-bin/command.js
的构造函数中,
class CommonBin {
constructor(rawArgv) {
this.rawArgv = rawArgv || process.argv.slice(2);
...
this.yargs = yargs(this.rawArgv);
...
}
}
process.argv的相关介绍请参考node官方文档,执行后argv参数样式基本为
this.rawArgv = [
"dev"
]
剩下的都是内部参数调整包装,接着看上文index.js
的第二步,this.load
方法,继承关系,实现在common-bin/command.js
中
// fullPath value is 'lib/cmd'
load(fullPath) {
...
// load entire directory
const files = fs.readdirSync(fullPath);
const names = [];
for (const file of files) {
if (path.extname(file) === '.js') {
const name = path.basename(file).replace(/\.js$/, '');
names.push(name);
this.add(name, path.join(fullPath, file));
}
}
}
简单说,这个地方扫描了lib/cmd
下所有js文件,将其名称正则后,KV形式加入到对应序列用作存取使用。this.add就是一个this[COMMANDS]的set方法,不粘贴了。
再然后便是new Commnad().start()
方法,自然也是在common-bin包下,不废话,上代码
start() {
co(function* () {
const index = this.rawArgv.indexOf('--get-yargs-completions');
if (index !== -1) {
this.rawArgv.splice(index, 2, `--AUTO_COMPLETIONS=${this.rawArgv.join(',')}`);
}
yield this[DISPATCH]();
}.bind(this)).catch(this.errorHandler.bind(this));
}
大名鼎鼎的co
包还是很有魅力的,具体请文章序幕文章。重要的在yield this[DISPATCH]()
下,继续上代码
* [DISPATCH]() {
// if sub command exist
if (this[COMMANDS].has(commandName)) {
const Command = this[COMMANDS].get(commandName);
...
const command = this.getSubCommandInstance(Command, rawArgv);
yield command[DISPATCH]();
return;
}
...
// print completion for bash
if (context.argv.AUTO_COMPLETIONS) {
...
} else {
// handle by self
yield this.helper.callFn(this.run, [ context ], this);
}
}
其实说到底,也是去实例化了子实例,也就是本文的/lib/cmd/dev.js
的[DISPATCH]
方法(在此前this[COMMANDS]
的set方法还记得嘛),cmd/dev.js不实现DISPATCH,因此直接跳过。然后到最后去执行cmd/dev.js的run()
* run(context) {
...
yield this.helper.forkNode(this.serverBin, devArgs, options);
}
constructor(){
...
this.serverBin = path.join(__dirname, '../start-cluster');
}
很明显啦,forkNode(先按照child_process.fork
去理解),对应的start-cluster的内容为
require(options.framework).startCluster(options);
而option.framework指的就是egg项目,egg的index.js文件中也指出
exports.startCluster = require('egg-cluster').startCluster;
因此指向调用的就是egg-cluster啦。
至此告一段落。egg-bin的dev指令流程也简单的梳理了一下,得益于common-bin,使得egg-bin的代码不是那么多层级,当然egg-bin不止只有dev
一个使用对象,其他的,下篇文章再做分析吧~
稍微休息一下,然后继续吧..、
egg-cluster
egg-cluster的启动继承ready+events模式,关于get-ready,这里不做过多叙述,直接上代码
const ready = require('get-ready');
const obj = {};
ready.mixin(obj);
// register a callback
obj.ready(() => console.log('ready func 1'));
obj.ready(() => console.log('ready func 2'));
console.log('ready1');
// mark ready
obj.ready(true);
console.log('ready2');
因此得出的console日志如下
/usr/local/bin/node ../ready.js
ready1
ready2
ready func 1
ready func 2
Process finished with exit code 0
结果很明显了,对于mixin的对象,ready方法有链式效果,因此在egg-cluster/index.js中有这么一句
exports.startCluster = function(options, callback){
new Master(options).ready(callback);
};
而作为Master.js的constructor上有这么一段
class Master extends EventEmitter{
constructor(){
...
this.ready(() => {
...
});
}
}
因此cluster将会先执行构造方法里面的ready,然后暴露到callback对象上。至于events呢,注意listener/on/once/emit方法即可,具体还请移步大师讲述官方文档。
在master.js的constructor上,把关于当前的调查信息罗列出来
class Master extends EventEmitter{
constructor(options) {
...
this.ready(() => {
...
const action = 'egg-ready';
this.messenger.send({ action, to: 'parent', data: { ... });
this.messenger.send({ action, to: 'app', data: this.options });
this.messenger.send({ action, to: 'agent', data: this.options });
});
this.on('agent-exit', this.onAgentExit.bind(this));
this.on('agent-start', this.onAgentStart.bind(this));
this.on('app-exit', this.onAppExit.bind(this));
this.on('app-start', this.onAppStart.bind(this));
this.on('reload-worker', this.onReload.bind(this));
// fork app workers after agent started
this.once('agent-start', this.forkAppWorkers.bind(this));
...
detectPort((err, port) => {
...
this.options.clusterPort = port;
this.forkAgentWorker();
});
}
}
这里又有一个核心大佬放出的detect-port
包github仓库这个包的作用就是在做一系列规范化检查后使用net.createServer
尝试创建查看端口是否可用,如果不可用端口+1(使用时7001被占用系统会启动在7002也是因为这个端口预创建测试放出探针结果)自然在master.constructor
上的cb方法就祈祷作用了,会调用master.js#forkAgentWorker
,这个方法主要以child_process.fork
启动了Agent,Agent继承链,Agent >> Egg.Agent >> Egg.EggApplication。
这里还得提前插一嘴,关于messenger
,因为接下来的调用都和消息有关,而egg的内部消息传输方向有相关规定,且传输消息均由messenger
承担。
messenger
在master.js
的constructor阶段创建,其作用指向便是消息传输,在源码注释里文件里面有个图
* master messenger,provide communication between parent, master, agent and app.
*
* ┌────────┐
* │ parent │
* /└────────┘\
* / | \
* / ┌────────┐ \
* / │ master │ \
* / └────────┘ \
* / / \ \
* ┌───────┐ ┌───────┐
* │ agent │ ------- │ app │
* └───────┘ └───────┘
作为调用端,也就是当前请求startCluster的对象,拥有对process的动作监听,其便是(parent),在egg-cluster
中index.js
里创建了master
,在master
中依靠接下来的顺序创建了agent
和app
,于是四者便连同在一起。在messenger中,作为消息的发送传递者,有相关数据指向
调用方法 | 发送对象 | to | 备注 |
---|---|---|---|
sendToMaster | this.master.emit | master | |
sendToParent | process.send | parent | |
sendToAppWorker | sendMessage | app | data里receiverPid是全部app_worker的pid |
sendToAgentWorker | sendmessage | agent |
Q: master和parent区别是什么?
从流程上来看,parent(current process)上创建master
先放出一张启动顺序的消息传输图片,记录下启动顺序(非全量记录)
按照这个顺序,我们详述
0. 放出探针(detect-port),获取可用端口
1. (序号1 --> 3)端口获取成功 发送’agent-start‘消息内容
和上面贴出代码一致,this.forkAgentWorker()
方法,会调用入child_process
去fork
文件agent_worker.js
const Agent = require(options.framework).Agent;
const agent = new Agent(options);
agent.ready(err => {
// don't send started message to master when start error
if (err) return;
...
process.send({ action: 'agent-start', to: 'master' });
});
这里比较好理解,目前已知option.framework
就是/{项目目录}/node_module/egg
,因此,这fork的就是egg.Agent
,当fork完毕后,Agent
基于events
调用this.ready(true)
,便会process发出消息agent-start
指向master,而为子进程的agent的消息事件,在master上早就等着你了,看这master.js
this.on('agent-start', this.onAgentStart.bind(this));
...
this.once('agent-start', this.forkAppWorkers.bind(this));
on和once的区别就不讲了,当首次触发agent-start
时,回调到第二行代码中,执行forkAppWorker
方法,至此,agent启动完毕,接下来就是启动worker(app)。于是乎,很顺利的就跳转回master.js
去执行接下来的逻辑链了。这里我严重、严重、严重提示一下,对于创建agent的,是childprocess.fork()
,先留着,以后做分析。
触发到forkAppworker
方法,贴出大概代码
forkAppWorkers() {
cfork({
exec: this.getAppWorkerFile(),
...
});
cluster.on('fork', worker => {
...
});
cluster.on('disconnect', worker => {
...
});
cluster.on('exit', (worker, code, signal) => {
...
});
cluster.on('listening', (worker, address) => {
this.messenger.send({
action: 'app-start',
data: { workerPid: worker.process.pid, address },
to: 'master',
from: 'app',
});
});
}
自然和fork出Agent类似,用了cfork去操作(还是一样,cfork做分篇介绍),当fork监听途中,会触发放出app-start
消息至master,至此,完成序号1 -> 序号3的消息发送。
我们趁热打铁,继续走...
2. (序号3 --> 4)agent启动成功,发送’app-start‘消息内容
当收到前序(序号3:action = 'app-start'),查找master中相关监听实现
this.on('app-start', this.onAppStart.bind(this));
onAppStart(data) {
...
const remain = this.isAllAppWorkerStarted ? 0 : this.options.workers - this.startSuccessCount;
...
// send message to agent with alive workers
this.messenger.send({
action: 'egg-pids',
to: 'agent',
data: this.workerManager.getListeningWorkerIds(),
});
...
// Send egg-ready when app is started after launched
if (this.isAllAppWorkerStarted) {
this.messenger.send({ action: 'egg-ready', to: 'app', data: this.options });
}
...
this.isAllAppWorkerStarted = true;
...
if (this.options.sticky) {
this.startMasterSocketServer(err => {
if (err) return this.ready(err);
this.ready(true);
});
} else {
this.ready(true);
}
}
我们能看到,在app启动方法中会发送egg-pids
消息至agent
中,并且判断isAllAppworkerStarted
,确认都启动后放出消息egg-ready
至app
关于egg-pids
,找了一大圈没有发现监听方法,在/test/fixture
下有几个测试demo是对其做了监听,在egg
包中,ipc有个Messenger
做了监听,因此推测当前消息是暴露给相关周边开发者,更有效获取pid用途,或者是更高深层层次的用法,我这个小白未能领悟到精神。
引用一下文章序中Egg 源码分析之 egg-cluster说的比较重要的内容
sticky 模式:Master 负责统一监听对外端口,然后根据用户 ip 转发到固定的 Worker 子进程上,每个 Worker 自己启动了一个新的本地服务
非 sticky 模式:每个Worker 都统一启动服务监听外部端口
继续看,出发了ready()
,监听事件
this.ready(() => {
...
const action = 'egg-ready';
this.messenger.send({ action, to: 'parent', data: { port: this[REALPORT], address: this[APP_ADDRESS] } });
this.messenger.send({ action, to: 'app', data: this.options });
this.messenger.send({ action, to: 'agent', data: this.options });
// start check agent and worker status
if (this.isProduction) {
this.workerManager.startCheck();
}
});
至此,在cluster包下的启动主流程粗略梳理了一下。大家可以缓口气,剩下的就是消息egg-ready
消息在Agent
、App(Worker)
、Master
中分别作了什么呢?这得看下一个包-egg
。
egg & egg-core
打个比方,如果说整个框架组成了一个屋子,egg-bin可以说是门,egg-cluster是客厅,我想egg就是卧室了吧,egg的重要可想而知,对于Master、Agent、和Worker(App),其中Master在egg-cluster中定义,Agent和Worker皆为egg包下的继承自EggApplication对象,
上几个篇幅已经简单说明了Master的初始化创建情况,接下来我们继续聊聊Agent和Worker。
egg.Agent
agent在官方文章中有相关定义:
Agent 好比是 Master 给其他 Worker 请的一个『秘书』,它不对外提供服务,只给 App Worker 打工,专门处理一些公共事务
Agent 虽然是 App Worker 的『小秘』,但是业务相关的工作不应该放到 Agent 上去做,不然把她累垮了就不好了
由于 Agent 的特殊定位,我们应该保证它相对稳定。当它发生未捕获异常,框架不会像 App Worker 一样让他退出重启,而是记录异常日志、报警等待人工处理
结合官方文档的定义,Agent有如下特点:轻量、捕获异常、少量业务代码
我们先看下Agent是怎么实现的:
Agent对象在egg-cluster创建环节中被创建出来,继承自egg.Agent
对象,该对象继承EggApplication
,且loader为./lib/loader/agent_worker_loader.js
文件,继承自egg-core.eggLoader
对象,
整体继承链如上,继续梳理。
我们先看到对象的constructor
./lib/agent.js
class Agent extends EggApplication {
constructor(options = {}) {
options.type = 'agent';
super(options);
this.loader.load();
// dump config after loaded, ensure all the dynamic modifications will be recorded
this.dumpConfig();
// keep agent alive even it doesn't have any io tasks
setInterval(() => {}, 24 * 60 * 60 * 1000);
this._uncaughtExceptionHandler = this._uncaughtExceptionHandler.bind(this);
process.on('uncaughtException', this._uncaughtExceptionHandler);
}
}
整个构造方法做了四件事
- 完成父类构建、[seq1]
- 驱动loader执行load方法,[seq2]
- dump相关配置文件进入./run目录下、[seq3]
- 监听异常事件。 [seq4]
steo0. 有些话得说在前面
为什么说有些东西要在前面说明,因为在阅读源码的时候,发现这是最基类的设计调用,我先做告知,方便理解。
Q1:loader是个什么?
loader加载器在egg-core包下有相关实现定义,翻看源码大致能看到这么个东西 egg-core/lib/loader/egg-loader.js
...
const loaders = [
require('./mixin/plugin'),
require('./mixin/config'),
require('./mixin/extend'),
require('./mixin/custom'),
require('./mixin/service'),
require('./mixin/middleware'),
require('./mixin/controller'),
require('./mixin/router'),
require('./mixin/custom_loader'),
];
for (const loader of loaders) {
Object.assign(EggLoader.prototype, loader);
}
module.exports = EggLoader;
很明显能看到,当前export对象做了mixin,这也是我感觉egg这么灵活的优点。我们能够看到作为外围暴露的loader对象,将会拥有./mixin/[xxxx].js(表中增加的)所有function。所以在子类\实现类下可以随意调用任何位置的func,如果有loader方法想查找实现,需要寻找到相关对象(一般从名字上能分辨出来)。
Q2:loader的指向?
我在阅读源码的时候就会发现,agent其实使用agent_loader,而原来本身也有loader,那在EggApplication里面调用的基础Egg-Core.loader到底是哪个呢?
答:因为是Symbol,是最外围定义的那个对象.如果不确定的地方建议debug一下就了解了.
分割线,让我们继续
step1. 我们先看下在父类做了什么./lib/egg.js
(Agent和App都继承当前对象)
父类剑指EggApplication文件,在EggApplication的constructor上,摘要部分信息 ./lib/egg.js
constructor(options = {}) {
options.mode = options.mode || 'cluster';
super(options);
...
this.loader.loadConfig();
...
this.ready(() => process.nextTick(() => {
const dumpStartTime = Date.now();
this.dumpConfig();
this.dumpTiming();
}));
...
this._unhandledRejectionHandler = this._unhandledRejectionHandler.bind(this);
process.on('unhandledRejection', this._unhandledRejectionHandler);
this[CLUSTER_CLIENTS] = [];
this.cluster = (clientClass, options) => {
options = Object.assign({}, this.config.clusterClient, options, {...});
const client = cluster(clientClass, options);
this._patchClusterClient(client);
return client;
};
}
为了简化理解难度,我们先抓几个重要的流程做分析(来自1步骤,这里编号seq1)
- super方法 [seql1.1]
- 驱使loader.loadConfig() [seq1.2]
- events的ready事件 [seq1.3]
- cluster的初始化 [seq1.4]
我们先跳过seq1.1
(不然量太大了,扛不住哈哈),回来后补,
seq1.2
关于loader.loadConfig()
是loader的mixin里面(曾经的我年轻了,这里有个指向问题,指向的其实不是egg-core.loader,需要先观察创建这个对象有没有相关指向,config.js
Agent
有指向,是agent_work_loader
,也是继承了这个基类,但是需要看其多态复写),相关实现请参考源码,agent_worker.js
class AgentWorkerLoader extends EggLoader {
loadConfig() {
this.loadPlugin();
super.loadConfig();
}
}
强制先执行了loadPlugin()
,加载各类插件,相关加载数据请参考源码,egg-core/lib/mixin/plugin.js
,我摘取几个顺序的方法调用
this._extendPlugins(this.allPlugins, eggPlugins);
this._extendPlugins(this.allPlugins, appPlugins);
this._extendPlugins(this.allPlugins, customPlugins);
可以看到,先加载了egg-framework本身的框架,然后用户的app中实现插件和custom自定义插件
关于super.loadConfig()
就完全是基类方法实现啦,简述就是读取了所有你的配置文件,具体读取顺序参考源码注释,留作记录。
// plugin config.default
// framework config.default
// app config.default
// plugin config.{env}
// framework config.{env}
// app config.{env}
seq1.3
当有ready(true)的时候会触发方法,目前没有仔细去寻找,留作下一阶段任务,dumpConfig()
seq1.4
是cluster的初始化,因为是在Application上的,所以无论是agent还是app都可以使用,这里的用途建议参考官方文档-多进程增强模式和cluster-client这两个文档,这里我也有很多疑惑,稍后回来钻研.
step2. this.loader.load()方法
按照之前Q1的叙述,loader为加载器,但是有个特殊的时,这里的使用对象是个symbol,在相关的class的get/set方法上做了定义,因此在Agent里面,自然也要寻找相关定义值,如下图
./lib/agent.js
const EGG_LOADER = Symbol.for('egg#loader');
const AgentWorkerLoader = require('./loader').AgentWorkerLoader;
...
class Agent extends EggApplication {
...
get [EGG_LOADER]() {
return AgentWorkerLoader;
}
...
}
抽取相关比较重要的,可以看到,作为Agent,是使用了AgentWorkerLoader,因此我们打开一下这个文件
./lib/loader/agent_loader.js
'use strict';
const EggLoader = require('egg-core').EggLoader;
/**
* Agent worker process loader
* @see https://github.com/eggjs/egg-loader
*/
class AgentWorkerLoader extends EggLoader {
/**
* loadPlugin first, then loadConfig
*/
loadConfig() {
this.loadPlugin();
super.loadConfig();
}
load() {
this.loadAgentExtend();
this.loadContextExtend();
this.loadCustomAgent();
}
}
module.exports = AgentWorkerLoader;
量不大,直接全复制过来了.我们能够发现,执行的load()方法其实就是三个方法的合集,对于所有插件的执行方法理解在其他文章中,稍后整理后放出
step3. dump相关配置
dumpConfig方法在agent调用了多次,相关源码
dumpConfig() {
const rundir = this.config.rundir;
try {
/* istanbul ignore if */
if (!fs.existsSync(rundir)) fs.mkdirSync(rundir);
// get dumpped object
const { config, meta } = this.dumpConfigToObject();
// dump config
const dumpFile = path.join(rundir, `${this.type}_config.json`);
fs.writeFileSync(dumpFile, CircularJSON.stringify(config, null, 2));
// dump config meta
const dumpMetaFile = path.join(rundir, `${this.type}_config_meta.json`);
fs.writeFileSync(dumpMetaFile, CircularJSON.stringify(meta, null, 2));
} catch (err) {
this.coreLogger.warn(`dumpConfig error: ${err.message}`);
}
}
关于里面dumpConfigToObject
方法,实则是读取了全局配置this.config + 所有plugins指向值,做成json输出到文件,如下源码所示
dumpConfigToObject() {
let ignoreList;
try {
// support array and set
ignoreList = Array.from(this.config.dump.ignore);
} catch (_) {
ignoreList = [];
}
const json = extend(true, {}, { config: this.config, plugins: this.plugins });
utils.convertObject(json, ignoreList);
return {
config: json,
meta: this.loader.configMeta,
};
}
自然,在egg框架启动的run/${type}_config.json
文件肯定就有啦,application和agent将会产生如下文件,meta对应文件指向,config.json为配置值
- {project_root}/run/
- agent_config.json
- agent_config_meta.json
- application_config.json
- application_config_meta.json
step4. 对异常的捕获
Agent的作用其中一点就是捕获异常,对于node异常,可以对全局Process监听uncaughtException事件,做出处理.
egg上加了对日志的打印逻辑处理,可自行查看源码
至此,egg.Agent构造流程梳理完毕.
egg.Application
app也就是worker(下面统一说app吧,方便理解).
app的相对于agent都是继承同一个对象,业务方向能够感受到更加针对通信,先翻出源码
application.js
class Application extends EggApplication {
/**
* @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));
this[WARN_CONFUSED_CONFIG]();
this[BIND_EVENTS]();
}
}
大体方向和agent差不多,具体可以产分成
- 父类构建(之前浅谈过了,这里就先跳过啦)
- loader的load方法 (Step.2)
- dumpConfig方法(Step.3)
- 触发 WARN_COUFUSED_CONFIG(Step.4)
- 触发 BIND_EVENTS(Step.5)
step2. loader.load
在app_worker_loader.js上,对load的定义比agent多点,也取决于设计理念的不同,相关源码,还是一样,对于插件的方法将会统一整理
class AppWorkerLoader extends EggLoader{
load() {
// app > plugin > core
this.loadApplicationExtend();
this.loadRequestExtend();
this.loadResponseExtend();
this.loadContextExtend();
this.loadHelperExtend();
// app > plugin
this.loadCustomApp();
// app > plugin
this.loadService();
// app > plugin > core
this.loadMiddleware();
// app
this.loadController();
// app
this.loadRouter(); // Dependent on controllers
this.loadCustomLoader();
}
}
Step3. dumpConfig方法
这里的dumConfig指向的不再单纯只是父类方法(Agent是直接应用父类方法的.)config和plugins的相关信息
./application.js
/**
* save routers to `run/router.json`
* @private
*/
dumpConfig() {
super.dumpConfig();
// dump routers to router.json
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}`);
}
}
这里的方法将会吧router的相关信息打印到run/router.json文件中。也符合app应当承担的router路由业务设计理念。
step4. 触发WARN_COUFUSED_CONFIG方法
能够看到对一个全局this.config.confusedConfiguration对象作出遍历合法判断,(贸贸然来句,这个框架各种this对象,有必要深挖一下,整理一下)后续分析吧
step5. 触发BIND_EVENTS
待分析。
关于对启动后的http/tcp/socket的监听逻辑。未完待续