Express 是一种保持最低程度规模的灵活 Node.js Web 应用程序框架,为 Web 和移动应用程序提供一组强大的功能。本文针对该框架进行一些简单的源码解读。
Express中的app
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, EventEmitter.prototype, false); mixin(app, proto, false); app.request = Object.create(req, { ... }); app.response = Object.create(res, { ... }); app.init(); return app; }
exports = module.exports = createApplication;
|
可以看出,调用express()返回的app其实是一个函数,调用app.listen()其实执行的是http.createServer(app).listen()。因此,app其实就是一个请求处理函数,作为http.createServer的参数。而express其实是一个工厂函数,用来生成请求处理函数。
中间件
An Express application is essentially a series of middleware calls.
一个Express应用实际上就是一系列中间件的调用。
中间件大致可分为两种,一种是普通中间件,通过 app.use('/user')
方法进行注册,该方法中的路径是匹配所有以 /user
开头的路径;另外一种是路由中间件,通过 app.METHOD()
方法进行注册,这种方式精确匹配路径,且只能处理确定请求方法的请求。
针对app.use部分的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| app.use = function use(fn) { var offset = 0; var path = '/'; if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } if (typeof arg !== 'function') { offset = 1; path = fn; } }
this.lazyrouter(); var router = this._router; var fns = flatten(slice.call(arguments, offset)); fns.forEach(function (fn) { if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); }
debug('.use app under %s', path); fn.mountpath = path; fn.parent = this;
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); }); });
fn.emit('mount', this); }, this);
return this; };
|
针对app.METHODS的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| methods.forEach(function(method){ app[method] = function(path){ if (method === 'get' && arguments.length === 1) { return this.set(path); }
this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }; });
|
路由详解
在上面两种中间件的实现代码中,都调用了 this.lazyrouter()
方法,所有涉及到路由的方法都会调用这个方法,作用是初始化一个应用的内部路由。
1 2 3 4 5 6 7 8 9 10 11
| 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)); } };
|
针对第一个默认路由,可以看下具体调用了哪些方法,最终实现了什么逻辑:
1 2 3 4 5 6 7
| this.get('query parser fn');
case 'query parser': this.set('query parser fn', compileQueryParser(val)); break;
|
针对第二个默认路由 init,是给 app 上的暴露出的 req、res 继承 node 原生的 request 和 response 的一些属性。
之所以不在 defaultConfiguration
方法中进行这一步路由的初始化,原因在于设置路由的相关参数需要调用app.set方法,这个方法明显需要有app实例,如果在获取app实例的时候就初始化了一个路由,这个路由的参数就没办法配置了。因此,在获取app实例后,必须先对路由参数进行配置,然后再调用对应的app.use等方法。
app.Methods
我们先以 app.get
为例,通过断点调试的方式来查看 app.get
这种路由的 _router
对象是什么结构。
上面的截图是当我们初始化一个 express 实例,并设置了一个 app.get()
的路由后,在 app.listen 处添加断点进行调试时的 app 实例的属性。可以看到在 app._router
中有一个 stack,里面按顺存放着三个 layer 对象,分别是初始化时的 query 和 init 两个路由,和第三个则是我们所设置的 get 路由。每一个 layer 中包含路由处理的回调函数 handle
,路由对象 route<Route>
,该对象中还包含一个存有 Layer 对象的栈(stack),就和 app._router.stack
相同。
根据上面对于 app.get
这种路由的结构分析,我们可以先猜想它的实现流程:
app.use
接下来我们来看 app.use
这种路由又有什么不同。
可以看到,在这种路由中,除了 query
和 init
两个初始化路由的方法外,第三个真正的回调方法被封装成的 Layer
对象中,route
属性的值为 undefined
。
所以我们可以猜想:在 app.use
这种路由里,传入的参数(路径、回调函数)会被封装成 Layer
对象(其中 route
属性为 undefined
),压入 app._router.stack
栈中。
两种路由的源码实现
接下来我们通过源码来分析我们写的 app.use('/main', someFun())
是如何成为一个 layer 对象并压入 app._router
的路由栈中的。
首先来看 app.use
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| app.use = function use(fn) { var path = '/'; this.lazyrouter(); var router = this._router;
fns.forEach(function (fn) { router.use(path, function mounted_app(req, res, next) { }); }); }, this);
return this; };
|
接下来看 Router 对象的 use 方法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| proto.use = function use(fn) { var path = '/'; var callbacks = flatten(slice.call(arguments, offset)); for (var i = 0; i < callbacks.length; i++) { var fn = callbacks[i]; var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; this.stack.push(layer); } return this; };
|
从代码中可以看到,与我们的猜想一致,app.use
这种路由的实现,是将传入的一个个路径或回调方法等参数封装成 Layer
对象压入 _router.stack
栈中。
然后来看 app.get
这种路由的代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| methods.forEach(function(method){ app[method] = function(path){ if (method === 'get' && arguments.length === 1) { return this.set(path); }
this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }; });
|
上面代码中最终返回的是 app
,那么这端代码对 app
进行了什么样的操作呢?我们可以将目光聚焦在 route[method].apply()
这行代码上。接下来我们可以看下 route
到底是个什么东西。this._router.route
的代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| proto.route = function route(path) { var route = new Route(path);
var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer); return route; };
|
这里可以看到返回的 route
是一个 Route
对象,在这个对象中将参数封装成了 Layer
对象推入了 app._router.stack
中。
接下来我们看一下 Route
对象中的一些实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function Route(path) { this.path = path; this.stack = []; this.methods = {}; }
methods.forEach(function(method){ Route.prototype[method] = function(){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var layer = Layer('/', {}, handle); layer.method = method;
this.methods[method] = true; this.stack.push(layer); } return this; }; });
|
至此,在调用 route[method].apply()
时就会为 app.METHODS
这种类型的路由的 Layer
中添加 route<Route>
属性。