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 || '<anonymous>')
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相关联起来