koa中間件分析


轉載請注明: TheViper http://www.cnblogs.com/TheViper 

另外可以參考http://purplebamboo.github.io/2014/05/24/koa-source-analytics-3/,作者用簡單的方式造了一個山寨koa.

koa是什么?

koa是從2013年11月開始發布,更新的。和express相比,koa太年輕了.但它(用文檔上的話說)通過組合不同的 generator,可以免除重復繁瑣的回調函數嵌套,並極大地提升常用錯誤處理效率。Koa 不在內核方法中綁定任何中間件,僅僅提供了一個輕量優雅的函數。

嚴格的說koa並不是完整的web框架,實際使用的時候,開發者根據自己的需要,需要用到其他的中間件。

koa只是提供了一種不同於connect的中間件解決方案,另外再加上一些對request,response的簡單封裝。這個看它的api就看的出來。比如,對請求/login?name=a&pwd=b,直接用koa的api,只能取url,query(name=a&pwd=b),它不像其他的框架,會進行進一步的封裝,將請求的參數封裝成鍵值形式。這時,需要用其他中間件如koa-bodyparser來完成。

還可能用到其他的中間件,具體參見https://github.com/koajs/koa/wiki,比如,

var path = require('path')
var route= require('koa-route');//路由
var koa = require('koa');
var gzip = require('koa-gzip');//gzip壓縮
var staticCache = require('koa-static-cache');//在響應中添加對靜態文件緩存的header
var json = require('koa-json');//返回json格式的響應
var bodyParser = require('koa-bodyparser');//解析請求參數
var app = koa();

var user_controller=require('./app/controllers/userController');

app.use(staticCache(path.join(__dirname, 'public'), {
  maxAge:24 * 60 * 60
}))
app.use(bodyParser());
app.use(gzip());
app.use(route.post('/login', new user_controller().login));
app.use(json({pretty: false}));

app.listen(8000);

下面分析koa中間件的實現。

使用方式

var koa = require('koa');
var app = koa();
//添加中間件1
app.use(function *(next){
  var start = new Date;
  console.log("start=======1111");
  yield next;
  console.log("end=======1111");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
});
//添加中間件2
app.use(function *(){
  console.log("start=======2222");
  this.body = 'Hello World';
  console.log("end=======2222");
});

app.listen(3000);
/*
start=======1111
start=======2222
end=======2222
end=======1111
GET / - 10
start=======1111
start=======2222
end=======2222
end=======1111
GET /favicon.ico - 5
*/

app.use()來添加中間件。use函數接受一個generator function。這個generator function就是一個中間件。generator function有一個參數next。這個next是下一個中間件generator function的對應generator對象。yield next;調用下一個中間件的代碼。具體的,

application.js

app.callback = function(){
  var mw = [respond].concat(this.middleware);
  var gen = compose(mw);
  var fn = co.wrap(gen);
  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).catch(ctx.onerror);
  }
};
app.listen = function(){
  debug('listen');
  var server = http.createServer(this.callback());
  return server.listen.apply(server, arguments);
};

listen()時,執行callback(),里面返回function(req,res)回調,然后是對node原生的監聽listen().也就是說,上面代碼相當於

var http = require("http");

http.createServer(function(request, response) {
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).catch(ctx.onerror);
}).listen(8888);

下面開始分析回調,

app.use = function(fn){
  assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
  debug('use %s', fn._name || fn.name || '-');
  this.middleware.push(fn);
  return this;
};

app.use只是把回調的generator function添加到middleware數組。注意,中間件必須是generator function。比如koa-json

module.exports = function(opts){
  。。。。。。。。。
  return function *filter(next){
    yield *next;
    。。。。。。。
  }
};

這也是編寫中間件的格式。在啟動文件中koa-json()返回generator function。

繼續說callback()回調

app.callback = function(){
  var mw = [respond].concat(this.middleware);
  var gen = compose(mw);
  var fn = co.wrap(gen);
  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).catch(ctx.onerror);
  }
};

首先將response添加到middleware中間件數組中,response也被寫出了generator function形式。注意數組中中間件的順序,后面可以看到這樣做的目的。然后compose(mw)用到了koa-compose模塊。

function compose(middleware){
  return function *(next){
    var i = middleware.length;
    var prev = next || noop();
    var curr;

    while (i--) {
      curr = middleware[i];
      prev = curr.call(this, prev);
    }

    yield *prev;
  }
}

可以看到compose的作用就是向每個中間件的next參數,按照它們在數組中的順序,傳入下一個中間件的generator function,有點像鏈表。compose(mw)返回generator.

然后co.wrap(gen);,新點的koa用的是co 4.x,co 4.x基於promise,添加了wrap()方法,該方法是將generator function變成promise供co使用。

老點的koa如0.8.1,在這里是var fn = co(gen);

回調里面self.createContext(req, res);,將node原生的request,response傳入koa,封裝。

onFinished(res, ctx.onerror);,onFinished是當請求關閉,完成或出現錯誤時,專門封裝的的模塊,這里不作討論。

fn.call(ctx).catch(ctx.onerror);,開始執行co(gen)()。

co分析中說到co內部有第一次的gen.next().后面異步操作成功后才會執行next(),但上面源碼卻找不到像next()的語句。為什么呢?

原因在於yield語句針對的對象。如果是yield generator function,那代碼會單步進入,如果單步進入后仍然有yield generator function,繼續單步進入。,無需用gen.next(),代碼會自動單步進入。如果有yield非函數,比如yield 'end',那相當於在那設置了一個斷點,代碼會暫時停在那。這時需要gen.next()才會繼續執行下面的代碼。

co分析中的例子因為里面有yield ‘start’,yield 'end'等語句,所以就需要多個gen.next()才能讓程序執行完。

下面是一個全是yield generator function的例子,是把前面的改了一下

function* run1() {
console.log("step in child generator1")
console.log("step out child generator1")
}

function* run() {
console.log("step in child generator")
var b=yield *run1();
console.log("step out child generator")
}
function* start() {
  yield *run();
  return 'over';
}
var it = start();
console.log(it.next());

結果

可以看到只用了一個next()就將所有的generator function都執行完了。

明白了這個再重新看compose,它是從數組最后的中間件開始,只要遇到yield,就單步進入,因為所有中間件都是以generator function的形式存在的,所以只要有第一次next(),就會執行到底,也就是執行到沒有yield next語句的中間件,然后又原路返回。具體的就像

來源:http://purplebamboo.github.io/2014/05/24/koa-source-analytics-3/

注意compose()里面yield *prev,此時i=0,prev就是*response()。

function *respond(next) {
  yield *next;
  .............
}

可以看到yield *next在最前面,就會單步進入下一個中間件。..。后面的過程,上面的圖說的很清楚。

不得不說,從compose到co,代碼一點都不復雜,但組合起來,就是一個非常巧妙的設計!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM