Express源码分析
1. 引入并调用express()时发生了什么?
- 使用
const express = require("express")
时, 默认会去寻找express模块下面的index.js
文件 - 在
index.js
中, 我们发现:
module.exports = require('./lib/express');
- 即引用了lib目录下面的
express.js
文件 - 在
express.js
中, 首行:exports = module.exports = createApplication
, 即默认导出了createApplication
这个函数 - 再看一下
createApplication
:
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, EventEmitter.prototype, false);
mixin(app, proto, false);
// expose the prototype that will get set on requests
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 }
})
app.init();
return app;
}
createApplication
中返回了一个Express示例, 即我们调用express()
时, 等价于createApplication()
2. app.listen(port, host, callback)
执行时, Express底层做了哪些事情?
- 在创建Express实例时, 我们发现app的实例上只挂载了request和response两个对象, 并没有listen方法, 其实是通过
mixin(app, proto, false)
挂载的, proto是一个对象, 在application.js
中定义的 - 在
application.js
找到listen方法, 源码如下:
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
- 原来是调用了NodeJS原生的http模块, 创建了一个server, 并开启了监听,
http.createServer
中的this指向app, 等价于如下:
app.listen = function listen() {
var server = http.createServer(function(req, res, next) {
app.handle(req, res, next);
});
return server.listen.apply(server, arguments);
};
3. 当使用中间件 app.use(middleware)
时Express做了什么事情?
app.use = function use(fn)
, fn的形式是一个函数- function中会调用
this.lazyrouter();
, 来初始化路由, 源码如下:
//application.js
app.lazyrouter = function lazyrouter() {
if (!this._router) {
this._router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabled('strict routing')
});
this._router.use(query(this.get('query parser fn')));
this._router.use(middleware.init(this));
}
};
var fns = flatten(slice.call(arguments, offset));
if (fns.length === 0) {
throw new TypeError('app.use() requires a middleware function')
}
this.lazyrouter();
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);
- 原来, 调用use使用中间件时, Express会创建一个router实例,然后获取中间件数组, 遍历所有的中间件匹配和当前参数相同的那个中间件, 最后通过router去处理, 因此, 更深一步的细节在
router.use()
这个方法里面 - router.use的源码如下: 在
router/index.js
下面
proto.use = function use(fn) {
var offset = 0;
var path = '/';
// default path to '/'
// disambiguate router.use([fn])
if (typeof fn !== 'function') {
var arg = fn;
while (Array.isArray(arg) && arg.length !== 0) {
arg = arg[0];
}
// first arg is the path
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
var callbacks = flatten(slice.call(arguments, offset));
if (callbacks.length === 0) {
throw new TypeError('Router.use() requires a middleware function')
}
for (var i = 0; i < callbacks.length; i++) {
var fn = callbacks[i];
if (typeof fn !== 'function') {
throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
}
// add the middleware
debug('use %o %s', path, fn.name || '<anonymous>')
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn);
layer.route = undefined;
this.stack.push(layer);
}
return this;
};
- 可以看到:
- 首先判断了参数
- 拿到use中所有注册的回调函数
- 遍历所有回调, 如果有不是函数类型的参数则抛出错误,并把每一个回调函数变成一个layer对象
- 把layer放入栈中
4. 用户发送了http请求, 注册的中间件如何被执行?
- 由于启动了服务, 所有的请求都会被监听到, 因此会执行监听时的回调:
var server = http.createServer(function(req, res, next) {
app.handle(req, res, next);
});
- 来看app.handle这个方法: 其实就是初始化了一个router, 并执行了
router.handle()
app.handle = function handle(req, res, callback) {
var router = this._router;
// final handler
var done = callback || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
// no routes
if (!router) {
debug('no routes defined on app');
done();
return;
}
router.handle(req, res, done);
};
- 在看
router.handle()方法:
简单来看, 就是初始化了一些参数, 并取出全局的stack, 调用next()
var self = this;
debug('dispatching %s %s', req.method, req.url);
var idx = 0;
var protohost = getProtohost(req.url) || ''
var removed = '';
var slashAdded = false;
var paramcalled = {};
// store options for OPTIONS request
// only used if OPTIONS request
var options = [];
// middleware and routes
var stack = self.stack;
// manage inter-router variables
var parentParams = req.params;
var parentUrl = req.baseUrl || '';
var done = restore(out, req, 'baseUrl', 'next', 'params');
// setup next layer
req.next = next;
// for options requests, respond with a default if nothing else responds
if (req.method === 'OPTIONS') {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
sendOptionsResponse(res, options, old);
});
}
// setup basic req values
req.baseUrl = parentUrl;
req.originalUrl = req.originalUrl || req.url;
next();
- 因此, 需要看一下next的具体实现:
function next(err) {
var layerError = err === 'route'
? null
: err;
// remove added slash
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
// restore altered req.url
if (removed.length !== 0) {
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '';
}
// signal to exit router
if (layerError === 'router') {
setImmediate(done, null)
return
}
// no more matching layers
if (idx >= stack.length) {
setImmediate(done, layerError);
return;
}
// get pathname of request
var path = getPathname(req);
if (path == null) {
return done(layerError);
}
// find next matching layer
var layer;
var match;
var route;
while (match !== true && idx < stack.length) {
layer = stack[idx++];
match = matchLayer(layer, path);
route = layer.route;
if (typeof match !== 'boolean') {
// hold on to layerError
layerError = layerError || match;
}
if (match !== true) {
continue;
}
if (!route) {
// process non-route handlers normally
continue;
}
if (layerError) {
// routes do not match with a pending error
match = false;
continue;
}
var method = req.method;
var has_method = route._handles_method(method);
// build up automatic options response
if (!has_method && method === 'OPTIONS') {
appendMethods(options, route._options());
}
// don't even bother matching route
if (!has_method && method !== 'HEAD') {
match = false;
continue;
}
}
// no match
if (match !== true) {
return done(layerError);
}
// store route for dispatch on change
if (route) {
req.route = route;
}
// Capture one-time layer values
req.params = self.mergeParams
? mergeParams(layer.params, parentParams)
: layer.params;
var layerPath = layer.path;
// this should be done for the layer
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(layerError || err);
}
if (route) {
return layer.handle_request(req, res, next);
}
trim_prefix(layer, layerError, layerPath, path);
});
}
-
其实next()无非就是做了一下几件事情
- 之前用户在注册中间件时, 需要执行的回调以layer对象的形式存储在了全局的一个stack中,因此在响应时, 需要找到与路径匹配的layer
- 找到对应的layer之后, 调用
layer.handle_request(req, res, next)
, 最终由layer处理响应
-
最终响应的执行:
layer.handle_request()
,源码如下:
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
- 在该方法中:
- 判断handle参数的个数
- 如果参数没问题, 就执行用户传进来的回调函数
- 若出现错误, 则调用执行错误的中间
5. next()为什么能调用下一个中间件?
- stack中的idx++, 会再去stack中寻找下一个匹配的中间件调用!