title: express4.2源码解析 date: 2014-05-18 15:00:50 categories: express tags: [nodejs, node, js, express]
express是nodejs平台上一个非常流行的框架,4.2.0是最新的版本,相比3.x版本优化了代码和api,去除了connect模块,自己实现了一个router组件,实现http请求的顺序流程处理,去除了很多绑定的中间件,使代码更清晰。
##1.使用express 如何使用express在官网有很好的讲解,只用experssjs实例app的几个函数,就可以构建构建web程序。
var express = require('express');
var logger = require('morgan');
var app = express();
//app.engine('html', require('ejs').renderFile);
app.use('/public',express.static(__dirname + '/public'));
app.use(logger());
app.get('/', function(req, res){
res.send('Hello World');
});
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});
上面是一个简单的web程序,返回浏览器hello world,就几个步骤,获取express实例对象,加入需要的中间件,加入路由响应,启动服务器,很简单吧,相比java,.net的框架轻量了很多,而且不需要单独架设web服务器,利用nodejs的异步非阻塞机制,可以大大提高网站的并发量。
###1.1中间件
app.use
加入中间件,所谓中间件其实就是java,.net平台MVC框架都会有的filter。
app.use(["path"],function(req,res,next){})
有两个参数,path代表route路径,可选,为空表示匹配所有路径,后面是回调函数,需要添加一个next参数,执行时,框架将传入一个next函数,调用它启动下一个中间件,下面是一个中间件的示例
app.use('/public',express.static(__dirname + '/public'));
app.use(logger());
app.use(function(req, res, next){
console.log('hello middleware');
next();
});
app.get('/', function(req, res){
res.send('Hello World');
});
运行结果为:
"C:\Program Files\nodejs\node.exe" index.js Listening on port 3000 hello middleware 127.0.0.1 - - [Fri, 16 May 2014 05:16:27 GMT] "GET / HTTP/1.1" 200 11 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36"
我们添加了一个自定义的中间件,打印出 hello middleware 从上面我们可以看出app.use
把中间件加入一个栈中,http request将触发整个中间件链条,并依次执行(通过 next()
函数实现),功能类似于filter,但其作用却大于filter,它可以动态地给req,res添加内容。margon是一个日志记录的包,记录每个request的信息。express.static()
是express保留的唯一个内置中间件,对/pulic路径下的route导向静态资源文件,不调用next。这样中间件就可以实现对指定或所有路径request和response的处理。
###1.2 app.get()/app.VERB() app.get有两个功能,第一次看express文档时都会很疑惑,app.get可以获取app.set设置的全局变量,也可以设置路由的处理函数,下面是get实现的源码,对js不是很熟悉的人会很纠结,代码里找不到get函数啊,app.get和app['get']的方式都可以定义对象的函数,下面是其实现的源码。
>application.js
/**
* Delegate `.VERB(...)` calls to `router.VERB(...)`.
*/
methods.forEach(function(method){
app[method] = function(path){
if ('get' == method && 1 == arguments.length) return this.set(path);
this.lazyrouter();
var route = this._router.route(path);
route[method].apply(route, [].slice.call(arguments, 1));
return this;
};
});
methods是一个数组,存储了http所有请求的类型,在method模块里定义,除了基本的get、port请求外,还有多达十几种请求,可能是为了兼容新的http标准吧。app[method]
中,method=='get'且只有一个参数,则执行set,执行的是获取变量的功能,否则,执行app.get('path',function(req,res){})
中path对应的回调函数,执行route组件的get方法(实现方式和这里一样),将route和回调存储进一个栈中。http请求触发执行,app.get也将产生一条路由中间件,执行后返回浏览器html页面。
module.exports = [ 'get', 'post', 'put', 'head', 'delete', 'options', 'trace', 'copy', 'lock', 'mkcol', 'move', 'purge', 'propfind', 'proppatch', 'unlock', 'report', 'mkactivity', 'checkout', 'merge', 'm-search', 'notify', 'subscribe', 'unsubscribe', 'patch', 'search' ];
##2.了解express4.2的结构
下面是express4.2的文件结构图:
-
express.js和application.js是主要的框架文件,暴露了express的api。
-
router文件夹下为router组件,负责中间件的插入和链式执行,具体讲解在下一章节。
-
middleware下的init.js和query.js为两个中间件,init.js的作用是初始化request,response, 看一下代码就能明白:
exports.init = function(app){ return function expressInit(req, res, next){ if (app.enabled('x-powered-by')) res.setHeader('X-Powered-By', 'Express'); req.res = res; res.req = req; req.next = next; req.__proto__ = app.request; res.__proto__ = app.response; res.locals = res.locals || Object.create(null); next(); }; };
query.js中间件的作用是格式化url,将url中的rquest参数剥离,储存到req.query中:
```js
module.exports = function query(options){
return function query(req, res, next){
if (!req.query) {
req.query = ~req.url.indexOf('?')
? qs.parse(parseUrl(req).query, options)
: {};
}
next();
};
};
```
- request.js和response.js, 提供了一些方法丰富request和response实例的功能,在init.js中初始化了http的req和res实例。
req.\__proto__ = app.request;res.\__proto__ = app.response;
- view.js提供模板渲染引擎的封装,通过
res.render()
调用引擎渲染网页,具体请看第五章
##3.Router组件
Router组件由三个文件组成,index.js为主文件,route.js主要功能是路由处理,layer保存中间件的数据结构,Router组件实例化后的对象如下图所示,stack
为中间件栈:这是第一章里代码执行时的对象结构图,我们可以看到route存储了五个中间件,包含两个默认的query和expressInit组件:
{ [Function: router]
params: {},
_params: [],
caseSensitive: false,
strict: false,
stack:
[ { keys: [], regexp: /^\/?(?=/|$)/i, handle: [Function: query] },
{ keys: [],
regexp: /^\/?(?=/|$)/i,
handle: [Function: expressInit] },
{ keys: [],
regexp: /^\/public\/?(?=/|$)/i,
handle: [Function: staticMiddleware] },
{ keys: [], regexp: /^\/?(?=/|$)/i, handle: [Function: logger] },
{ keys: [], regexp: /^\/?(?=/|$)/i, handle: [Function] } ]
}
下面是Route的实例,stack
为其http.verb
的method
和响应函数对, 如下图所示,”/"为一条路由的路径,接受method为get的http请求。
{ path: '/',
stack: [ { method: 'get', handle: [Function] } ],
methods: { get: true }
}
- index.js主要处理中间件的执行,包括中间的插入,错误处理,执行(handle)等
- route.js主要处理路由信息,每条路由都会生成一个Route实例,通过index.js里的
proto.route(path)
方法可以创建一个path对应的Route实例,并封装在layer中,加入中间件栈。另外Route.get (Route['get']) 方法也是在这里动态生成的。
- layer.js是中间件的存储结构。
看
router.stack
的最后一条,发现它的handle是一个无名的function,看了源码你就会知道,这个无名funtion就是路由'/'对应的处理函数,每条路由都会作为一个中间件加入栈中。
我们每次调用app.get()
就新建了一个Route实例(见1.2节代码),调用链条为app['get']=>router.Route['get']
。
如下代码,调用Route['get']
,Route中将会将get标示加入self.methods中,防止重复定义,然后生成一个数据项加入self.stack,数据项{ method: 'get', handle: [Function] }
含method标示和路由处理函数fn。
在1.2节代码中,app.get()
函数将Route实例封装在layer中,作为一个中间件加入栈中,当触发执行时,会将处理函数fn取出执行。
>route.js
methods.forEach(function(method){
Route.prototype[method] = function(){
var self = this;
var callbacks = utils.flatten([].slice.call(arguments));
callbacks.forEach(function(fn) {
if (typeof fn !== 'function') {
var type = {}.toString.call(fn);
var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
throw new Error(msg);
}
debug('%s %s', method, self.path);
if (!self.methods[method]) {
self.methods[method] = true;
}
if (!self.stack) {
self.stack = [];
}
else if (typeof self.stack === 'function') {
self.stack = [{ handle: self.stack }];
}
self.stack.push({ method: method, handle: fn });
});
return self;
};
##4.中间件触发流程 ###4.1主要过程 中间件触发通过以下代码:
>express.js
function createApplication() {
var app = function(req, res, next) {
app.handle(req, res, next);
};
mixin(app, proto);
mixin(app, EventEmitter.prototype);
app.request = { __proto__: req, app: app };
app.response = { __proto__: res, app: app };
app.init();
return app;
}
express模块返回一个app作为http.createServer()
的回调函数,这样一个http请求将触发执行app.handle()
执行中间件,下面我们看看app.handle()
的代码:
app.handle = function(req, res, done) {
var env = this.get('env');
this._router.handle(req, res, function(err) {
if (done) {
return done(err);
}
// unhandled error
if (err) {
// default to 500
.........
return;
}
// 404
debug('default 404');
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html');
if ('HEAD' == req.method) return res.end();
res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '\n');
});
};
app.handle()
调用了router组件的handle(req,res,fn)
函数执行中间件,链式执行完所有中间件后,done
函数是定义的错误处理函数,在htpp.createServer(function(res,req,done)
中传入,下面将讲述express的核心route组件。
###4.2 router组件
router组件主要有三个文件组成,index.js和route.js是其主要逻辑部分,layer.js作为中间件封装的数据结构。
下面的代码是route生成http.verb的函数:
methods.forEach(function(method){
Route.prototype[method] = function(){
var self = this;
var callbacks = utils.flatten([].slice.call(arguments));
callbacks.forEach(function(fn) {
if (typeof fn !== 'function') {
var type = {}.toString.call(fn);
var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
throw new Error(msg);
}
debug('%s %s', method, self.path);
if (!self.methods[method]) {
self.methods[method] = true;
}
if (!self.stack) {
self.stack = [];
}
else if (typeof self.stack === 'function') {
self.stack = [{ handle: self.stack }];
}
self.stack.push({ method: method, handle: fn });
});
//console.log(self)
return self;
};
1.2节中,application.js里的methods.each调用的就是这里生成http.verb处理函数,Reoute实例化的时候就生成了对应http.verb的处理函数(Route['method'])。
代码里可以看出,http.verb可以一次添加多个处理函数,形式为
function(req,res,next)
或者function(req,res)
.
下面是router组件构建的函数:
var proto = module.exports = function(options) {
options = options || {};
function router(req, res, next) {
router.handle(req, res, next);
}
// mixin Router class functions
router.__proto__ = proto;
router.params = {};
router._params = [];
router.caseSensitive = options.caseSensitive;
router.strict = options.strict;
router.stack = [];
return router;
};
router是一个对象构造函数,router.__proto__ = proto
引入了整个proto的所有函数,包括use,handle等待相关中间件操作函数,定义了中间件储存的数组,配置等。
router完全可以作为一个对象定义为
var router ={}
,源码里,发现router没有进行实例化,所以这个构造函数式没有必要的。
下面我们看看router是如何触发链式执行的:
proto.handle = function(req, res, done) {
.............
// middleware and routes
var stack = self.stack;
// request-level next
var parent = req.next;
done = wrap(done, function(old, err) {
req.next = parent;
old(err);
});
req.next = next;
.............
next();
function next(err) {
.........
}
}
这里主要展示了中间件执行的过程,每调用一次next()
就会有一个中间件触发,并再一次调用next(),看next里的代码:
if (route) {
return layer.handle(req, res, next);
}
layer是一个保存中间件路径,处理函数的数据结构,想详细了解请看源码,上面的代码表示如果路径和这个中间件配置的路径匹配,则执行其回调,next就是我们第一章开头讲的,下一个中间件触发的函数,现在应该知道这个函数从哪来了吧?
递归执行next,直到执行完为止,执行done,但是我们会发现done一直就是null,不知道这是不是老的nodejs遗留下来的问题,createServer的回调现在不会传入第三个参数了。
var layer = stack[idx++];
if (!layer) {
return done(err);
}
下面看看route.js 是如何运行的吧
#router/index.js
proto.route = function(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.dispatch
作为一个中间件加入了栈中,并返回一个Route实例,Route实例包含了路由处理的各种方法和信息,其中route.dipatch
也是其原型函数,用来处理相同路由的不同http.verb
,下面我们看看这个函数。
Route.prototype.dispatch = function(req, res, done){
var self = this;
var method = req.method.toLowerCase();
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
req.route = self;
// single middleware route case
if (typeof this.stack === 'function') {
this.stack(req, res, done);
return;
}
var stack = self.stack;
if (!stack) {
return done();
}
//这里进行递归地链式调用,遍历所有的处理函数
var idx = 0;
(function next_layer(err) {
if (err && err === 'route') {
return done();
}
var layer = stack[idx++];
if (!layer) {
return done(err);
}
if (layer.method && layer.method !== method) {
return next_layer(err);
}
var arity = layer.handle.length;
if (err) {
if (arity < 4) {
return next_layer(err);
}
try {
layer.handle(err, req, res, next_layer);
} catch (err) {
next_layer(err);
}
return;
}
if (arity > 3) {
return next_layer();
}
try {
layer.handle(req, res, next_layer);
} catch (err) {
next_layer(err);
}
})();
};
这个函数很长,主要过程可以简单叙述一下,也是通过next_layer函数,链式访问一条路由的post,get等方法的回调函数,根据req.method
来判断请求类型,执行相应处理函数,不同http.verb可以执行不同回调,也就是说,express一条路由可以响应多种类型的请求。但是注意到,这样回调函数应该写成function(req, res, next){ ..... next()}
。
讲了很多,估计大家都昏头了,下面的流程图会很清晰的让大家知道整个过程。
##5.View的实现
4.x版本的render和3.x版本不一样,这里以回调的方式进行render,而不在内部调用res.send()
示例:
res.render('index', function(err, html){
....
res.send(html);
});
res.render('user', { name: 'Tobi' }, function(err, html){
// ...
});
res.render()
的实现比中间件简单很多,总体来说,经过三次封装,进行了一些配置,调用链条为res.render() => app.render() =>view.render()=> require("jade")/reqiure("ejs").render()
,首先看app.engine
,将jade或ejs模板引擎的render函数存入了engines数组中
app.engine = function(ext, fn){
if ('function' != typeof fn) throw new Error('callback function required');
if ('.' != ext[0]) ext = '.' + ext;
this.engines[ext] = fn;
return this;
};
app.defaultConfiguration()
(app初始化的一个函数),把View的构造函数保存。
// default configuration
this.set('view', View);
app.render()
将其取出并调用,初始化一个View实例,并执行‘view.render()’渲染模板,注意初始化函数将engines传入了View实例,里面保存了模板引擎的render函数。
view = new (this.get('view'))(name, {
defaultEngine: this.get('view engine'),
root: this.get('views'),
engines: engines
});
.....
try {
view.render(opts, fn);
} catch (err) {
fn(err);
}
view.render()
执行的便是模板引擎的render函数,fn为渲染完成后的回调函数。
View.prototype.render = function(options, fn){
this.engine(this.path, options, fn);
};