摘要:在后續的總結中,我會繼續分析,并準備將一些值得分析的逐一解讀,也會涉及一些。從一個官方示例開始這是官方給出的一個簡單程序,運行后訪問顯示。第一行載入了框架,我們來看源代碼中的。代碼的開始定義了一個函數,函數有形參,,為回調函數。
這兩天仔細看了看express的源碼,對其的整個實現有了較清晰的認識,所以想總結一下寫出來,如果有什么不對的地方,望指出。
這是第一篇,首先介紹一個最簡單的express應用運行過程,初步分析了其在源碼中的具體實現,還沒有涉及到一些比較重要的內容比如路由組件的實現方式,中間件的觸發流程等。在后續的總結中,我會繼續分析,并準備將一些值得分析的public api逐一解讀,也會涉及一些private api。
基于的版本截止寫這篇文章時目前最新的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。
從一個官方示例開始var express = require("express"); var app = express(); app.get("/", function(req, res){ res.send("Hello World"); }); app.listen(3000);
這是官方給出的一個簡單程序,運行后訪問localhost:3000顯示Hello World。下面我們就來仔細看看這段程序。
首先第一行
var express = require("express");
這是典型的Node.js模塊載入代碼,關于Node.js的模塊載入機制,不了解的同學建議看看樸靈的深入Node.js的模塊機制,非常有幫助。
第一行載入了express框架,我們來看源代碼中的index.js。
module.exports = require("./lib/express");
好吧,還要繼續require,我們看./lib/express.js
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實例所需要的動作,并在執行完畢后返回一個函數。
代碼的開始定義了一個函數,函數有形參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。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87548.html
摘要:就是每一個教程里面開始教學的事例,啟動服務器的回調函數。,從入口開始分析源碼首先是把模塊的屬性全部進里面去,在把事件的屬性全部進里面去,這是為了給增加事件功能。 express4.X源碼解讀第一天 express4.X 跟3.X 有很大區別,4.X 去除了connect的依賴,3.X基于connect的中間件基本全部不能用,如果還有可以使用的,也是4.X重寫的。所以要想繼續使用這些熟悉...
摘要:載入了框架,我們來看源代碼中的。函數函數代碼如下代碼的開始定義了一個函數,函數有形參,,為回調函數。相應的,等同于繼承,從而讓有了事件處理的能力。 此為裁剪過的筆記版本。 原文在此:https://segmentfault.com/a/11...原文在此: https://cnodejs.org/topic/574... 感謝@YiQi ,@leijianning 帶來的好文章。我稍作...
摘要:如果此時我們不想把文件輸出到內存里,可以通過修改的源代碼來實現。服務啟動成功。。。根據請求的,拼接出 ? webpack-dev-middleware 是express的一個中間件,它的主要作用是以監聽模式啟動webpack,將webpack編譯后的文件輸出到內存里,然后將內存的文件輸出到epxress服務器上;下面通過一張圖片來看一下它的工作原理: showImg(https:...
摘要:最近在研究,學著使用,開始不會用,就百度了一下,沒有百度到特別完整的解答。查閱了的,綜合了網友的博客,解讀了的源碼,以及使用和驗證,終于明白了中的使用。默認為網站域名過期時間,類型為。使用插件,后續代碼直接使用或者即可 最近在研究express,學著使用cookie,開始不會用,就百度了一下,沒有百度到特別完整的解答。查閱了express的API,綜合了網友的博客,解讀了cookie-...
摘要:最近開始看源碼,并將源碼解讀放在了我的計劃中。相對于其他源碼解讀的文章,基本都會從整體設計開始講起,樓主覺得這個庫有點特殊,決定按照自己的思路,從用代替說起。源碼沒有出現注意,其實有出現一處,是為,而不是,而用代替之。 Why underscore 最近開始看 underscore源碼,并將 underscore源碼解讀 放在了我的 2016計劃 中。 閱讀一些著名框架類庫的源碼,就好...
閱讀 1079·2021-11-16 11:44
閱讀 1368·2019-08-30 13:12
閱讀 2401·2019-08-29 16:05
閱讀 3070·2019-08-28 18:29
閱讀 904·2019-08-26 13:41
閱讀 3228·2019-08-26 13:34
閱讀 2596·2019-08-26 10:35
閱讀 931·2019-08-26 10:28