閱讀express的感悟

在github上看了半天的源碼,也是雲裏霧裏,勉強也算看完了,經過查看不少人的講解也方便了個人理解,今天記錄下來,也算是作個筆記。node

進入express的源碼文件裏咱們能夠看到8個文件:middleware  router  application.js   express.js   request.js   response.js   utils.js   view.jsgit

官網給出一個最簡單的例子:github

 1 var express = require('express');  
 2 var app = express();  
 3   
 4 app.get('/', function (req, res) {  
 5   res.send('Hello World!');  
 6 });  
 7   
 8 var server = app.listen(3000, function () {  
 9   var host = server.address().address;  
10   var port = server.address().port;  
11   
12   console.log('Example app listening at http://%s:%s', host, port);  
13 });  

讓咱們看看它背後是怎麼實現的:web

首先找到入口文件,見名知意,打開express.js,咱們看到這樣的代碼:express

 1 function createApplication() {  
 2   //建立app對象  
 3   var app = function(req, res, next) {  
 4     app.handle(req, res, next);  
 5   };  
 6   //繼承node的事件對象  
 7   mixin(app, EventEmitter.prototype, false);  
 8   //繼承./application對象  
 9   mixin(app, proto, false);  
10   //app.request和response繼承node原生的request和response對象  
11   app.request = { __proto__: req, app: app };  
12   app.response = { __proto__: res, app: app };  
13   //初始化app對象  
14   app.init();  
15   return app;  
16 }  

app.init()方法調用的是繼承自./application.js的方法。npm

 

下面是application.js中的init方法:json

 

 1 /**
 2  * 初始化服務器
 3  *
 4  *   -  設置默認配置
 5  *   - 設置默認中間件
 6  *   - 設置默認映射路由方法
 7  *
 8  * @private
 9  */
10 
11 app.init = function init() {
12   this.cache = {};
13   this.engines = {};
14   this.settings = {};
15 
16   this.defaultConfiguration();
17 };

 1  //初始化app的默認配置 
 2 app.defaultConfiguration = function defaultConfiguration() {
 3   var env = process.env.NODE_ENV || 'development';
 4 
 5   // default settings
 6   this.enable('x-powered-by');
 7   this.set('etag', 'weak');
 8   this.set('env', env);
 9   this.set('query parser', 'extended');
10   this.set('subdomain offset', 2);
11   this.set('trust proxy', false);
12 
13   // trust proxy inherit back-compat
14   Object.defineProperty(this.settings, trustProxyDefaultSymbol, {
15     configurable: true,
16     value: true
17   });
18 
19   debug('booting in %s mode', env);
20  //監聽mount事件,當咱們向express中添加中間件的時候會觸發mount事件,  
21   //這裏會將每一箇中間件的request對象,response對象,engines對象,settings對象經過__proto__造成原型連,  
22   //最頂層的request和response對象是Node原生的request和response對象,在createApplication中定義  
23   this.on('mount', function onmount(parent) {
24     // inherit trust proxy
25     if (this.settings[trustProxyDefaultSymbol] === true
26       && typeof parent.settings['trust proxy fn'] === 'function') {
27       delete this.settings['trust proxy'];
28       delete this.settings['trust proxy fn'];
29     }
30 
31     // inherit protos
32     this.request.__proto__ = parent.request;
33     this.response.__proto__ = parent.response;
34     this.engines.__proto__ = parent.engines;
35     this.settings.__proto__ = parent.settings;
36   });
37 
38   // setup locals
39   this.locals = Object.create(null);
40 
41   // top-most app is mounted at /
42   this.mountpath = '/';
43 
44   // default locals
45   this.locals.settings = this.settings;
46 
47   // default configuration
48   this.set('view', View);
49   this.set('views', resolve('views'));
50   this.set('jsonp callback name', 'callback');
51 
52   if (env === 'production') {
53     this.enable('view cache');
54   }
55 
56   Object.defineProperty(this, 'router', {
57     get: function() {
58       throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.');
59     }
60   });
61 };

express()分析結束。api

 1 app.get('/', function (req, res) { 2 res.send('Hello World!'); 3 });  數組

該api的做用是建立到"/"的get請求的路由處理器,app.get/post/head等方法在application文件中的下述代碼中定義:服務器

 1 methods.forEach(function(method) {  
 2   app[method] = function(path) {  
 3     if (method === 'get' && arguments.length === 1) {  
 4       //get方法特殊處理,只有一個參數的時候,獲取app.settings[path]
 5       return this.set(path);  
 6     }  
 7   
 8     //給app對象綁定一個路由管理器Router  
 9     this.lazyrouter();  
10   
11     //使用路由管理器給指定的path建立一個路由對象處理對象  
12     var route = this._router.route(path);  
13     //調用路由處理對象的相應方法,即:去除第二個參數,做爲處理程序,並傳入route[method]  
14     route[method].apply(route, slice.call(arguments, 1));  
15     return this;  
16   };  
17 });
 原來,這些方法都是動態添加的。methods是一個數組,裏面存放了一系列web請求方法,以上方法經過對其進行遍歷,給app添加了與請求方法同名的一系列方法,即:app.get()、app.post()、app.put()等,
在這些方法中,首先經過調用lazyrouter實例化一個Router對象,而後調用this._router.route方法實例化一個Route對象,最後調用route[method]方法並傳入對應的處理程序完成path與handler的關聯。

在這個方法中須要注意如下幾點:
  1. lazyrouter方法只會在首次調用時實例化Router對象,而後將其賦值給app._router字段
  2. 要注意Router與Route的區別,Router能夠看做是一箇中間件容器,不只能夠存放路由中間件(Route),還能夠存放其餘中間件,在lazyrouter方法中實例化Router後會首先添加兩個中間件:query和init;而Route僅僅是路由中間件,封裝了路由信息。Router和Route都各自維護了一個stack數組,該數組就是用來存放中間件和路由的。


這裏先聲明一下,本文提到的路由容器(Router)表明「router/index.js」文件的到導出對象,路由中間件(Route)表明「router/route.js」文件的導出對象,app表明「application.js」的導出對象。
Router和Route的stack是有差異的,這個差異主要體如今存放的layer(layer是用來封裝中間件的一個數據結構)不太同樣,




因爲Router.stack中存放的中間件包括但不限於路由中間件,而只有路由中間件的執行纔會依賴與請求method,所以Router.stack裏的layer沒有method屬性,而是將其動態添加(layer的定義中沒有method字段)
到了Route.stack的layer中;layer.route字段也是動態添加的,能夠經過該字段來判斷中間件是不是路由中間件。  

   能夠經過兩種方式添加中間件:app.use和app[method],前者用來添加非路由中間件,後者添加路由中間件,這兩種添加方式都在內部調用了Router的相關方法來實現:
 1 //添加非路由中間件
 2 proto.use = function use(fn) {
 3   /* 此處略去部分代碼
 4    其實咱們不關注path是什麼,默認是"/",固然他也會經過參數去
 5  查找path,可是咱們關注的是layer的生成,還有他的.route屬性。
 6 
 7  */
 8   callbacks.forEach(function (fn) {
 9     if (typeof fn !== 'function') {
10       throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn));
11     }
12     // add the middleware
13     debug('use %s %s', path, fn.name || '<anonymous>');
14     ////實例化layer對象並進行初始化
15     var layer = new Layer(path, {
16       sensitive: this.caseSensitive,
17       strict: false,
18       end: false
19     }, fn);
20     //非路由中間件,該字段賦值爲undefined
21     layer.route = undefined;
22     this.stack.push(layer);
23   }, this);
24   return this;
25 };
26 
27 //添加路由中間件
28 proto.route = function(path){
29   //實例化路由對象
30   var route = new Route(path);
31   //實例化layer對象並進行初始化
32   var layer = new Layer(path, {
33     sensitive: this.caseSensitive,
34     strict: this.strict,
35     end: true
36   }, route.dispatch.bind(route));
37   //指向剛實例化的路由對象(很是重要),經過該字段將Router和Route關聯來起來
38   layer.route = route;
39   this.stack.push(layer);
40   return route;
41 };

對於路由中間件,路由容器中的stack(Router.stack)裏面的layer經過route字段指向了路由對象,那麼這樣一來,Router.stack就和Route.stack發生了關聯,關聯後的示意模型以下圖所示:

 

在運行過程當中,路由容器(Router)只會有一個實例,而路由中間件會在每次調用app.route、app.use或app[method]的時候生成一個路由對象,在添加路由中間件的時候路由容器至關因而一個代理,
Router[method]其實是在內部調用了Route[method]來實現路由添加的,路由容器中有一個route方法,至關因而路由對象建立工廠。經過添加一個個的中間件,在處理請求的時候會按照添加的順序逐個調用,
若是遇到路由中間件,會逐個調用該路由對象中stack數組裏存放的handler,這就是express的流式處理,是否是有點相似asp.net中的管道模型,調用過程以下圖所示:







   咱們按順序來看下這3行源碼:
  this.lazyrouter(); 
 var route = this._router.route(path); 
 route[method].apply(route, slice.call(arguments, 1));  


 1 /**
 2  * lazily adds the base router if it has not yet been added.
 3  *
 4  * We cannot add the base router in the defaultConfiguration because
 5  * it reads app settings which might be set after that has run.
 6  *
 7  * @private
 8  */
 9 app.lazyrouter = function lazyrouter() {
10   if (!this._router) {
11     this._router = new Router({
12       caseSensitive: this.enabled('case sensitive routing'),
13       strict: this.enabled('strict routing')
14     });
15 
16     this._router.use(query(this.get('query parser fn')));
17     this._router.use(middleware.init(this));
18   }
19 };

那Router是什麼? 咱們先看下Router模塊的構造函數:在./router/index.js文件中:

 1 var proto = module.exports = function(options) {
 2   var opts = options || {};
 3 
 4   function router(req, res, next) {
 5     router.handle(req, res, next);
 6   }
 7 
 8   // mixin Router class functions
 9   router.__proto__ = proto;
10 
11   router.params = {};
12   router._params = [];
13   router.caseSensitive = opts.caseSensitive;
14   router.mergeParams = opts.mergeParams;
15   router.strict = opts.strict;
16   router.stack = [];
17 
18   return router;
19 };

這裏是個知識點,其實這個router就至關於this,咱們new出一個對象也就3個部分:

  1. 建立一個空對象,而且 this 變量引用該對象,同時還繼承了該函數的原型。
  2.  
    屬性和方法被加入到 this 引用的對象中。
  3.  
    新建立的對象由 this 所引用,而且最後隱式的返回 this 。

 

proto是Router的原型對象,上面有不少方法,稍後分析,咱們再看

this.lazyrouter();後的

var route = this._router.route(path);
咱們來看一下在./router/index.js文件中定義的route方法:
 1 proto.route = function route(path) {  
 2     
 3   //建立並初始化route對象  
 4   var route = new Route(path);  
 5   
 6   //建立一個layer對象,並放入棧中,  
 7   //在處理業務的時候會根據path的匹配規則,來匹配對應的route,  
 8   //若是是使用use接口(好比添加中間件的時候),是不指定route的,  
 9   var layer = new Layer(path, {  
10     sensitive: this.caseSensitive,  
11     strict: this.strict,  
12     end: true  
13   }, route.dispatch.bind(route));  
14   
15   layer.route = route;  
16   
17   this.stack.push(layer);  
18   console.log("app.get(path,cb) return route:" + JSON.stringify(route));  
19   return route;  
20 };  

 

注意!在route方法裏咱們用了route模塊,注意和Router的區別,稍後總結。咱們先去在./router/route.js中看看Route的構造方法:

1 function Route(path) {
2   this.path = path;
3   this.stack = [];
4 
5   debug('new %s', path);
6 
7   // route handlers for various http methods
8   this.methods = {};
9 }

不知道你們注意到沒有,Router模塊初始化有一個stack數組,route模塊也有一個stack數組:Router模塊的stack是裝的Layer,咱們去./router/layer.js下看Layer模塊:

 1 function Layer(path, options, fn) {
 2   if (!(this instanceof Layer)) {
 3     return new Layer(path, options, fn);
 4   }
 5 
 6   debug('new %s', path);
 7   var opts = options || {};
 8 
 9   this.handle = fn;
10   this.name = fn.name || '<anonymous>';
11   this.params = undefined;
12   this.path = undefined;
13   this.regexp = pathRegexp(path, this.keys = [], opts);
14 
15   if (path === '/' && opts.end === false) {
16     this.regexp.fast_slash = true;
17   }
18 }

上邊是Layer的構造函數,咱們能夠看到這裏定義handle,params,path和regexp等幾個主要的屬性:

  1. 其中最重要的就是handle,它就是咱們剛剛在route中建立Layer對象傳入的中間件函數。
  2. params其實就是req.params,至於如何實現的咱們能夠之後再作探討,今天先不作說明。
  3. path就是咱們定義路由時傳入的path。
  4. regexp對於Layer來講是比較重要的一個屬性,由於下邊進行路由匹配的時候就是靠它來搞定的,而它的值是由pathRegexp得來的,其實這個pathRegexp對應的是一個第三方模塊path-to-regexp,它的功能是將path轉換成regexp,具體用法你們能夠自行查看。
上邊的代碼中在建立Layer對象的時候傳入的handle函數爲route.dispatch.bind(route),咱們來看看route.js中的route.dispatch:
 1 Route.prototype.dispatch = function dispatch(req, res, done) {
 2   var idx = 0;
 3   var stack = this.stack;
 4   if (stack.length === 0) {
 5     return done();
 6   }
 7 
 8   var method = req.method.toLowerCase();
 9   if (method === 'head' && !this.methods['head']) {
10     method = 'get';
11   }
12 
13   req.route = this;
14 
15   next();
16 
17   function next(err) {
18     if (err && err === 'route') {
19       return done();
20     }
21 
22     var layer = stack[idx++];
23     if (!layer) {
24       return done(err);
25     }
26 
27     if (layer.method && layer.method !== method) {
28       return next(err);
29     }
30 
31     if (err) {
32       layer.handle_error(err, req, res, next);
33     } else {
34       layer.handle_request(req, res, next);
35     }
36   }
37 };

咱們發現dispatch中經過next()獲取stack中的每個layer來執行相應的路由中間件,這樣就保證了咱們定義在路由上的多箇中間件函數被按照定義的順序依次執行。到這裏咱們已經知道了單個路由是被如何執行的,那咱們定義的多個路由之間又是如何被依次執行的呢,如今咱們來看看index.js中的handle函數:

 
  1 proto.handle = function handle(req, res, out) {
  2   var self = this;
  3 
  4   debug('dispatching %s %s', req.method, req.url);
  5 
  6   var search = 1 + req.url.indexOf('?');
  7   var pathlength = search ? search - 1 : req.url.length;
  8   var fqdn = req.url[0] !== '/' && 1 + req.url.substr(0, pathlength).indexOf('://');
  9   var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
 10   var idx = 0;
 11   var removed = '';
 12   var slashAdded = false;
 13   var paramcalled = {};
 14 
 15   // store options for OPTIONS request
 16   // only used if OPTIONS request
 17   var options = [];
 18 
 19   // middleware and routes
 20   var stack = self.stack;
 21 
 22   // manage inter-router variables
 23   var parentParams = req.params;
 24   var parentUrl = req.baseUrl || '';
 25   var done = restore(out, req, 'baseUrl', 'next', 'params');
 26 
 27   // setup next layer
 28   req.next = next;
 29 
 30   // for options requests, respond with a default if nothing else responds
 31   if (req.method === 'OPTIONS') {
 32     done = wrap(done, function(old, err) {
 33       if (err || options.length === 0) return old(err);
 34       sendOptionsResponse(res, options, old);
 35     });
 36   }
 37 
 38   // setup basic req values
 39   req.baseUrl = parentUrl;
 40   req.originalUrl = req.originalUrl || req.url;
 41 
 42   next();
 43 
 44   function next(err) {
 45     var layerError = err === 'route'
 46       ? null
 47       : err;
 48 
 49     // remove added slash
 50     if (slashAdded) {
 51       req.url = req.url.substr(1);
 52       slashAdded = false;
 53     }
 54 
 55     // restore altered req.url
 56     if (removed.length !== 0) {
 57       req.baseUrl = parentUrl;
 58       req.url = protohost + removed + req.url.substr(protohost.length);
 59       removed = '';
 60     }
 61 
 62     // no more matching layers
 63     if (idx >= stack.length) {
 64       setImmediate(done, layerError);
 65       return;
 66     }
 67 
 68     // get pathname of request
 69     var path = getPathname(req);
 70 
 71     if (path == null) {
 72       return done(layerError);
 73     }
 74 
 75     // find next matching layer
 76     var layer;
 77     var match;
 78     var route;
 79 
 80     while (match !== true && idx < stack.length) {
 81       layer = stack[idx++];
 82       match = matchLayer(layer, path);
 83       route = layer.route;
 84 
 85       if (typeof match !== 'boolean') {
 86         // hold on to layerError
 87         layerError = layerError || match;
 88       }
 89 
 90       if (match !== true) {
 91         continue;
 92       }
 93 
 94       if (!route) {
 95         // process non-route handlers normally
 96         continue;
 97       }
 98 
 99       if (layerError) {
100         // routes do not match with a pending error
101         match = false;
102         continue;
103       }
104 
105       var method = req.method;
106       var has_method = route._handles_method(method);
107 
108       // build up automatic options response
109       if (!has_method && method === 'OPTIONS') {
110         appendMethods(options, route._options());
111       }
112 
113       // don't even bother matching route
114       if (!has_method && method !== 'HEAD') {
115         match = false;
116         continue;
117       }
118     }
119 
120     // no match
121     if (match !== true) {
122       return done(layerError);
123     }
124 
125     // store route for dispatch on change
126     if (route) {
127       req.route = route;
128     }
129 
130     // Capture one-time layer values
131     req.params = self.mergeParams
132       ? mergeParams(layer.params, parentParams)
133       : layer.params;
134     var layerPath = layer.path;
135 
136     // this should be done for the layer
137     self.process_params(layer, paramcalled, req, res, function (err) {
138       if (err) {
139         return next(layerError || err);
140       }
141 
142       if (route) {
143         return layer.handle_request(req, res, next);
144       }
145 
146       trim_prefix(layer, layerError, layerPath, path);
147     });
148   }
 

從上邊的代碼咱們能夠看出,這裏也是利用next(),來處理stack中的每個Layer,這裏的stack是Router的stack,stack中存貯了多個route對應的layer,獲取到每一個layer對象以後,用請求的path與layer進行匹配,此處匹配用的是layer.match,若是能匹配到對應的layer,則得到layer.route,若是route不爲空則執行對應的layer.handle_request(),若是route爲空說明這個layer是經過use()添加的非路由中間件,須要特別說明的是,若是經過use()添加的非路由中間件沒有指定path,則會在layer.match中默認返回true,也就是說,沒有指定path的非路由中間件會匹配全部的http請求。

 

回過頭來看看route模塊裏的stack是存的什麼:

 1 methods.forEach(function(method){
 2   Route.prototype[method] = function(){
 3     var handles = flatten(slice.call(arguments));
 4 
 5     for (var i = 0; i < handles.length; i++) {
 6       var handle = handles[i];
 7 
 8       if (typeof handle !== 'function') {
 9         var type = toString.call(handle);
10         var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
11         throw new Error(msg);
12       }
13 
14       debug('%s %s', method, this.path);
15 
16       var layer = Layer('/', {}, handle);
17       layer.method = method;
18 
19       this.methods[method] = true;
20       this.stack.push(layer);
21     }
22 
23     return this;
24   };
25 });

咱們看到他這個layer其實都是默認"/",咱們能夠當他是用.use方法加的中間件。

在本節的最後一塊兒來看下app.listen的實現(application.js中):

1 app.listen = function listen() {  
2   var server = http.createServer(this);  
3   return server.listen.apply(server, arguments);  
4 };  

其中的this指的是app對象,在前面express()中已經分析過app的原型了,其中app的構造函數就是:

 1 var app = function(req, res, next) { 2 app.handle(req, res, next); 3 };  

因此app.listen翻譯下其實就是咱們常見的下述寫法:

1 app.listen = function listen() {  
2   var server = http.createServer(function(req,res,next){  
3     app.handle(req,res,next)  
4   });  
5   return server.listen(arguments);  
6 };  

app.handle的源碼在./application.js中:

 1 app.handle = function handle(req, res, callback) {  
 2   var router = this._router;  
 3   
 4   // final handler  
 5   var done = callback || finalhandler(req, res, {  
 6     env: this.get('env'),  
 7     onerror: logerror.bind(this)  
 8   });  
 9   
10   // no routes  
11   if (!router) {  
12     debug('no routes defined on app');  
13     done();  
14     return;  
15   }  
16   
17   router.handle(req, res, done);  
18 };  

該方法將監聽到的用戶請求轉入router中處理,即第一階段處理。

上面分析了express啓動加載的過程,重點是對router和route的管理。

如下面的用戶請求爲例:

 

 1 app.get("/a/b", function(req, res) { 2 res.end("hello,world"); 3 }); 

 

router中按照用戶定義的中間件和請求映射順序註冊處理棧,下面是一個完成初始化的處理棧截圖:

該棧中有6個layer,按照用戶初始化順序添加到數組中(前兩個是express默認添加的),其中第三個layer用來處理/a/b的請求。

好比當用戶在發送/a/b的請求的時候,在router中的layer棧中作循環處理,依次判斷路徑是否匹配,route是否爲空,直到找到匹配的layer爲止.

 

該layer的定義以下:

 1 proto.route = function route(path) {  
 2   console.log("app.get(path,cb) path value:" + JSON.stringify(path));  
 3   var route = new Route(path);  
 4   
 5   var layer = new Layer(path, {  
 6     sensitive: this.caseSensitive,  
 7     strict: this.strict,  
 8     end: true  
 9   }, route.dispatch.bind(route));//指定該layer的處理函數dispatch  
10   
11   layer.route = route;//指定該layer對應的二級處理路由器  
12   
13   this.stack.push(layer);  
14   console.log("app.get(path,cb) return route:" + JSON.stringify(route));  
15   return route;  
16 };  

  而後調用layer的handle_request的方法

 1 Layer.prototype.handle_request = function handle(req, res, next) {  
 2   var fn = this.handle;  
 3   
 4   if (fn.length > 3) {  
 5     // not a standard request handler  
 6     return next();  
 7   }  
 8   
 9   try {  
10     fn(req, res, next);  
11   } catch (err) {  
12     next(err);  
13   }  
14 };  

其中的this.handler即在定義layer的時候指定的route的dispatch方法,調用fn方法即調用dispatch方法。

 1 Route.prototype.dispatch = function dispatch(req, res, done) {  
 2   var idx = 0;  
 3   var stack = this.stack;  
 4   if (stack.length === 0) {  
 5     return done();  
 6   }  
 7   
 8   var method = req.method.toLowerCase();  
 9   if (method === 'head' && !this.methods['head']) {  
10     method = 'get';  
11   }  
12   
13   req.route = this;  
14   
15   next();  
16   
17   function next(err) {  
18     if (err && err === 'route') {  
19       return done();  
20     }  
21   
22     var layer = stack[idx++];  
23     if (!layer) {  
24       return done(err);  
25     }  
26   
27     if (layer.method && layer.method !== method) {  
28       return next(err);  
29     }  
30   
31     if (err) {  
32       layer.handle_error(err, req, res, next);  
33     } else {  
34       layer.handle_request(req, res, next);  
35     }  
36   }  
37 };  

這裏將用戶的請求導入到了route的layer棧中(該棧在application.js中和router中的棧一同完成初始化)

1 var route = this._router.route(path);//註冊router的layer棧  
2 route[method].apply(route, slice.call(arguments, 1));//注入route的layer棧,該棧中的處理函數即用戶定義的回調函數  

同router的棧處理順序相似,不一樣的是這裏的layer.handle_request最終調用的將是用戶定義的回調函數,而再也不是dispach函數。

 最後:我仍是想說一下,咱們的路由機制!實際上是經過內部的next函數,進行遞歸的,他其實有2個next函數,一個定義在proto.handle裏,這是處理中間件的,另一個在dispatch裏,是用來處理路由的。具體你們能夠看下這篇文章,反正講的很明白了

點擊這裏

相關文章
相關標籤/搜索