国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Koa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理

mrcode / 1182人閱讀

摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務(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ā)生的一系列事情。

啟動(dòng)

如果只是單純用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是如何來的。

有意思的地方 無new也可使用的構(gòu)造函數(shù)

首先看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方法開始。

啟動(dòng)服務(wù)器

首先是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)。

處理響應(yīng)

繼續(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一般,requestresponse方法涇渭分明。同時(shí)在使用過程中,是明顯有感覺到KoaExpress要便利的。而不僅僅是解決回調(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)并處理的圖,是這樣的:

中間件中的this

到這一步,這些東西就好解釋了。

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)流程便完整的走了一遍。

結(jié)語

有些感慨,也有些唏噓。
有很多想說的,但也感覺沒什么可說的。
就這樣吧。

前端路漫漫,且行且歌。

最后附上本人博客地址和原文鏈接,希望能與各位多多交流。

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

相關(guān)文章

  • Koa源碼閱讀筆記(4) -- ctx對(duì)象

    摘要:本筆記共四篇源碼閱讀筆記源碼閱讀筆記源碼閱讀筆記服務(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...

    ityouknow 評(píng)論0 收藏0
  • Koa源碼閱讀筆記(2) -- compose

    摘要:于是抱著知其然也要知其所以然的想法,開始閱讀的源代碼。問題讀源代碼時(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) -...

    roland_reed 評(píng)論0 收藏0
  • Koa源碼閱讀筆記(1) -- co

    摘要:正好自己之前也想看的源代碼,所以趁著這個(gè)機(jī)會(huì),一口氣將其讀完。源碼解讀的源代碼十分簡潔,一共才兩百余行。結(jié)語的源代碼讀取來不難,但其處理方式卻令人贊嘆。而且閱讀的源代碼,是閱讀源碼的必經(jīng)之路。 本筆記共四篇Koa源碼閱讀筆記(1) -- coKoa源碼閱讀筆記(2) -- composeKoa源碼閱讀筆記(3) -- 服務(wù)器の啟動(dòng)與請(qǐng)求處理Koa源碼閱讀筆記(4) -- ctx對(duì)象 起...

    taoszu 評(píng)論0 收藏0
  • Koa2源碼閱讀筆記

    摘要:引言最近空閑時(shí)間讀了一下的源碼在閱讀的源碼的過程中,我的感受是代碼簡潔思路清晰不得不佩服大神的水平。調(diào)用的時(shí)候就跟有區(qū)別使用必須使用來調(diào)用除了上面的的構(gòu)造函數(shù)外,還暴露了一些公用的,比如兩個(gè)常見的,一個(gè)是,一個(gè)是。 引言 最近空閑時(shí)間讀了一下Koa2的源碼;在閱讀Koa2(version 2.2.0)的源碼的過程中,我的感受是代碼簡潔、思路清晰(不得不佩服大神的水平)。下面是我讀完之后...

    plus2047 評(píng)論0 收藏0
  • koa源碼閱讀[1]-koakoa-compose

    摘要:接上次挖的坑,對(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...

    vibiu 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<