adonisjs是一个node.js的web框架,主要在web安全领域做的比较好一点,所以俺研究了一下adonisjs的源码,

server启动过程分析

基础模块

  1. core 核心模块,处理命令 ,查看app状态
  2. httpserver http服务模块,负责与子进程的通讯
  3. assember 官方的命令实现,如server等,
  4. application 实际运行的应用,ioc挂载在上面,application.container

node ace serve 命令调用流程

先初始化一个core.ignitor对象

在core.kenel里面处理声明文件(manifest.json),然后加载command对象,

然后开始启动application,这里具体的分析放到下面,启动完application之后会执行命令

server 命令会初始化一个assembler下的httpServer ,

new DevServer().start()
// 声明了一个httpServer 
this.httpServer = new HttpServer.HttpServer()
this.httpServer.start() // 这里主要用于接收子进程的消息

start里面会拉起一个子进程 ,用于启动http服务

// 这里是子进程执行server.ts 并且预加载register, 注册了 实例
this.childProcess = execa.node('server.ts', [], {
      buffer: false,
      stdio: 'inherit',
      cwd: this.projectRoot,
      env: {
        FORCE_COLOR: 'true',
        ...this.env,
      },
      nodeOptions: ['-r', '@adonisjs/assembler/build/register'].concat(this.nodeArgs),
    })
// server.ts
new Ignitor(__dirname).httpServer().start()
// start
this.createServer(serverCallback)
this.monitorHttpServer()
await this.listen()
//  createServer 
import {createServer} from 'http'
const handler = this.server.handle.bind(this.server)
this.server.instance = createServer(handler)
// listen instance就是官方库的http
this.application.start()
this.server.instance.listen()

从子进程拿到消息后 打印启动成功

this.childProcess.on('message', (message) => {
            if (message && message['origin'] === 'adonis-http-server') {
                this.emit('ready', message);
            }
        });

子进程会拉起实际的http服务

node -r register server.js

在register中 注册了一个require的hook函数,将 '@adonis:ioc' 的包 替换为 global['use']
global['use']的注册 在app启动过程中会注册

application启动过程分析

在constructor中先声明了rcfile, 即 .adonisjsrc.json, 声明了预加载文件和providers (这里也是ioc不过是外部包的注入),并且设置环境变量,全局ioc属性,吧application注册到ioc,注册好helper

目前主要预加载 全局中间件和路由

app 启动流程 内部维护了一个 state

流转过程为

initiated 初始化

⇒ setup 加载配置 环境变量 注册 logger等

⇒ registered 加入providers

⇒ booted 启动provider

⇒ 加载预加载文件

⇒ ready 启动成功

处理命令时会调用wire, 实际代码如下,这里就是app的流转过程

await this.application.setup();
await this.application.registerProviders();
await this.application.bootProviders();
await this.application.requirePreloads();
// requirePreloads
this.resolveModule(node.file)
// resolveModule
require(path)

请求生命周期

实际最后处理逻辑在 http-server/server/index 下的handle 函数 handle 是通用函数,可以用在其它的httpServer里面

可以看到每一次请求都是一个新的context对象

// request初始化主要做了参数解析
const request = new Request.Request(req, res, this.encryption, this.httpConfig);
const response = new Response.Response(req, res, this.encryption, this.httpConfig, this.router);
// 这里做个一个请求观察器
const requestAction = this.getProfilerRow(request);
const ctx = this.getContext(request, response, requestAction);
// getContext 可以看到每一个request都是带了id的
getContext(request, response, profilerRow) {
        return new HttpContext.HttpContext(request, response, this.application.logger.child({
            request_id: request.id(),
        }), profilerRow);
    }
this.handleRequest(ctx, requestAction, res)
// handleRequest 
await this.runBeforeHooksAndHandler(ctx);
await this.hooks.executeAfter(ctx);
requestAction.end({ status_code: res.statusCode });
ctx.response.finish();

实际上处理请求分两个函数,before 和after,

before函数 先执行before的hook函数

// 先执行before hooks
return this.hooks.executeBefore(ctx).then((shortcircuit) => {
            if (!shortcircuit) {
                return this.requestHandler.handle(ctx);
            }
});

然后找到请求的对应处理路由,加载中间件,将最后处理函数放到最后,然后开始执行中间件


// 然后实际处理开始 这里的middlware都是从ioc容器中取出来
this.requestHandler =  new RequestHandler(this.middleware, this.router)
// requestHandler
async handle(ctx) {
				// 这里对路由进行匹配,写入参数 
        this.findRoute(ctx);
        return this.handleRequest(ctx);
}
// handleRequest 
this.globalMiddleware = new co_compose.Middleware().register(middleware);
        this.handleRequest = (ctx) => {
            return this.globalMiddleware
                .runner()
                .executor(this.executeMiddleware)
                .finalHandler(ctx.route.meta.finalHandler, [ctx])
                .run([ctx]);
}

中间件的run函数执行, 实现的灰常优雅,在内部实现了队列,调用next之后,会等待后面的所有中间件执行完毕,完成了洋葱模型

// run 函数 
private invoke() {
    const fn = this.list[this.index++]
    if (!fn) {
      return this.registeredFinalHandler.fn(...this.registeredFinalHandler.args)
    }
    return this.executorFn(fn, this.params)
 }
// 这里写的很好,简洁而又有效,
// next 函数就是这里的invoke 动态调用下一个函数
// run 函数
this.params = params.concat(this.invoke.bind(this))
return this.invoke()

再来看看executeAfter ,和before里面类似

for (let hook of this.hooks.after) {
    await hook(ctx);
}

ioc处理

先看如何注入

使用了@inject的类 他的constructor都会被写入inject 属性,

if (!target.constructor.hasOwnProperty('inject')) {
            Object.defineProperty(target.constructor, 'inject', {
                value: {},
            });
        }
target.constructor.inject[propertyKey] = target.constructor.inject[propertyKey] || [];

以route中的函数依赖注入为代表

// 这里的handler实际上是一个string,
Route.post('/login', 'AuthController.login') 
// 这里在路由提交完之后,调用了optimize函数,其中compileHandler 这里将实际处理函数替换
compileHandler(route) {
			this.resolver = container.getResolver(undefined, 'httpControllers', 'App/Controllers/Http');
      route.meta.resolvedHandler = this.resolver.resolve(route.handler, route.meta.namespace);
}
// 这里将router的resolvedHandler 注册好了之后,去找真正的处理函数
{
	type: 'alias',
	method: 'get',
	namespace: 'App/Controllers/Http/ProductController',
}
this.runRouteHandler = async (ctx) => {
            const routeHandler = ctx.route.meta.resolvedHandler;
             await this.resolver.call(routeHandler, undefined, [ctx]);
};
// 核心在于 这里的call
// routeHandler {type: 'alias', namespace: 'App/Controllers/Http/ProductController', method: 'list'}
// 这里实际上 是先makeAsync 实例化这个对象然后在调用 callSync, 最后是这个方法和参数
public async call(
    namespace: string | IocResolverLookupNode<string>,
    prefixNamespace?: string,
    args?: any[]
  ): Promise<any> {
    const lookupNode =
      typeof namespace === 'string' ? this.resolve(namespace, prefixNamespace) : namespace
    return this.container.callAsync(
      await this.container.makeAsync(lookupNode.namespace),
      lookupNode.method,
      args
    )
  }
// makeAsync 这里实际上就是执行了
async makeAsync(target, method, args) {
	return await target[method](args)
}
require(path)
// makeAsync 这里直接new这个对象,并且把依赖注入进去
return new target(...(await this.resolveAsync(target.name, this.getInjections(target, 'instance'), runtimeValues)))
// getInjections 直接判断这个对象是否有inject prop是instance
return target.constructor.hasOwnProperty('inject') ? target.inject[prop] || [] : [];
// resolveAsync 这里是一个递归调用
return Promise.all(injections.map((injection, index) => {return this.container.makeAsync(injection);}));

adonisjs模块的ioc怎么做的

在上面的启动中 会先预加载 @adonisjs/assembler/build/register,

然后会调用adonisjs/require-ts包, 在adonisjs/application 中启动的时候会注册

global[Symbol.for('ioc.use')] = this.container.use.bind(this.container);
global[Symbol.for('ioc.make')] = this.container.make.bind(this.container);
global[Symbol.for('ioc.call')] = this.container.call.bind(this.container);

然后在compile中注册一个require的hook, 然后通过adonisjs/ioc-transformer会将代码的require修改

import Encryption from '@ioc:Adonis/Core/Encryption' 
// 转化如下 然后 gloabal 是在app生命周期里面的registerGlobal中修改的
require(global['ioc.use']('Adonis/Core/Encryption'))

性能优化问题

在上面启动过程中 会执行

this.server.optimize();
// server 是由adonisjs/http-server注入
this.server = this.application.container.use('Adonis/Core/Server')

最后实际的optimize代码

/**
     * Optimizes internal handlers, based upon the existence of
     * before handlers and global middleware. This helps in
     * increasing throughput by 10%
     */
    optimize() {
        this.router.commit();
        this.hooks.commit();
        this.requestHandler.commit();
    }

链接 http://assets.processon.com/chart_image/61bdc70b1e08534302e9addd.png

链接 http://assets.processon.com/chart_image/61bdce8a1efad41dea251ac1.png

adonisjs 启动类加载顺序

链接 http://assets.processon.com/chart_image/61bf1f026376892b1df6547f.png