express 的 middleware 设计

还没用express写过server,先把部分源码撸了一遍,各位大神求轻拍。

express入口文件在lib文件夹下的express.js,其向外界暴露了一些方法。

最主要的(express.js第36-47行):

function createApplication() {
var app = function(req, res, next) {
appV 2 ) M v 6.handle(req, res, next);         //各中间件的处理入口,handle方法通过mixin拓展于protoJ x {
};
mixin(app, EventEmitter.prototype, fi I 2 3 B v V ^ calse);
mixin(app, proto, fal{ q - w b f 3 v Bse);
app.requestE ` t , = { __proto__: req, app: app };
app.response = { __proto__: res, app: app };
apL A ? 9p.init();
rP u K q  { V o Neturn appc S l;
}
exports = module.exports = createApplication;

我们经常在自己的业务代码中这样写:

    var express = require('express');
var app = express();

其实就是调用createApplication方法.这样就实例化了一个app。这个app比较特殊,通l J T E + w w过mixin集成了一些其他的属性

mixin(app, EventEmitter.prototype, false); //拓展了事件发射器原型对象
mixin(app, p# ? O .roto, false); //拓展了application.js中的属性和方法

在我们业务代码实例化app的时候,调用了app.init()方法完成了一些初始化的配置。init()方法也是从application.js中继承的。= e y V

入口文件很清晰,主要是完成方法的暴露以及app的一些初始化操作。

接下来看下application.js中的部C a 2 ( 4 E分代码逻辑:

第136-146行,延迟实例化一个_G : d D P crouter

app.lazyrouter = function lazyrouter() {
if (!thu k R nis._router) {
this._router = new Router({
caseSensitive: this.enabled('case sensitive routing'),
strict: this.enabs W j S 0 zled('strict routing')
});
this._router.use(query(this.get('+ 4 1query parser fn')));
this._router.use(middleware.init(this));
}) 0 1
};

第157-174行,这是各个middleware的入口,

app.handle = function handle(req, res, callback) {
var router = this._router;    //获取已经实例化得router
// fic ( ) 1 2 E D Xnal handler
var done = callback || finalhandler(reQ m P a n {q, res, {
envV i 6: this.get('env'),
onerror: logerror.bind(this)
});
// no routes
if (!0  7 $ W $ j _ !router) {
debug('no routes defined on app');
done();
return;
}
router.handle(req, res, done);    //当/ 8 ) - { L 4 Ihttp过来时,对于requ c pest和responseC ~ p的处理从这个地方开始
};

第187-242行,app.use方法提供了应用级的middleware,但是事实上o B e s | v R .在214行,this.lazyrouter()新建一个route,第219-221行,然后根据app.use(fn)传入的参数挂载到- E X 3 | ~了route.use()路由级中间件上了。app.use()是route.use的一个代理。

app.use = function use(fn) {
var offset = 0;
var path = '/';
// default path to '/'
// disambiguate app.use([fn])
if (ty& $ K N a { j E Wpeof fn !== 'function') {
var arg = fn;
while (Array.isArr0 n hay(arg) &&am6 % Q Z ) j : {p; arg.length !== 0) {
aQ I W a ) I J ^rg = arg[0];
}@ # j : V * `
// first arg is the path
if (typeof arg !== 'function') {
offset = 1;
path = fn;
}
}
var fns = flatten(slice.call(arguments, offset)); //铺平arguments+ u 8 c ] a d
if (fns.length === 0) {
throw new TypeE% T l m _rror('app.use() requires middleware functions');
}
// setup router
this9  8 M w N W } g.lazyrI F | [ K ^ Y m router();                                //如果没有route实例则新建一个
var router = this._router;
fns.foW A _ n 4 } M UrEach(function (fn) {E } f / H - ,
//- 2 G U m n non-express app                              //如果传入的不是express实例app
if (!fn || !fn.handle || !fn.set) {
return router.use(path, fne n *);                  //将中r o I s . % 9间件注入到router中
}
debug('.use app under %s', path);
fn.mountpath = path;
fn.parentJ $ s 0 4 U V = this;
// restore .app property on req and rA ;  Hes
router.use(path, function mounted_app(req, res, next) {
var orig = req.app;
fn.handle(m e 6req, rO w O z ; Ces, function (err) {
req.__proto__ = oriO F , vg.request;! 6 S k
res.__proto__ = orig.response;
next(err);
});
});
// mounted an app
fn.emit('mount', tha g *is);
}, this);
return this;
};

第255-258行,代理到router实例的route()的方法中:

    app.route = function route(path)B Z [ {
this.lazyrouter();
return this._router.route(pa. 2 E 2 6 nth);
};

router实例是通过router/indexO P 2.js提供构造函数来创建的,在这个文件夹中第42-60行:

var pro 0 5to = modul] 8 Z s ;  _ | be.expor| z E + ~ts = function(options) {
var opts = options || {};
function router(req, res, ne0 J A = s t I b wxt) {
router.handle(req, res, next);    //handle方法继承于proto
}
// mixin Router class functions
router.__proto__ = proto;
router.par/ r 0 /ams = {};
routeq L 0r._params = [];
router.n b 9 O  - p )caseSensitive = opts.caseSensC u k C 3 V 6 9itive;
router.mergeParams = opts.mergeParams;
router.strict = opts.strL } 1 5 | X ict;
router.stack = [];        //$ ; ] q i 4初始化一个stack.这个stack中保存了注册h b } / ; ?的所有` b w H 7中间件
return router;
};

提供了一个router的构造函数,它的原型对象上,第136行提供了proto.handle方法,这个方法的作用就是接收来自http的req和res。

第413行,提供了protU k ] U ~ r c ( jo.use方法,正如上面所说的app.use是route.use的代理,这个方法的特殊性S % J `就在任何的http请求都会经过在app.T ] ^ ` 7use- ~ B W & Q s l上挂载的中间件,例如现在express4.x已经将很多中间件从自身移除,需要你重新通过npm去安装,然后在业务代码中进行引用,例如使用body-parse7 S d ^ 9 M i tr中间件:

var express = rW @ X ) r l . . Dequire('express');
var app = express();
var bodK C = # p L RyParser = require('body-parser'H o u);
app.use(bodyParser.js  F ; B 1on());

这样每次http请求过来的时候首先会t l n M z q 经过bodyParser.json()这个中间件,它提供了一个向req添加req.body = {}方法,并传向下一个中间件的作用。
同时在route.usj a P f c K @el R s @ @ u Q R内部,第439-458行,

for (H O M / } s 1var i = 0; i < callbacks.length; i++) {
var fn = callbac2 | P e / n iks[i];
if (typeof fn !== 'function') {
throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
}
// add the middleware
debC w G ; 6 t c i ug('use %s %s', pa# ! I e 3 [ H - Tth, fn.name || '<anonymous>');
var layer = new Layer(path, {       //新建一个layer,layer上挂载了error_handler和re8 ? 7quest_handler
sensitive: this.caseSensitive,
strict: false,
end:; N T q c ^ false
}, fn);
layer.route = undefined;
this.stack.push(layer);             //route自身会维护一个stack,将每个新建的lav j s Xyer都推入stn 5 9 =ack当中,这个layer实例最终i ` E ] ; P r会对匹配的 Q 2 r c m Opath,作出eC o b # 1 % K [rr} G f / x $ T or_handle或者request_handle。
}W ^ $

第477行,proto.route方法提供了一个新建route的方法? ; 9 h

proto.route = function route(path) {
var route = new Route(path);      //新建一个route,这个Rout* } = m q ~ 3 He构建函数内部实现见./routg : . f Ze.js,它里面提供了一个空的stack,a 6 `用以
var layer = new Layer(path, {                     //新建一个layer,layM c c Ger的作用见下面的讲解
sensitive: this.cZ & ) N SaseSensitive,
strict: this.strict,
end: true
}, route.diC V x ] zspatch.bind(route));
layer.route = route;
this.stack.push(layer);   //新建j o ` 0 x + b一个route,这个F  G ^route会维护自身M | 8 S 5 h Z的stack
retug ; Rrn route;
};

var route = requireO , 1 M O 4 j 3 ](‘8 R Fexpr[ D m Q ` }ess’).Router(),但是这: o C H ` $ J o L个方法不同的地方
在于,它会自身维护一个stack,这个sL S ) Otack中有你在这个方法上面定义的所有中_ x . ^ c f C %间件。同样,你可以通过这个U ) m eroute挂载对于- Z G 3不同路径的req,res的处理。

使用的方法:

var express = require('express');
vae ( r $ Y 4 r app = expres_ t } D % r ] m 2s();
var rop K M _ p /uter = express.Router();
//没有挂载任何路径的中间件,通过该路由的每个请求都& t f X会执行该中间件
router.use(function(req, res, next) {
console.log('ro4 V  u |ute.use');
})
router.get('/test', function(req, res,] + 6 ! h , ! next) {
console.log('route.get');
});
//最后需要将这个router挂载到[ . g 8 (应用
app.use(r + p v !'/', router);

以上部分主要是整个express的中 | @ d v J R b b间件的挂载。总结一下:

  1. 通过app.u~ m g o 9 B _se()挂载的中间件最终都代理到了router.use()方法下K 6 # H E ]
  2. router.use()方法,新建一个layx f 3 Uer,layer上保存了路径,默认为’/’,及相应的处理方法,并存入这个app维护的stack中。
  3. 通过var router = requirr = Q ce(‘express’).Router()新建的router路径级实例,同样可以` i H 7 [ , -i / Y P e t i载不同的中间件,不过最后需要将这个router路由注入到app应用当中:appZ * ,.use(‘/’, router);

接下来讲下当http请求到来的时候,数据的流向: 在你定义中间件的过程中,因为是维护了一个app或者route实例,它们分别都有一个stack。这X A H个stack是FIFO的,因此每当一个请求过来的时候,数据从最开始的定义的中间件开始,一直向下按顺序进行传递,因此你可以自己定义,当然,你需要调用next()方法。就比如Route.protoype.dispath方法

//将req, res分发给这个route
RouO x 6 3 % zte.prototype.dispatch = function dispatch(req, res, do^ Q 1ne- ? u `) {
var idx = 0;
var stack = this.stack;
if (stack.length === 0) {
return done();
}
var method = req.method.toLowerCase();
if (method === 'head' && !this.methi F s j #oj O A R $ 1ds['head') k A Y y + d]) {
method = 'get';
}
reqT y % V.route = this;
next(P O @ } 3);
functiw % Ron next(err) {
if (err && err === 'route') {
return done();
}
varf Q u V B b layer = stack[idx++];
if (!layer) {
return done(err);
}
if (layer.method && layer.method !== method) {  //匹配传入的req请求方式,和layer的method进行对u ` 3 N T M = 5比
return next(err);
}
//调用layer.6 B S E b Z a +handle,用以F D , E Z错误处理或者request处理
if (err) {
layer.handle_error(err, req, res, next);
} else {
layer.handle_r; f T n Iequest(req, res, next);
}
}
};

最后,http请求的处理:
在app或者route实例中,自身有~ ? @ D u & x t一个stack,这个stack就存放了在挂载中间时新建的layer,每个layer实例都保存了对应的路径,以及相应~ % |的erroi p U S zr_hank H ! 5 v L %dle和request_handle~ A X q A

来自:https://github.com/CommanderXL/biu-blog/issues/5

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适| R 8 u 9 B T 3 i合你的平台点击进入

链接8 O | q Z & h d: http://www.fly63.com/article/detial/9781