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