摘要:就是每一個(gè)教程里面開(kāi)始教學(xué)的事例,啟動(dòng)服務(wù)器的回調(diào)函數(shù)。,從入口開(kāi)始分析源碼首先是把模塊的屬性全部進(jìn)里面去,在把事件的屬性全部進(jìn)里面去,這是為了給增加事件功能。
express4.X源碼解讀第一天
express4.X 跟3.X 有很大區(qū)別,4.X 去除了connect的依賴,3.X基于connect的中間件基本全部不能用,如果還有可以使用的,也是4.X重寫(xiě)的。所以要想繼續(xù)使用這些熟悉的中間件,就要手動(dòng)安裝依賴包,或者用一些其他的中間件。
下面開(kāi)始源碼解讀 1. express是什么typeof express === "function" //true
可以知道express是個(gè)函數(shù),這個(gè)函數(shù)是程序啟動(dòng)就會(huì)運(yùn)行起來(lái)
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; }
上面這個(gè)函數(shù)就是express,有沒(méi)有看到很熟悉的東西,看到app沒(méi),還在哪里看到過(guò)這個(gè)熟悉的東西...
對(duì)了,沒(méi)錯(cuò)。就是每一個(gè)nodejs教程里面開(kāi)始nodejs教學(xué)的事例,nodejs啟動(dòng)服務(wù)器:http.createSever 的回調(diào)函數(shù)。app是express貫穿整個(gè)流程的函數(shù)。其實(shí)整個(gè)express 執(zhí)行過(guò)程就是往req,res這兩個(gè)對(duì)象不停的修改屬性,添加屬性。直到完成請(qǐng)求。中間件也就是通過(guò)app做為回調(diào),進(jìn)而修改req,res。從而實(shí)現(xiàn)可插拔的效果。
var app = express();
這就是為什么引入express,都要開(kāi)始執(zhí)行一下這個(gè)函數(shù)。
2. 程序是如何啟動(dòng)的express做為一個(gè)web框架,首先要有啟動(dòng)一個(gè)服務(wù)器的,我們看下服務(wù)器是在哪里啟動(dòng)的
var server = app.listen(app.get("port"), function() { debug("Express server listening on port " + server.address().port); });
express用了一個(gè)我不太喜歡用的寫(xiě)法,他把所有的方法直接放到app這個(gè)函數(shù)上去了,大家都知道函數(shù)在js中就是對(duì)象,除了本身是可以執(zhí)行以外,和對(duì)象是沒(méi)有什么區(qū)別的。不過(guò)這就無(wú)形之中增加了閱讀代碼的難度,而且很容易混淆,因?yàn)閍pp既做為一個(gè)中間件,還要做為一個(gè)公共方法的載體。
好了,講到啟動(dòng)服務(wù)器,app是沒(méi)有啟動(dòng)服務(wù)器的能力的,這個(gè)能力是在application 這個(gè)文件中被mix進(jìn)去的,其實(shí)就是mix一個(gè)http.createServer方法,但是這里還是要看一下代碼。
app.listen = function(){ var server = http.createServer(this); return server.listen.apply(server, arguments); };
看到this沒(méi)有啊,這個(gè)this很重要哈,this == app 。app做為回調(diào)已經(jīng)傳進(jìn)來(lái)了,神奇的中間件在這里開(kāi)始了旅程。
3,從入口開(kāi)始分析源碼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 三個(gè)對(duì)象,現(xiàn)在看不出來(lái)作用,具體執(zhí)行過(guò)程到defaultConfiguration里面看看
this.enable("x-powered-by")
看到了enable,然后進(jìn)去看enable其實(shí)就set,只不過(guò)第二個(gè)參數(shù)是boolean。set是什么呢?還記得我們沒(méi)有了解功能的三個(gè)對(duì)象之一的setting,這個(gè)set就是往setting對(duì)象添加一些屬性而已。
好 先看defaultConfiguration
this.enable("x-powered-by")
設(shè)置x-powered-by 為true,x-powerd-by是什么意思呢?
有些查詢工具在我們輸入某個(gè)站點(diǎn)的URL后就能判斷這個(gè)站點(diǎn)的WebServer與程序類型。
就是在http請(qǐng)求的時(shí)候,能夠看到x-powered-by:Express,不設(shè)置 就看不到服務(wù)區(qū)類型,這應(yīng)該是http請(qǐng)求的一部分
this.set("etag", "weak");
這里處理etag的 Express依賴了一個(gè)叫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);
這里繼續(xù)設(shè)置屬性。
// 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! Please see the 3.x to 4.x migration guide for details on how to update your app."); } });
這里的mount,我之前不知道什么意思,后來(lái)看其他應(yīng)用才知道,這是用來(lái)掛載其他應(yīng)用的,比如我有幾個(gè)應(yīng)用,可以起幾個(gè)業(yè)務(wù)服務(wù),用一個(gè)中央服務(wù)監(jiān)聽(tīng)端口,然后掛載其他幾個(gè)應(yīng)用模塊
下面研究一下app.use這個(gè)方法研究發(fā)現(xiàn)這個(gè)時(shí)候express的初始化流程已經(jīng)走完了,以前看過(guò)3.X的源碼,貌似不是這樣子的,但是仔細(xì)觀察,確確實(shí)實(shí)到這里是結(jié)束了。剩余的方法都是怎么處理的呢?在細(xì)細(xì)往下看吧
add middleware to the app router
這是源碼里面的解釋,向路由添加中間件,前面說(shuō)過(guò)中間件和路由沒(méi)有本質(zhì)區(qū)別,是一樣的東西。
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這么個(gè)東西,這個(gè)函數(shù)里面new 了一個(gè)Router對(duì)象,所以這一張暫時(shí)略過(guò)了 我們要去route里面看看了
昨天看源碼遇到了麻煩,發(fā)現(xiàn)很多代碼還不是那么容易看懂,有些迷糊,然后犯了一些錯(cuò)誤,打了很多斷點(diǎn)終于弄清楚了
想要明白express的處理流程,必須先要弄清楚app.use和 app.handle這兩個(gè)方法,這兩個(gè)方法很重要。
前面我們已經(jīng)知道app本身是做為回調(diào)參數(shù)傳進(jìn)http.createServer里面的,應(yīng)用所有的路由都會(huì)掉進(jìn)這個(gè)函數(shù)里面去,經(jīng)過(guò)一個(gè)一個(gè)中間件進(jìn)行處理。本身想想不是很復(fù)雜,但看起代碼來(lái)還是很蛋疼的
首先req,res被封裝了很多方法進(jìn)去,但是這個(gè)方法是在什么地方mix進(jìn)去的呢。在這里我就犯了個(gè)錯(cuò)誤,錯(cuò)誤的認(rèn)為會(huì)在use的時(shí)候就會(huì)有這個(gè)方法,所以我在use函數(shù)里面找啊找,打了很多個(gè)斷點(diǎn),始終沒(méi)有找到哪里執(zhí)行了這個(gè)操作。
但實(shí)際上,use始終沒(méi)有做這個(gè)操作,use的作用就是route里面把這個(gè)回調(diào)push進(jìn)route實(shí)例的stack里面,看代碼
if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); }
app的use執(zhí)行了 Route實(shí)例的use。繼續(xù)看Route的use
var layer = new Layer(path, { sensitive: self.caseSensitive, strict: false, end: false }, fn); layer.route = undefined; self.stack.push(layer);
去看會(huì)發(fā)現(xiàn)route的use和app的use會(huì)有些重復(fù)的代碼,不同的地方就在于Route的use會(huì)創(chuàng)建一個(gè)layer。這個(gè)layer就是個(gè)實(shí)例,就是每個(gè)回調(diào)函數(shù)的實(shí)例。這個(gè)實(shí)例包括全局配置的一些屬性,比如嚴(yán)格匹配,大小寫(xiě)。還有就是把當(dāng)前use的路由url和回調(diào)存儲(chǔ)起來(lái)了,全部push進(jìn)stack里面去。
看下route的實(shí)例化過(guò)程,會(huì)發(fā)現(xiàn)express默認(rèn)放置了兩個(gè)中間件進(jìn)去。代碼如下
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默認(rèn)就會(huì)有兩個(gè)中間件,query和 middleware。程序執(zhí)行到這里已經(jīng)執(zhí)行結(jié)束了。
那又有問(wèn)題了,request,response這兩個(gè)對(duì)象的很多擴(kuò)展方法,從何而來(lái)。
下面就來(lái)看看吧
打開(kāi)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是在這里被放置到回調(diào)的req,res上去的。由于內(nèi)置的這兩個(gè)中間件是首先添加的,被放置在stack的前兩個(gè),所以每個(gè)請(qǐng)求進(jìn)來(lái)首先會(huì)進(jìn)入這兩個(gè)中間件里面去,然后帶了很多東西進(jìn)入其他的中間件去。
還有問(wèn)題啊,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); };
這里會(huì)把layer里面存儲(chǔ)的route正則拿來(lái)和當(dāng)前路由匹配,成功則進(jìn)入回調(diào)執(zhí)行,失敗則繼續(xù)執(zhí)行。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/82627.html
摘要:打包好后本地測(cè)試運(yùn)行是否正常環(huán)境搭建這個(gè)時(shí)候需要利用中的方法如下安裝最新版本中將命令工具分家出來(lái)了還需要安裝一個(gè)命令工具創(chuàng)建一個(gè)工程進(jìn)入項(xiàng)目主目錄安裝必備包啟動(dòng)程序把打包后的文件夾放在文件夾里訪問(wèn)就能看到項(xiàng)目了這樣測(cè)試好了后就可以丟后 打包好后本地測(cè)試運(yùn)行是否正常環(huán)境搭建: 這個(gè)時(shí)候需要利用node中的express,方法如下: 安裝express: npm install -g ex...
摘要:前言由于最近公司需要做一個(gè)聊天監(jiān)控的項(xiàng)目,老大讓我把后臺(tái)也做了,于是才真正實(shí)踐深入的內(nèi)部。幾番折騰終于把項(xiàng)目搭起來(lái)了。發(fā)生服務(wù)特定錯(cuò)誤,則前去目錄下的和文件刪掉,以管理員身份運(yùn)行命令行然后重新啟動(dòng)服務(wù)即可。 前言 由于最近公司需要做一個(gè)聊天監(jiān)控的項(xiàng)目,老大讓我把后臺(tái)也做了,于是才真正實(shí)踐深入node.js的內(nèi)部。幾番折騰終于把項(xiàng)目搭起來(lái)了。 經(jīng)濟(jì)基礎(chǔ) node.js (安裝配置傳送門(mén)...
摘要:前言由于最近公司需要做一個(gè)聊天監(jiān)控的項(xiàng)目,老大讓我把后臺(tái)也做了,于是才真正實(shí)踐深入的內(nèi)部。幾番折騰終于把項(xiàng)目搭起來(lái)了。發(fā)生服務(wù)特定錯(cuò)誤,則前去目錄下的和文件刪掉,以管理員身份運(yùn)行命令行然后重新啟動(dòng)服務(wù)即可。 前言 由于最近公司需要做一個(gè)聊天監(jiān)控的項(xiàng)目,老大讓我把后臺(tái)也做了,于是才真正實(shí)踐深入node.js的內(nèi)部。幾番折騰終于把項(xiàng)目搭起來(lái)了。 經(jīng)濟(jì)基礎(chǔ) node.js (安裝配置傳送門(mén)...
摘要:最近寫(xiě)復(fù)旦二手平臺(tái)的時(shí)候開(kāi)始嘗試用一直推崇了很久的組件化。經(jīng)過(guò)一番抉擇之后選擇了的組合。所以在這里分享一下具體的實(shí)踐流程。自己有自己獨(dú)特的依賴注入以及模塊聲明方式,看起來(lái)似乎和是水火不容的,但事實(shí)上他們完全可以融合。 最近寫(xiě)復(fù)旦二手平臺(tái)的時(shí)候開(kāi)始嘗試用一直推崇了很久的組件化。經(jīng)過(guò)一番抉擇之后選擇了 webpack + angular 的組合。所以在這里分享一下具體的實(shí)踐流程。 Web...
摘要:有如下模塊源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析源碼解析使用和監(jiān)控和博客從到學(xué)習(xí)介紹從到學(xué)習(xí)上搭建環(huán)境并構(gòu)建運(yùn)行簡(jiǎn)單程序入門(mén)從到學(xué)習(xí)配置文件詳解從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自 Flink Metrics 有如下模塊: Flink Metrics 源碼解析 —— Flink-metrics-core Flink Metrics 源碼解析 —— Flink-metr...
閱讀 3312·2021-11-23 09:51
閱讀 2935·2021-10-28 09:33
閱讀 895·2021-10-08 10:04
閱讀 3704·2021-09-22 15:13
閱讀 1028·2019-08-30 15:55
閱讀 2918·2019-08-30 15:44
閱讀 577·2019-08-30 13:04
閱讀 2946·2019-08-30 12:56