這兩天仔細看了看express的源碼,對其的整個實現有了較清晰的認識,因此想總結一下寫出來,若是有什麼不對的地方,望指出。html
這是第一篇,首先介紹一個最簡單的express
應用運行過程,初步分析了其在源碼中的具體實現,尚未涉及到一些比較重要的內容好比路由組件的實現方式,中間件的觸發流程等。在後續的總結中,我會繼續分析,並準備將一些值得分析的public api
逐一解讀,也會涉及一些private api
。node
截止寫這篇文章時目前最新的tags是4.4.2
。我是直接看的master分支。express
的commits提交很是頻繁,但整體的實現思路應該不會有大的變化。其在4.x後作了較大的改動,相對於3.x最大的地方在於再也不依賴connect
,並移除了幾乎全部的內置中間件,具體的變更請看官方wiki的 Migrating from 3.x to 4.x 及 New features in 4.x。git
var express = require('express'); var app = express(); app.get('/', function(req, res){ res.send('Hello World'); }); app.listen(3000);
這是官方給出的一個簡單程序,運行後訪問localhost:3000
顯示Hello World。下面咱們就來仔細看看這段程序。github
首先第一行web
var express = require('express');
這是典型的Node.js模塊載入代碼,關於Node.js
的模塊載入機制,不瞭解的同窗建議看看樸靈的深刻Node.js的模塊機制,很是有幫助。express
第一行載入了express
框架,咱們來看源代碼中的index.js
。npm
module.exports = require('./lib/express');
好吧,還要繼續require,咱們看./lib/express.jsapi
exports = module.exports = createApplication;
從這裏咱們能夠看出,程序的第一行express
最後實際是這個createApplication
函數。第二行則是運行了這個函數,而後返回值賦給了app
。該函數代碼以下數組
var EventEmitter = require('events').EventEmitter; var mixin = require('utils-merge'); var proto = require('./application'); var req = require('./request'); var res = require('./response'); 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
的'main'
函數,其中完成了全部建立express
實例所須要的動做,並在執行完畢後返回一個函數。app
代碼的開始定義了一個函數,函數有形參req
,res
,next
爲回調函數。
函數體只有一條語句,執行app.handle
,handle
方法在application.js
文件中定義,此處是經過mixin
導入(見下文),handle
的代碼以下
app.handle = function(req, res, done) { var router = this._router; // final handler done = done || finalhandler(req, res, { env: this.get('env'), onerror: logerror.bind(this) }); // no routes if (!router) { debug('no routes defined on app'); // generate error var err = new Error('No routes or middlewares have been defined'); err.status = 500; done(err); return; } router.handle(req, res, done); };
它的做用就是將每對[req,res]
進行逐級分發,做用在每一個定義好的路由及中間件上,直到最後完成,具體的過程咱們會在後續進行分析。
而後來看看中間的兩行
mixin(app, proto); mixin(app, EventEmitter.prototype);
mixin
是在頭部的require處載入的utils-merge模塊,它的代碼以下
exports = module.exports = function(a, b){ if (a && b) { for (var key in b) { a[key] = b[key]; } } return a; };
很明顯,mixin(app, proto);
的做用便是將proto
中全部的property所有導入進app
,proto
在頭部的require處載入的是./lib/application.js
文件,其中定義了大部分express
的public api
,如app.set,app.get,app.use...詳見官方的API文檔。mixin(app, EventEmitter.prototype);
則將Node.js
的EventEmitter
中的原型方法所有導入了app。
再來看接下來的兩行
app.request = { __proto__: req, app: app }; app.response = { __proto__: res, app: app };
這裏定義了app
的request
和response
對象,使用了對象的字面量表示法,使其分別繼承自req
(頂部導入的request.js
)和res
(頂部導入的response.js
),並反向引用了app
自身。爲何要這樣作呢?這個問題我一開始想不明白,後來我就乾脆把這兩行代碼刪了,運行,固然就是報錯,答案就在錯誤中的信息裏。
TypeError: Object #
has no method 'send'
顯示找不到'send'
方法,爲何呢?首先咱們從app.get()
方法看起,不熟悉的人會找不到它在源碼中的位置,其實它在application.js
中是這樣的
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
請求方法的數組,具體代碼在這裏。
從上面的代碼中咱們能夠看到,這裏其實是遍歷了全部methods中定義的方法,固然其中包括get
,並且get
方法是被'重載'的,即當app.get();
的參數只有一個時候,執行的是獲取變量的功能,不然,執行route
組件中的route.get
方法,將該路由和回調函數(即第二個參數)存儲進一個棧中(後續會進一步分析)。
回到原來的問題,在這裏,關鍵是看中間的
this.lazyrouter();
咱們看它的具體代碼
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._router.use(middleware.init(this)); } };
它的做用是在第一次定義路由的時候初始化路由(添加基本的路由),注意最後一句用到了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
,能夠看到其中用到了我所疑惑app.request
和app.respone
,它使req
和res
繼承自了request.js
和response.js
中的定義,也所以在我去掉了那兩行代碼後會出現res.send
找不到的狀況。
另外,定義app.response
對象時反引用自身,也使得後面在response
對象中可以經過this.app
得到所建立的express
實例。
讓咱們回到createApplication
函數,接下來是app.init();
。顯然,做用是初始化,作哪些工做呢?
app.init = function(){ this.cache = {}; this.settings = {}; this.engines = {}; this.defaultConfiguration(); };
設定了cache對象(render的時候用到),各類setting的存儲對象,engines對象(模板引擎),最後進行默認的配置,代碼有點長這裏就不上了,就是作一些默認的配置。
好了,createApplication
函數就是這些,固然,其中略去了不少重要的問題,好比路由組件的實現方式,中間件的觸發流程等,這我會在後續的總結中進行分析。
最開頭的官方示例中還有最後一句
app.listen(3000);
代碼以下
app.listen = function(){ var server = http.createServer(this); return server.listen.apply(server, arguments); };
其實是調用了Node.js
原生的http
模塊的CreatServer
方法,API文檔說明是
http.createServer([requestListener])#
Returns a new web server object.The requestListener is a function which is automatically added to the 'request' event.
方法返回的是一個web server
對象,其中的參數爲HTTP request
事件觸發後執行的函數(這裏咱們給的就是咱們在createApplication
函數中得到的app)。
最後,返回的web server
有一個監聽端口的listen
方法,參數爲須要監聽的端口號,本示例中即爲3000
。