Express
基于 Node.js 平台,快速、开放、极简的 web 开发框架
安装
//应用生成器工具npm install express-generator -g//创建express应用包express app//安装依赖npm install
成功生成后,会产生以下的目录和文件:
|---bin|---node_module|---public|---routes|---view|---app.js|---package.json
接下来我们通过:
npm start
启动程序后,访问127.0.0.1:3000,就能访问到express的页面了。
接下来通过研究源码,来探讨express路由原理的实现。
路由
我们通过查看app.js和index.js文件:
app.js
var index = require('./routes/index');app.use('/', index);//或app.get('/', index);
routes/index.js
var express = require('express');var router = express.Router();router.get('/', function(req, res, next) { res.render('index', { title: 'Express' });});
可以看出,express的路由大概实现 定义一份路由规则文件,再通过app.use()或者app[METHOD]来建立路由规则访问联系,虽然两者的结果一样,但是存在本质上的区别。
下图是主要涉及的几个文件:
接下来我们通过源码首先看看app.use()具体是一个什么样实现思路。
app.use
我们打开node_module里的express文件夹。打开lib/application.js文件。
app.use = function use(fn) { var offset = 0; var path = '/'; // default path to '/' // disambiguate app.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 fns = flatten(slice.call(arguments, offset)); if (fns.length === 0) { throw new TypeError('app.use() requires middleware functions'); } // setup router 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); return this;};
看到use里部分的代码,开始做了判断处理use挂载的是路径还是function,并且通过lazyrouter()方法实例router类,并且全局只存在一个router实例对象,最终调用router.use()方法。
接着,我们到lib/router/index.js 看router.use方法的实现:
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 middleware functions'); } for (var i = 0; i < callbacks.length; i++) { var fn = callbacks[i]; if (typeof fn !== 'function') { throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn)); } // add the middleware debug('use %o %s', path, fn.name || '') var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; this.stack.push(layer); } return this;};
通过对比app.use方法,router.use前半部分处理相同,但后面实例化一个Layer类,并且丢进stack里。
Layer类保存Router和Route一些数据信息:
相同点:
path都是存放挂载路径,options.end用来判断是否是路由中间件。
不同点:
Router和Route的区别是一个是添非路由中间件,另一个是添加路由中间件。
他们的layer.route指向也不一样,一个指向undefined,另一个没有route属性。
文章进行到一半,我们小总结一下,app.use()方法是用来添加非路由中间件的,最终是调用router实例方法,会实例划一个Layer类对象用于存放数据,并且把layer对象push进router.stack里,全局只有一个router。
app[METHOD]
我们通过源码去探讨路由中间件app[METHOD]是一个怎样的原理:
在app.js把app.use('\',index)
改成app.get('\',index)
.
application.js:
methods.forEach(function(method) { app[method] = function(path) { if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; };});
可以看出,代码里做了一个app.get方法的判断处理,get方法只有一个参数时,是获取app的本地变量,后面还是实例化router对象,并且用router上的route方法放回的对象去调用Route类上的route[METHOD].
/lib/router/route.js
methods.forEach(function(method){ Route.prototype[method] = function(){ var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; if (typeof handle !== 'function') { var type = toString.call(handle); var msg = 'Route.' + method + '() requires callback functions but got a ' + type; throw new Error(msg); } debug('%s %o', method, this.path) var layer = Layer('/', {}, handle); layer.method = method; this.methods[method] = true; this.stack.push(layer); } return this; };});
在route里有一个实例化的layer,且放在stack里,与Router的layer不同的是,Route的没有layer.route且layer.method存放http方法。
到这里,我们大概可以总结下路由中间件和非路由中间件的联系,如下图:
app初始化时,会push两个方法(init,query)进router.stack里。我们可以通过app.use往app添加非路由中间件,也可以通过app[METHOD]添加路由中间件,同样是push layer实例对象,但route是指向Route实例化的对象。
完整的Router逻辑过程,如图:
总结
- express中添加中间件方法有app.use和app[METHOD],当然还有内置的Router类,app.use用来添加非路由中间件,app[METHOD]用来添加路由中间件。
- Layer类封装中间的path和handle(fns的处理)
- Router和Route都有对应的stack,但是Route在整个app中只有一个,而Route可以又多个。放在Router stack里的路由中间件,通过Layer.route指向Route,与Route stack相关联起来