摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務器啟動與請求處理源碼閱讀筆記對象起因前兩天閱讀了的基礎,和中間件的基礎。的前端樂園原文鏈接源碼閱讀筆記服務器啟動與請求處理
起因本筆記共四篇
Koa源碼閱讀筆記(1) -- co
Koa源碼閱讀筆記(2) -- compose
Koa源碼閱讀筆記(3) -- 服務器の啟動與請求處理
Koa源碼閱讀筆記(4) -- ctx對象
前兩天閱讀了Koa的基礎co,和Koa中間件的基礎compose。
然后這兩天走在路上也在思考一些Koa運行機制的問題,感覺總算有點理通了。
今天就來解讀一下Koa啟動時,發生的一系列事情。
如果只是單純用Koa,那么啟動服務器是很方便的。
下面就是一個最簡單的Hello World的例子。
var koa = require("koa") var app = new koa() app.use(function * (next) { this.set("Powered by", "Koa2-Easy") yield next }) app.use(function * (next) { this.body = "Hello World!" }) app.listen(3000)
在上一節對koa-compose的分析中,解決了我一個問題,那就是使用中間件時,那個next參數是如何來的。
這一節也會解決一個問題,那就是中間件中的this是如何來的。
首先看Koa構造函數的源代碼:
/** * Expose `Application`. */ module.exports = Application; /** * Initialize a new `Application`. * * @api public */ function Application() { if (!(this instanceof Application)) return new Application; this.env = process.env.NODE_ENV || "development"; this.subdomainOffset = 2; this.middleware = []; this.proxy = false; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }
在Application函數內部的第一句很有意思。
if (!(this instanceof Application)) return new Application;
因為是構造函數,但很多人會忘記使用new來初始化。但是在Koa,則做了一點小措施,從而達到了是否調用new都能初始化的效果。
原型的寫法關于原型的寫法,很多人肯定不陌生。以Koa的Application為例,平時如果要寫原型的屬性,那么會是這樣寫的。
function Application() {} Application.prototype.listen = function () {} Application.prototype.callback = function () {}
這樣寫的話,每次都需要寫冗長的Application.prototype。
而在Koa中,則使用一個變量,指向了prototype。
var app = Application.prototype; app.listen = function () {} app.callback = function () {}
寫起來簡潔,看起來也簡潔。
服務器の啟動流程在Koa中,或者說一切Node.js的Web框架中,其底層都是Node.js HTTP模塊來構建的服務器。
那么我就對這點產生了好奇,到底是什么,能讓發送給服務器的相應,被Koa等框架截獲,并進行相應處理。
同時在Koa框架中,調用listen方法才能啟動服務。
那么服務器的啟動流程就從listen方法開始。
首先是listen方法的源代碼
/** * Shorthand for: * * http.createServer(app.callback()).listen(...) * * @param {Mixed} ... * @return {Server} * @api public */ app.listen = function(){ debug("listen"); var server = http.createServer(this.callback()); return server.listen.apply(server, arguments); };
不難看出,只有使用了listen方法,http服務才會被真正的創建并啟動。
而查閱文檔,則看到在http.createServer(this.callback())中傳入的參數的作用。
在這里,server 每次接收到請求,就會將其傳入回調函數處理。
同時listen方法執行完畢時,server便開始監聽指定端口。
所以在這里,callback便成為一個新的重點。
繼續放上callback的源代碼(刪除部分無用部分):
/** * Return a request handler callback * for node"s native http server. * * @return {Function} * @api public */ app.callback = function(){ var fn = co.wrap(compose(this.middleware)); var self = this; if (!this.listeners("error").length) this.on("error", this.onerror); return function(req, res){ res.statusCode = 404; var ctx = self.createContext(req, res); onFinished(res, ctx.onerror); fn.call(ctx).then(function () { respond.call(ctx); }).catch(ctx.onerror); } };
在這兒,Koa的注釋對這個函數的作用解釋的很清楚。
Return a request handler callback for node"s native http server.
而這兒,對于閉包的應用則讓我眼前一亮。
由于服務器啟動后,中間件是固定的,所以像初始化中間件,保持this引用,注冊事件這種無需多次觸發或者高耗能事件,便放入閉包中好了。
一次創建,多次使用。
說到這兒想起一個問題,上次NodeParty, Koa演講結束后,有人詢問Koa能否根據請求做到動態加載中間件,當時他沒回答出來。
就源代碼來看,是不能做到動態加載的。最多也只是在中間件內部做一些判斷,從而決定是否跳過。
往下繼續讀,則可以看到這一行:
var ctx = self.createContext(req, res);
在context中,是把一些常用方法掛載至ctx這個對象中。
比如在koa中,直接調用this.body = "Hello World"這種response的方法,或者通過this.path獲得request的路徑都是可行的。
而不用像Express一般,request和response方法涇渭分明。同時在使用過程中,是明顯有感覺到Koa比Express要便利的。而不僅僅是解決回調地獄那么簡單。
在第一節Koa源碼閱讀筆記(1) -- co中,已經解釋了co.wrap的作用。
這兒可以再看一次compose函數的源代碼。
function compose(middleware){ return function *(next){ // next不存在時,調用一個空的generator函數 if (!next) next = noop(); var i = middleware.length; // 倒序處理中間件,給每個中間件傳入next參數 // 而next則是下一個中間件 while (i--) { next = middleware[i].call(this, next); } return yield *next; } } function *noop(){}
在這里,中間件被倒序處理,保證第一個中間件的next參數為第二個中間件函數,第二個的next參數則為第三個中間件函數。以此類推。
而最后一個則以一個空的generator函數結尾。
在這兒,有想了很久才想通的點,那就是next = middleware[i].call(this, next);時,middleware沒有返回值,為什么next參數等于下一個函數。
到后來才想通,中間件都是generator函數。generaotr會返回一個指向內部狀態的指針對象。
這一點我在co的閱讀筆記用提及, 也在阮一峰的《ECMAScript 6入門》看到了。
不同的是,調用Generator函數后,該函數并不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象。需要手動調用它的next()方法。
但當時就是想不起來,結果睡了一覺就突然領悟了。= =
最近也在上一門課,名稱就叫《學習如何學習》,里面也有提到睡眠能幫自己整理記憶,遇到問題也不需要死鉆牛角尖,說不定過一會兒答案會自己浮現的。
目前來看,確實是說的很對。
同時在compose函數最后的部分,返回了一個yield *next;
通過翻閱 《ECMAScript 6入門》-- 可知。
如果在Generater函數內部,調用另一個Generator函數,默認情況下是沒有效果的。這個就需要用到yield*語句,用來在一個Generator函數里面執行另一個Generator函數。
也就是說,其實每次執行時,是這樣的:
co(function* (next) { if (!next) next = noop(); var i = middleware.length; while (i--) { next = middleware[i].call(this, next); } return yield *next; })
return yield *next, next作為第一個中間件,會被執行。
如果碰到中間件中的next,則會被co繼續調用和執行。
因為在co中,碰到generator函數是這樣的:
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
當然,如果在某個中間件中,碰到了以yield形式調用的函數,則會按co的規則,一路調用下去。
當中間件調用時,會返回一個Promise,而Promise在co中,會通過onFulfilled函數,實現自動調用。
從而就形成了獨特的Koa風格。
有點迷糊的話,舉個具體的栗子:
var koa = require("koa") var app = new koa() app.use(function * (next) { console.log("middleware 1 start") yield next console.log("middleware 1 finished") }) app.use(function * (next) { console.log("middleware 2 finished") }) app.listen(3000)
當接收到響應時,首先輸出middleware 1 start,然后碰到了 yield next, next是下一個中間件,會被co處理為Promise函數。
而當第二個中間件執行完畢時,Promise自動調用then函數,而then卻又是第一個中間件的onFulfilled函數。
那么第一個中間件就會繼續向下執行。直到執行完成。
所以最后Koa的接收響應并處理的圖,是這樣的:
到這一步,這些東西就好解釋了。
var ctx = self.createContext(req, res); onFinished(res, ctx.onerror); fn.call(ctx).then(function () { respond.call(ctx); }).catch(ctx.onerror);
fn是處理過的中間件函數,使用call將創建好的ctx對象作為this傳入,就可以實現在中間件中使用this來處理請求/響應。
其他在整個處理過程中,心細的小伙伴還注意到了onFinished函數和respond函數。
onFinished函數是一個Node的模塊。地址。
作用則是在請求結束或錯誤是自動調用。所以這兒把ctx.onerror這個錯誤處理函數傳入,防止請求就直接是錯的。
而respond則是koa內部的函數,用于處理在中間件內部經過處理的ctx對象,并發送響應。
至此,Koa的啟動和響應流程便完整的走了一遍。
有些感慨,也有些唏噓。
有很多想說的,但也感覺沒什么可說的。
就這樣吧。
前端路漫漫,且行且歌。
最后附上本人博客地址和原文鏈接,希望能與各位多多交流。
Lxxyx的前端樂園
原文鏈接:Koa源碼閱讀筆記(3) -- 服務器の啟動與請求處理
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/90862.html
摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務器啟動與請求處理源碼閱讀筆記對象起因前兩天終于把自己一直想讀的源代碼讀了一遍。首先放上關鍵的源代碼在上一篇源碼閱讀筆記服務器啟動與請求處理中,我們已經分析了的作用。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4...
摘要:于是抱著知其然也要知其所以然的想法,開始閱讀的源代碼。問題讀源代碼時,自然是帶著諸多問題的。源代碼如下在被處理完后,每當有新請求,便會調用,去處理請求。接下來會繼續寫一些閱讀筆記,因為看的源代碼確實是獲益匪淺。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4) -...
摘要:正好自己之前也想看的源代碼,所以趁著這個機會,一口氣將其讀完。源碼解讀的源代碼十分簡潔,一共才兩百余行。結語的源代碼讀取來不難,但其處理方式卻令人贊嘆。而且閱讀的源代碼,是閱讀源碼的必經之路。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務器の啟動與請求處理Koa源碼閱讀筆記(4) -- ctx對象 起...
摘要:引言最近空閑時間讀了一下的源碼在閱讀的源碼的過程中,我的感受是代碼簡潔思路清晰不得不佩服大神的水平。調用的時候就跟有區別使用必須使用來調用除了上面的的構造函數外,還暴露了一些公用的,比如兩個常見的,一個是,一個是。 引言 最近空閑時間讀了一下Koa2的源碼;在閱讀Koa2(version 2.2.0)的源碼的過程中,我的感受是代碼簡潔、思路清晰(不得不佩服大神的水平)。下面是我讀完之后...
摘要:接上次挖的坑,對相關的源碼進行分析第一篇。和同為一批人進行開發,與相比,顯得非常的迷你。在接收到一個請求后,會拿之前提到的與來創建本次請求所使用的上下文。以及如果沒有手動指定,會默認指定為。 接上次挖的坑,對koa2.x相關的源碼進行分析 第一篇。 不得不說,koa是一個很輕量、很優雅的http框架,尤其是在2.x以后移除了co的引入,使其代碼變得更為清晰。 express和ko...
閱讀 881·2021-11-22 09:34
閱讀 1020·2021-10-08 10:16
閱讀 1833·2021-07-25 21:42
閱讀 1801·2019-08-30 15:53
閱讀 3532·2019-08-30 13:08
閱讀 2193·2019-08-29 17:30
閱讀 3354·2019-08-29 17:22
閱讀 2185·2019-08-29 15:35