示例代码
下面代码使用了 Express 中间件、路由、并监听了 3000 端口,接下来我们就深入 Express 源码来看看都发生了什么。
'use strict'
var express = require('../../');
var app = module.exports = express()
app.use(function myMiddle(req, res, next) {
console.log('Request URL:', req.url);
console.log('App instance:', req.app); // 访问应用程序实例
next();
}, function myMiddle2(req, res,next) {
console.log('myMiddle2')
next();
});
app.get('/', function middleware1(req, res, next) {
console.log('Middleware 1');
next();
},function helloWorld(req, res){
res.send('Hello World');
});
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
初始化
express.js 入口文件
调用 express() 后就进入 express.js 执行了 createApplication 函数。
主要点
- 初始化 app 对象
- 通过 Object.definePropert(destination, name, descriptor) 将 EventEmitter.prototype 和 application 自身的属性和方法设置到 app 上。
- 调用 app.init() 初始化。因为 mixin(app, proto, false); 将 application 中 app 上的方法都挂载到 app 上了。
var bodyParser = require('body-parser')
var EventEmitter = require('events').EventEmitter;
var mixin = require('merge-descriptors');
var proto = require('./application');
var Router = require('router');
var req = require('./request');
var res = require('./response');
exports = module.exports = createApplication;
function createApplication() {
// 创建 app 函数对象
var app = function(req, res, next) {
app.handle(req, res, next);
};
// 将 EventEmitter.prototype 自身的属性都设置到 app 函数上
mixin(app, EventEmitter.prototype, false);
// 将 application 对象自身的属性都设置到 app 函数上
mixin(app, proto, false);
// 设置请求对象 (req) 和响应对象 (res) 的原型
// 以便在处理请求和响应时,这些对象可以访问应用程序实例 (app)
app.request = Object.create(req, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
// expose the prototype that will get set on responses
app.response = Object.create(res, {
app: { configurable: true, enumerable: true, writable: true, value: app }
})
// 调用 application 中的 init 函数
app.init();
return app;
}
mixin 方法实际调用如下,将遍历出入的 EventEmitter.prototype、和 proto 将他们自身的属性设置为 app 属性的描述符。
application.js
先粗略看 application.js 文件结构,主要做的就是在 app 对象上挂载一系列的方法。接下来我们挑几个比较核心的方法看下它的实现。
app.init()
在 app 上设置 router 访问器描述符,拦截 app.router 属性访问。每当访问时判断是否创建了 router 实例。单利模式如果没有则创建,没有则返回 router 对象。
app.init = function init() {
var router = null;
this.cache = Object.create(null);
this.engines = Object.create(null);
this.settings = Object.create(null);
this.defaultConfiguration();
// Setup getting to lazily add base router
Object.defineProperty(this, 'router', {
configurable: true,
enumerable: true,
get: function getrouter() {
if (router === null) {
router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabled('strict routing')
});
}
return router;
}
});
};
app.use()
- this.router 访问 app.router 触发访问器描述符,创建 router 对象。
- 调用 router.use(path, fn) 这里使用了第三方的库。这里需要注意的是 app.use() 中间件本身是没有 path的,这里默认跟它传了一个 '/' 。
app.use = function use(fn) {
var offset = 0;
var path = '/';
var fns = flatten.call(slice.call(arguments, offset), Infinity);
if (fns.length === 0) {
throw new TypeError('app.use() requires a middleware function')
}
// get router
var router = this.router;
fns.forEach(function (fn) {
// non-express app
if (!fn || !fn.handle || !fn.set) {
return router.use(path, fn);
}
debug('.use app under %s', path);
fn.mountpath = path;
fn.parent = this;
// restore .app property on req and res
router.use(path, function mounted_app(req, res, next) {
var orig = req.app;
fn.handle(req, res, function (err) {
setPrototypeOf(req, orig.request)
setPrototypeOf(res, orig.response)
next(err);
});
});
// mounted an app
fn.emit('mount', this);
}, this);
return this;
};
- 下面代码从 2-21 行之间是为了获取参数 arguments 中回调函数的部分
- 然后是遍历回调函数,将 callbacks 回调函数作为 实例化 Layer 的 handle 的参数
- layer.route = undefined app.use() 方式创建的 layer 对象是没有关联的 route,因为他只有一个回调函数,不需要 route 去维护一个 stack 回调数组
- 将 layer 实例 push 到 router.stack,注意的是 router.stack 是外层的栈,可以简单理解为大循环,route.stack 可以理解为小循环
Router.prototype.use = function use (handler) {
let offset = 0
let path = '/'
// default path to '/'
// disambiguate router.use([handler])
if (typeof handler !== 'function') {
let arg = handler
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0]
}
// first arg is the path
if (typeof arg !== 'function') {
offset = 1
path = handler
}
}
const callbacks = flatten(slice.call(arguments, offset))
if (callbacks.length === 0) {
throw new TypeError('argument handler is required')
}
for (let i = 0; i < callbacks.length; i++) {
const fn = callbacks[i]
if (typeof fn !== 'function') {
throw new TypeError('argument handler must be a function')
}
// add the middleware
const layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn)
layer.route = undefined
this.stack.push(layer)
}
return this
}
app.get()
- method 可以是 get、post 等,所以 app.get()、 app.post() 调用这里的方法
- this.route 代理调用的 app.route() -> router.route() 由于层级比较多我就不一一贴代码了
methods.forEach(function(method){
app[method] = function(path){
if (method === 'get' && arguments.length === 1) {
// app.get(setting)
return this.set(path);
}
var route = this.route(path);
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
所以最终实际调用的是 Router.prototype.route 方法,主要做了以下几件事
- 创建 route 对象,用于维护路由匹配处理函数,这里使用 stack 数组来存储,因为可以添加多个如:app.get('/', fn1, fn2, fn3)
- 创建 layer 对象,标识 end=true,并且将 route.dispatch 函数作为 handle 传给 new Layer
- route.dispatch 就是用于遍历 route.stack 执行下一个栈中的 layer 的,后面会分析
- 将 layer.route = route 将 layer 和 route 建立关系
- 将 layer 存到 router.stack 大循环中,这样可以通过 layer 找到 route,然后继续执行 route.stack 中的小循环
继续分析 route[method].apply(route, slice.call(arguments, 1)); 调用。前面创建了 route 对象,这里直接调用了 route.get() 方法。
调用此方法做了以下几件事
- 创建内存 layer 对象
- 将 this.methods[method] = true 就是标识请求方法
- 将 layer 存放在 route.stack 中,至此内外循环,router 和 route 以及相关的 layer 都已经创建完毕
app.listen()
使用 http 创建 server 并监听了 3000 端口等待客户端请求
请求接口
next 函数的实现
为了方便理解,可以先看下 next() 函数的伪代码
app.get('/', function middleware1(req, res, next) {
console.log('Middleware 1');
next(); // 执行 next 函数继续匹配 route.stack 中 layer
},function helloWorld(req, res){
res.send('Hello World');
});
Route.prototype.dispatch = function dispatch (req, res, done) {
const stack = this.stack // 路由中相关的回调函数
function next (err) { // 定义 next 函数,遍历 stack
const layer = findLayerInStack(); // 匹配 stack 中的 layer
layer.handleRequest(req, res, next); // 执行 layer 处理请求函数
}
}
Layer.prototype.handleRequest = function handleRequest (req, res, next) {
const fn = this.handle; // 取出回调函数
fn(req, res, next); // 执行回调函数,即执行 middleware1 函数
}
Express 中 router 依赖中定义了两个 next 函数分别是在 Router.prototype.handle 中定义了内部函数 next(),以及在 Route.prototype.dispatch 定义了内部函数 next() 。他们分别的作用是遍历 router.stack 中的 layer 和遍历 route.stack 中的 layer 。所以它们的实现原理都是一样的。取一个出来举例:
Route.prototype.dispatch = function dispatch (req, res, done) {
let idx = 0 // 记录遍历的 layer 的序号
const stack = this.stack // route.stack 所有中间件回调函数
let sync = 0 // 记录当前同步调用的层数,防止中间件调用过程中出现无限递归, 每次调用 next 都会累加1
if (stack.length === 0) { // 这里的 done() 是外部的 next() 函数
return done()
}
let method = typeof req.method === 'string'
? req.method.toLowerCase()
: req.method
if (method === 'head' && !this.methods.head) {
method = 'get'
}
req.route = this
next()
function next (err) {
// signal to exit route
if (err && err === 'route') { // 执行外层 next(),匹配下一个路由
return done()
}
// signal to exit router
if (err && err === 'router') {
return done(err)
}
// no more matching layers
if (idx >= stack.length) {
return done(err)
}
// max sync stack
if (++sync > 100) { // 限制一次路由匹配中最多调用 100 次 回调函数,剩余的会放到下一个tick
return defer(next, err)
}
let layer
let match
// find next matching layer
// 查找 route.stack 中符合的 layer
// 每次调用 next() match 都会会被重置,重新查找下一个,idx 闭包变量则会持续更新
while (match !== true && idx < stack.length) {
layer = stack[idx++]
match = !layer.method || layer.method === method
}
// no match
if (match !== true) {
return done(err)
}
if (err) {
layer.handleError(err, req, res, next)
} else {
// 执行具体的回调函数,注意这里把 next 函数传递给回调函数
layer.handleRequest(req, res, next)
}
sync = 0
}
}
next 调用流程
总结
对 Express 初始化流程,以及响应过程中 next() 函数是如何工作的做了详细的讲解。关于 Express 整体脉络梳理可以查看我之前的文章 # Express 源码分析-脉络梳理。