半小時(shí)掌握koa

半小時(shí)掌握koa

<h3 id="koa分析與實(shí)現(xiàn)">koa分析與實(shí)現(xiàn)</h3>

<h4 id="example">1.從一個(gè)例子開(kāi)始</h4>

'use strict';

let Application = require('koa');

let app = new Application();

app.use(function*(next) {
  console.log('東方紅');
  yield next;
  console.log('太陽(yáng)升');
  console.log();
});

app.use(function*(next) {
  console.log('-1979年,');
  yield next;
  console.log('-那是一個(gè)春天');
});

app.use(function*() {
  let message = '--我愛(ài)北京,天安門(mén)';
  console.log(message);
  this.body = 'hello world';
});

const PORT = 8888;
app.listen(PORT);

console.log('start at ' + PORT);

上面代碼闸盔,先后添加了3個(gè)middleware。請(qǐng)求的結(jié)果如下:
<pre>
東方紅
-1979年,
--我愛(ài)北京,天安門(mén)
-那是一個(gè)春天
太陽(yáng)升
</pre>

<h4 id="abstract">2.koa之流程</h4>

GitHub
GitHub
  • 每一個(gè)請(qǐng)求到達(dá)服務(wù)器后,初始化請(qǐng)求上下文對(duì)象
  • 將上下文對(duì)象按照順序,在每個(gè)中間件中運(yùn)行一遍
    • 執(zhí)行中間件1默刚,next前的邏輯
    • next
      • 執(zhí)行中間件2的邏輯
    • 執(zhí)行中間件1媚创,next后的邏輯
  • 將body返回給客戶(hù)端

上面的過(guò)程就是創(chuàng)建http服務(wù)器的時(shí)候,callback參數(shù)應(yīng)該做的事情蹄殃。

<h4 id="appObjectAnalyse">3.app對(duì)象分析</h4>

  • 屬性:由于這里只是簡(jiǎn)單的山寨一下koa,所以只需要一個(gè)middleware數(shù)組你踩,用來(lái)存儲(chǔ)中間件诅岩。如果感興趣,想完善的話可以添加其他的屬性带膜,實(shí)現(xiàn)相關(guān)功能吩谦。
  • 公共方法:從開(kāi)始的例子可以輕松看出有如下2個(gè)方法。
    • use
    • listen
  • 私有方法:
    • callback:創(chuàng)建http服務(wù)器的時(shí)候需要提供的回調(diào)函數(shù)
    • response:相應(yīng)函數(shù)

<h4 id="callback">4.callback實(shí)現(xiàn)</h4>

先從callback下手膝藕。因?yàn)槭酵ⅲ鉀Q了這個(gè)函數(shù),其他函數(shù)的實(shí)現(xiàn)就是輕松加愉快芭挽。
不過(guò)滑废,開(kāi)始我們就會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題蝗肪。回調(diào)函數(shù)是node底層調(diào)用的蠕趁,我們沒(méi)有辦法把middleware參數(shù)傳遞進(jìn)薛闪。
這個(gè)時(shí)候,閉包玩的比較熟練的同學(xué)也許就會(huì)心中竊喜了俺陋。
沒(méi)錯(cuò)解決這個(gè)問(wèn)題的常用手段就是使用高階函數(shù)豁延,利用js的閉包這一特性。

function callback(middleware) {
    return function (req, res) {
    }
}

解決了上邊的問(wèn)題腊状,我們順著流程寫(xiě)就好了诱咏。

首先構(gòu)造上下文對(duì)象

function callback(middleware) {
    return function (req, res) {
        let ctx = {
          req: req,
          res: res
        };
    }
}

之后需要做的事情就是,順序調(diào)用每一個(gè)middleware缴挖。但是我們手中有的數(shù)據(jù)結(jié)構(gòu)只是一個(gè)generator function array胰苏,顯然完成不了任務(wù)。因此醇疼,考慮將它map一下硕并,讓它變成一個(gè)類(lèi)似function鏈表一樣的東西。

代碼如下:

let next = function*(){};

var i = middleware.length;
while (i--) {
    next = middleware[i].call(ctx, next);
}

ps: 這里有一點(diǎn)不知道大家發(fā)現(xiàn)了沒(méi)有Q砭!倔毙!在每一個(gè)請(qǐng)求到來(lái)的時(shí)候,都需要進(jìn)行一次這種map乙濒。也就是說(shuō)陕赃,掛載的中間件越多,性能就會(huì)越差颁股。我覺(jué)得這也是么库,koa1在性能上比express差的一個(gè)原因吧。 性能測(cè)試

接著甘有,調(diào)用一下鏈表頭的function诉儒,就可以實(shí)現(xiàn)middleware的調(diào)用。

function callback(middleware) {
  return function (req, res) {
    let ctx = {
      req: req,
      res: res
    };
    co(function *() {
      let next = function*(){};

      var i = middleware.length;
      while (i--) {
        next = middleware[i].call(ctx, next);
      }
      return yield next;
    })
  }
}

最后亏掀,就是將上下文的body返回給前端忱反。

function callback(middleware) {
  return function (req, res) {
    let ctx = {
      req: req,
      res: res
    };
    co(function *() {
      let next = function*(){};

      var i = middleware.length;
      while (i--) {
        next = middleware[i].call(ctx, next);
      }
      return yield next;
    }).then(() => response.call(ctx))
      .catch(e => console.log(e.stack));
  }
}

以上就是整個(gè)callback的代碼炕横,不過(guò)錯(cuò)誤處理比較粗糙主穗。大家可以自己完善一下-

<h4 id="otherFn">5.其他function</h4>

其他function就比較簡(jiǎn)單了佳头。分別寫(xiě)在下面

app.listen = function () {
  var server = http.createServer(callback(this.middleware));
  return server.listen.apply(server, arguments);
};
app.use = function (fn) {
  this.middleware.push(fn);
};
function response() {
  this.res.writeHead(200, {
    'Content-Type': 'text/plain'
  });
  this.res.end(this.body);
}

最后整個(gè)myKoa的代碼

'use strict';

let co = require('co');
let http = require('http');

function Application() {
  this.middleware = [];
}

var app = Application.prototype;


app.listen = function () {
  var server = http.createServer(callback(this.middleware));
  return server.listen.apply(server, arguments);
};

app.use = function (fn) {
  this.middleware.push(fn);
};


function callback(middleware) {
  return function (req, res) {
    let ctx = {
      req: req,
      res: res
    };
    co(function *() {
      let next = function*(){};

      var i = middleware.length;
      while (i--) {
        next = middleware[i].call(ctx, next);
      }
      return yield next;
    }).then(() => response.call(ctx))
      .catch(e => console.log(e.stack));
  }
}

//------private function
function response() {
  this.res.writeHead(200, {
    'Content-Type': 'text/plain'
  });
  this.res.end(this.body);
}

module.exports = Application;
   

<h3 id="koaCompoment">koa與他的小伙伴</h3>

<h4 id="koa-router">koa-router</h4>

<h5 id="router-memory">1.內(nèi)存模型</h5>

GitHub
GitHub

上圖是一個(gè)簡(jiǎn)化的koa-router的內(nèi)存快照间影。
1個(gè)router最主要的屬性就是stack注竿,他是一個(gè)layer的數(shù)組。每當(dāng)我們調(diào)用一次router.verb或者router.use就會(huì)有一個(gè)layer被創(chuàng)造出來(lái),放到這個(gè)數(shù)組中巩割。
如上圖所示胰丁,每個(gè)layer主要有4個(gè)屬性。
請(qǐng)大家喂分,務(wù)必記住這個(gè)內(nèi)存模型锦庸,后文的討論都會(huì)圍繞這張圖進(jìn)行。

<h5 id="router-flow">2.請(qǐng)求處理流程</h5>

在上一部分曾經(jīng)說(shuō)過(guò)蒲祈,koa的每一個(gè)中間件都是一個(gè)generator function甘萧,koa-router也不能免俗。別看他代碼比koa要多梆掸,但是他就是一個(gè)函數(shù)而已扬卷。
接下來(lái)我們就看一下當(dāng)請(qǐng)求到達(dá)服務(wù)器后這個(gè)函數(shù)都干了些什么。

  • 首先便利router的stack數(shù)組酸钦,通過(guò)每一個(gè)layer的正則版本的路徑檢查當(dāng)前請(qǐng)求的path是否符合這則表達(dá)式怪得。
  • 通過(guò)了第一步的檢查的layer,進(jìn)入了第二階段的篩選卑硫。這個(gè)階段的標(biāo)準(zhǔn)是layer的methods屬性欢伏,methods為空數(shù)組或者methods數(shù)組中有當(dāng)前請(qǐng)求的verb便通過(guò)篩選。
  • 通過(guò)篩選的layer會(huì)被放入一個(gè)叫做pathAndMethod的數(shù)組中
  • 我們下載可以看下下我們又有的數(shù)據(jù)結(jié)構(gòu)-->一個(gè)fn*的二維數(shù)組酿雪。是否有種似曾相識(shí)的感覺(jué)囊墓毒。那么踪栋,下一步就是講數(shù)組變成fn鏈表,然后調(diào)用一下開(kāi)頭的fn捏检。

以上就是koa-router的整個(gè)流程熊楼。

<h5 id="router.verb">3.verb</h5>
相信大家對(duì)于這個(gè)方法的用法應(yīng)該很熟悉了吧,我就在這里不多說(shuō)了能犯。
這里鲫骗,我想強(qiáng)調(diào)的是。每次使用這個(gè)方法的時(shí)候就會(huì)增加一個(gè)layer踩晶。也就是說(shuō)篩選合適的layer的操作消耗的時(shí)間就會(huì)變多执泰。在設(shè)計(jì)跟優(yōu)化程序的時(shí)候應(yīng)該注意這個(gè)特征。
另外渡蜻,推薦一種性能更好的使用方法

//method1
router.get('/blabla',fn1,fn2,...)

//method2
router.use('/blabla',fn1)
router.get('/blabla',fn2)

method1和2可以實(shí)現(xiàn)相同的功能术吝,但是method1只會(huì)創(chuàng)建一個(gè)layer计济,這個(gè)layer會(huì)有兩個(gè)middleware。不難發(fā)現(xiàn)method1的效率會(huì)更高一些排苍。
好了verb就說(shuō)到這了吧沦寂。

<h5 id="router.use">3.use</h5>
關(guān)于這個(gè)方法,我想說(shuō)的是淘衙,use不但可以u(píng)se一個(gè)自己寫(xiě)的中間件传藏,而且還可以u(píng)se一個(gè)router。這種方法可以讓我們實(shí)現(xiàn)用文件夾定義url的效果彤守,我個(gè)人覺(jué)得這是一種有沒(méi)得方法毯侦。

var forums = new Router();
var posts = new Router();

posts.get('/', function *(next) {...});
posts.get('/:pid', function *(next) {...});
forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods());

// responds to "/forums/123/posts" and "/forums/123/posts/123"
app.use(forums.routes());

我想,也許有人會(huì)好奇這個(gè)功能是怎么實(shí)現(xiàn)的吧遗增,當(dāng)時(shí)看到api文檔的時(shí)候我也很好奇叫惊。下面我就來(lái)說(shuō)一下吧。
直接上源代碼:

middleware = middleware.filter(function (fn) {
    if (fn.router) {
      fn.router.stack.forEach(function (layer) {
        if (path) layer.setPrefix(path);
        if (router.opts.prefix) layer.setPrefix(router.opts.prefix);
        router.stack.push(layer);
      });

      if (router.params) {
        Object.keys(router.params).forEach(function (key) {
          fn.router.param(key, router.params[key]);
        });
      }

      return false;
    }

    return true;
  });

從上面可以很輕易的看出做修,作者是先檢測(cè)了一下這個(gè)fn霍狰,發(fā)現(xiàn)如果是router,那么讀取這個(gè)router的所有l(wèi)ayer饰及,把每一個(gè)layer設(shè)置一下前綴蔗坯,然后直接放到父router的stack中。機(jī)智吧-.

<h5 id="router.param">3.param </h5>
最后要說(shuō)的是param這個(gè)方法燎含。

router
  .param('user', function *(id, next) {
    this.user = users[id];
    if (!this.user) return this.status = 404;
    yield next;
  })
  .get('/users/:user', function *(next) {
    this.body = this.user;
  })
  .get('/users/:user/friends', function *(next) {
    this.body = yield this.user.getFriends();
  })
  // /users/3 => {"id": 3, "name": "Alex"}
  // /users/3/friends => [{"id": 4, "name": "TJ"}]

當(dāng)時(shí)宾濒,看到這段代碼的時(shí)候,確實(shí)激動(dòng)了一把屏箍。如作者說(shuō)的绘梦,這個(gè)功能可以很方便的實(shí)現(xiàn)auto-loading作者是validation。
但是赴魁,但是卸奉,但是!S庇i谩!E斯啊U铞!同志們一定要注意芦岂,這里面有一個(gè)小小的坑瘪弓。
閑言碎語(yǔ)不要說(shuō),我們直接上代碼

var app = require('koa')();
var router = require('koa-router')();
       
router
  .get('/test/:id', function *() {
    console.log('get', this.test);
    this.body = 'hello world';
  })
  .use('/test/:id', function*(next) {
    console.log('use', this.test)
    yield next;
  })
  .param('id', function * (id, next) {
    this.test = this.test || 0;
    this.test = this.test + id;
    yield next;
  });

app.use(router.routes())
  .use(router.allowedMethods());

app.listen(8888);

/**
輸出結(jié)果
use 01
get 011
**/

相信眼尖的通知們已經(jīng)知道發(fā)生了什么盔腔!沒(méi)錯(cuò)杠茬,param對(duì)應(yīng)的fn執(zhí)行了兩遍月褥,這是因?yàn)閜aram會(huì)把中間件放到layer的middleware數(shù)組中弛随。
好了瓢喉,雖然有點(diǎn)小小的瑕疵,但是舀透,用的時(shí)候注意就好了栓票。這個(gè)功能還是很好用的,吼吼吼愕够。

以上就是koa-router我想說(shuō)的全部?jī)?nèi)容走贪,至于怎么用的話,看一下api文檔就好了惑芭,我只能說(shuō)作者設(shè)計(jì)的很簡(jiǎn)潔坠狡,一切說(shuō)明都是廢話。

希望我也能早日設(shè)計(jì)出這么有沒(méi)得api遂跟。

<h4 id="koa-body">koa-body</h4>

這個(gè)組件使用起來(lái)也是非常方便的逃沿,在這里說(shuō)一下的目的是,由于前一陣子公司使用的body parse用起來(lái)難用的不要不要的幻锁。所以在這里推薦一下吧凯亮。

下面的內(nèi)容基本就是readme的翻譯,沒(méi)興趣的可以直接不看哄尔。

<h5>1.簡(jiǎn)介</h5>
koa-body支持multipart, urlencoded 和 json 請(qǐng)求體假消,提供跟express的multer一樣的功能。他是對(duì)co-body和formidable的封裝富拗。

<h5>2.使用方法</h5>
koa-body可以向multer一樣使用鸣戴,非常簡(jiǎn)單葵擎,因?yàn)槟憧梢詮腸tx.request.body 或者 ctx.req.body 中獲得fields和files

var app      = require('koa')(),
    koaBody   = require('koa-body');

app.use(koaBody({formidable:{uploadDir: __dirname}}));
app.use(function *(next) {
  if (this.request.method == 'POST') {
    console.log(this.request.body);
    // => POST body
    this.body = JSON.stringify(this.request.body);
  }
  yield next;
});
app.listen(3131)

koa-body還可以跟koa-router一起使用

var app     = require('koa')(),
    router  = require('koa-router')(),
    koaBody = require('koa-body')();

router.post('/users', koaBody,
  function *(next) {
    console.log(this.request.body);
    // => POST body
    this.body = JSON.stringify(this.request.body);
  }
);

app.use(router.routes());

<h5>3.參數(shù)</h5>

  • patchNode bool 把request的body給node的ctx.req签餐,默認(rèn)為false盯串。
  • patchKoa bool 把request的body給koa的ctx.req体捏,默認(rèn)為false。
  • jsonLimit String|Integer json body的最大byte數(shù)沃呢,默認(rèn)為1mb
  • formLimit String|Integer form body的最大byte數(shù)薄霜,默認(rèn)為56kb
  • textLimit String|Integer text body的最大byte數(shù)纸兔,默認(rèn)為56kb
  • encoding String 設(shè)置field的編碼,默認(rèn)為utf-8
  • multipart Boolean 是否解析multipart body崎坊,默認(rèn)為false
  • formidable Object 床底給formidable的設(shè)置項(xiàng)
  • strict bool 如果激活洲拇,koa-body不會(huì)解析GET,HEAD,DELETE請(qǐng)求呻待,默認(rèn)為true
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奏篙,隨后出現(xiàn)的幾起案子秘通,更是在濱河造成了極大的恐慌敛熬,老刑警劉巖应民,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件繁仁,死亡現(xiàn)場(chǎng)離奇詭異黄虱,居然都是意外死亡庸诱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)昧识,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)滞诺,“玉大人环疼,你說(shuō)我怎么就攤上這事炫隶⊙植埽” “怎么了伪阶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)栅贴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)檐薯,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任注暗,我火速辦了婚禮坛缕,結(jié)果婚禮上捆昏,老公的妹妹穿的比我還像新娘骗卜。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布焚刺。 她就那樣靜靜地躺著兄淫,像睡著了一般屯远。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捕虽,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天慨丐,我揣著相機(jī)與錄音,去河邊找鬼泄私。 笑死房揭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的晌端。 我是一名探鬼主播捅暴,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咧纠!你這毒婦竟也來(lái)了蓬痒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤漆羔,失蹤者是張志新(化名)和其女友劉穎梧奢,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體演痒,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亲轨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸟顺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惦蚊。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诊沪,靈堂內(nèi)的尸體忽然破棺而出养筒,到底是詐尸還是另有隱情,我是刑警寧澤端姚,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布晕粪,位于F島的核電站,受9級(jí)特大地震影響渐裸,放射性物質(zhì)發(fā)生泄漏巫湘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一昏鹃、第九天 我趴在偏房一處隱蔽的房頂上張望尚氛。 院中可真熱鬧,春花似錦洞渤、人聲如沸阅嘶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)讯柔。三九已至抡蛙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間魂迄,已是汗流浹背粗截。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捣炬,地道東北人熊昌。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像湿酸,于是被迫代替她去往敵國(guó)和親婿屹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容