adonisjs是一个node.js的web框架,主要在web安全领域做的比较好一点,所以俺研究了一下adonisjs的源码,
server启动过程分析
基础模块
- core 核心模块,处理命令 ,查看app状态
- httpserver http服务模块,负责与子进程的通讯
- assember 官方的命令实现,如server等,
- 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