express4.X 跟3.X 有很大區別,4.X 去除了connect的依賴,3.X基於connect的中間件基本所有不能用,若是還有可使用的,也是4.X重寫的。因此要想繼續使用這些熟悉的中間件,就要手動安裝依賴包,或者用一些其餘的中間件。node
typeof express === 'function' //true
能夠知道express是個函數,這個函數是程序啓動就會運行起來web
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沒,還在哪裏看到過這個熟悉的東西...express
對了,沒錯。就是每個nodejs教程裏面開始nodejs教學的事例,nodejs啓動服務器:http.createSever 的回調函數。app是express貫穿整個流程的函數。其實整個express 執行過程就是往req,res這兩個對象不停的修改屬性,添加屬性。直到完成請求。中間件也就是經過app作爲回調,進而修改req,res。從而實現可插拔的效果。json
var app = express();
這就是爲何引入express,都要開始執行一下這個函數。服務器
express作爲一個web框架,首先要有啓動一個服務器的,咱們看下服務器是在哪裏啓動的app
var server = app.listen(app.get('port'), function() { debug('Express server listening on port ' + server.address().port); });
express用了一個我不太喜歡用的寫法,他把全部的方法直接放到app這個函數上去了,你們都知道函數在js中就是對象,除了自己是能夠執行之外,和對象是沒有什麼區別的。不過這就無形之中增長了閱讀代碼的難度,並且很容易混淆,由於app既作爲一箇中間件,還要作爲一個公共方法的載體。框架
好了,講到啓動服務器,app是沒有啓動服務器的能力的,這個能力是在application 這個文件中被mix進去的,其實就是mix一個http.createServer方法,可是這裏仍是要看一下代碼。dom
app.listen = function(){ var server = http.createServer(this); return server.listen.apply(server, arguments); };
看到this沒有啊,這個this很重要哈,this == app 。app作爲回調已經傳進來了,神奇的中間件在這裏開始了旅程。ide
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; }
好,下面到了application模塊的init方法裏面去了函數
app.init = function(){ this.cache = {}; this.settings = {}; this.engines = {}; this.defaultConfiguration(); };
增長了cache setting engines 三個對象,如今看不出來做用,具體執行過程到defaultConfiguration裏面看看
this.enable('x-powered-by')
看到了enable,而後進去看enable其實就set,只不過第二個參數是boolean。set是什麼呢?還記得咱們沒有了解功能的三個對象之一的setting,這個set就是往setting對象添加一些屬性而已。
好 先看defaultConfiguration
this.enable('x-powered-by')
設置x-powered-by 爲true,x-powerd-by是什麼意思呢?
有些查詢工具在咱們輸入某個站點的URL後就能判斷這個站點的WebServer與程序類型。
就是在http請求的時候,可以看到x-powered-by:Express,不設置 就看不到服務區類型,這應該是http請求的一部分
this.set('etag', 'weak');
這裏處理etag的 Express依賴了一個叫etag的包
var env = process.env.NODE_ENV || 'development'; this.set('env', env); this.set('query parser', 'extended'); this.set('subdomain offset', 2); this.set('trust proxy', false);
這裏繼續設置屬性。
// inherit protos this.on('mount', function(parent){ this.request.__proto__ = parent.request; this.response.__proto__ = parent.response; this.engines.__proto__ = parent.engines; this.settings.__proto__ = parent.settings; }); // setup locals this.locals = Object.create(null); // top-most app is mounted at / this.mountpath = '/'; // default locals this.locals.settings = this.settings; // default configuration this.set('view', View); this.set('views', resolve('views')); this.set('jsonp callback name', 'callback'); if (env === 'production') { this.enable('view cache'); } Object.defineProperty(this, 'router', { get: function() { 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.'); } });
這裏的mount,我以前不知道什麼意思,後來看其餘應用才知道,這是用來掛載其餘應用的,好比我有幾個應用,能夠起幾個業務服務,用一箇中央服務監聽端口,而後掛載其餘幾個應用模塊
研究發現這個時候express的初始化流程已經走完了,之前看過3.X的源碼,貌似不是這樣子的,可是仔細觀察,確確實實到這裏是結束了。剩餘的方法都是怎麼處理的呢?在細細往下看吧
add middleware to the app router
這是源碼裏面的解釋,向路由添加中間件,前面說過中間件和路由沒有本質區別,是同樣的東西。
app.use = function use(fn) { var offset = 0; var path = '/'; var self = this; // 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 = self; // 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) { req.__proto__ = orig.request; res.__proto__ = orig.response; next(err); }); }); // mounted an app fn.emit('mount', self); }); return this; };
因而咱們看到lazyrouter這麼個東西,這個函數裏面new 了一個Router對象,因此這一張暫時略過了 咱們要去route裏面看看了
昨天看源碼遇到了麻煩,發現不少代碼還不是那麼容易看懂,有些迷糊,而後犯了一些錯誤,打了不少斷點終於弄清楚了
想要明白express的處理流程,必須先要弄清楚app.use和 app.handle這兩個方法,這兩個方法很重要。
前面咱們已經知道app自己是作爲回調參數傳進http.createServer裏面的,應用全部的路由都會掉進這個函數裏面去,通過一個一箇中間件進行處理。自己想一想不是很複雜,但看起代碼來仍是很蛋疼的
首先req,res被封裝了不少方法進去,可是這個方法是在什麼地方mix進去的呢。在這裏我就犯了個錯誤,錯誤的認爲會在use的時候就會有這個方法,因此我在use函數裏面找啊找,打了不少個斷點,始終沒有找到哪裏執行了這個操做。
但實際上,use始終沒有作這個操做,use的做用就是route裏面把這個回調push進route實例的stack裏面,看代碼
if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); }
app的use執行了 Route實例的use。繼續看Route的use
var layer = new Layer(path, { sensitive: self.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; self.stack.push(layer);
去看會發現route的use和app的use會有些重複的代碼,不一樣的地方就在於Route的use會建立一個layer。這個layer就是個實例,就是每一個回調函數的實例。這個實例包括全局配置的一些屬性,好比嚴格匹配,大小寫。還有就是把當前use的路由url和回調存儲起來了,所有push進stack裏面去。
看下route的實例化過程,會發現express默認放置了兩個中間件進去。代碼以下
app.lazyrouter = function() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled('case sensitive routing'), strict: this.enabled('strict routing') }); this._router.use(query(this.get('query parser fn'))); this._router.use(middleware.init(this)); } };
因此app默認就會有兩個中間件,query和 middleware。程序執行到這裏已經執行結束了。
那又有問題了,request,response這兩個對象的不少擴展方法,從何而來。
下面就來看看吧
打開middleware/init
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(); }; };
這裏就看到了 request,response是在這裏被放置到回調的req,res上去的。因爲內置的這兩個中間件是首先添加的,被放置在stack的前兩個,因此每一個請求進來首先會進入這兩個中間件裏面去,而後帶了不少東西進入其餘的中間件去。
還有問題啊,use不是能夠增長路由嗎 不是能夠控制哪一些中間件走哪一些路由嘛,那是怎麼控制的呢。看這裏。。。
proto.match_layer = function match_layer(layer, req, res, done) { var error = null; var path; try { path = parseUrl(req).pathname; if (!layer.match(path)) { path = undefined; } } catch (err) { error = err; } done(error, path); };
這裏會把layer裏面存儲的route正則拿來和當前路由匹配,成功則進入回調執行,失敗則繼續執行。