egg框架启动分析

egg框架启动分析

egg框架目前已经使用了大半年。框架因koa特色、强约束的feel、“为了毫无保留榨干服务器性能”的slogan,脱颖而出。框架使用大半年挺舒适,入手坡度不算太高。学而不思则罔,对这阶段的也就和工作内容做简要总结,如果有任何错误,还请积极指正,感谢。

有不少文章已经对相关内容作了阐述,感谢相关作者。值得深读

漫谈 egg-bin
Egg 源码分析之 egg-cluster
co库相关实现

所谓万事开头难,对于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-portgithub仓库这个包的作用就是在做一系列规范化检查后使用net.createServer尝试创建查看端口是否可用,如果不可用端口+1(使用时7001被占用系统会启动在7002也是因为这个端口预创建测试放出探针结果)自然在master.constructor上的cb方法就祈祷作用了,会调用master.js#forkAgentWorker,这个方法主要以child_process.fork启动了Agent,Agent继承链,Agent >> Egg.Agent >> Egg.EggApplication。

这里还得提前插一嘴,关于messenger,因为接下来的调用都和消息有关,而egg的内部消息传输方向有相关规定,且传输消息均由messenger承担。

messengermaster.js的constructor阶段创建,其作用指向便是消息传输,在源码注释里文件里面有个图

 * master messenger,provide communication between parent, master, agent and app.
 *
 *             ┌────────┐
 *             │ parent │
 *            /└────────┘\
 *           /     |      \
 *          /  ┌────────┐  \
 *         /   │ master │   \
 *        /    └────────┘    \
 *       /     /         \    \
 *     ┌───────┐         ┌───────┐
 *     │ agent │ ------- │  app  │
 *     └───────┘         └───────┘

作为调用端,也就是当前请求startCluster的对象,拥有对process的动作监听,其便是(parent),在egg-clusterindex.js里创建了master,在master中依靠接下来的顺序创建了agentapp,于是四者便连同在一起。在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

先放出一张启动顺序的消息传输图片,记录下启动顺序(非全量记录)

msg_event.jpg

按照这个顺序,我们详述

0. 放出探针(detect-port),获取可用端口

1. (序号1 --> 3)端口获取成功 发送’agent-start‘消息内容

和上面贴出代码一致,this.forkAgentWorker()方法,会调用入child_processfork文件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消息在AgentApp(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);
  }
}

整个构造方法做了四件事

  1. 完成父类构建、[seq1]
  2. 驱动loader执行load方法,[seq2]
  3. dump相关配置文件进入./run目录下、[seq3]
  4. 监听异常事件。 [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)

  1. super方法 [seql1.1]
  2. 驱使loader.loadConfig() [seq1.2]
  3. events的ready事件 [seq1.3]
  4. cluster的初始化 [seq1.4]

我们先跳过seq1.1(不然量太大了,扛不住哈哈),回来后补,

seq1.2关于loader.loadConfig()是loader的mixin里面config.js(曾经的我年轻了,这里有个指向问题,指向的其实不是egg-core.loader,需要先观察创建这个对象有没有相关指向,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差不多,具体可以产分成

  1. 父类构建(之前浅谈过了,这里就先跳过啦)
  2. loader的load方法 (Step.2)
  3. dumpConfig方法(Step.3)
  4. 触发 WARN_COUFUSED_CONFIG(Step.4)
  5. 触发 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的监听逻辑。未完待续

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容