Koa源碼分析(三) -- middleware機制的實現

Abstract

本系列是關於Koa框架的文章,目前關注版本是Koa v1。主要分爲如下幾個方面:html

  1. Koa源碼分析(一) -- generator
  2. Koa源碼分析(二) -- co的實現
  3. Koa源碼分析(三) -- middleware機制的實現

Koa歸納

Koa是基於generator與co之上的新一代中間件框架,它的優點主要集中在如下幾個方面node

  1. 中間件機制
  2. 封裝了request/response, context對象
  3. 使用yield,方便異步編程進行流程控制
  4. 在忽略同步或者異步的狀況下,使用try catch能夠獲取程序運行中的異常(錯誤處理是服務端程序的核心)

示例代碼

var Koa = require('koa');
var app = new 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
*/

從上述代碼中,咱們添加了兩個middleware,其中第一個middleware中有一個輸入參數next,並經過yield進行調用。經過分析輸出的log信息,不難發現,先運行middelware1中的yield以前的代碼,而後進入到middleware2中運行,待middleware2運行結束後又回到middleware1中,並運行yield以後的代碼。編程

因爲app.use輸入的是generator函數,若是熟悉generator函數的同窗,或許會說,這是將middleware2做爲middleware1中的next參數,依次調用多個generator函數。對,沒錯,實際運行就是這樣的,可是koa框架是如何組織代碼實現這樣方面的調用,將地獄式調用的異步編程編程這樣清晰的結構?請看下文的源碼分析數組

源碼分析

Application初始化

function Application() {
    if (!(this instanceof Application)) return new Application;
    this.env = process.env.NODE_ENV || 'development';
    this.subdomainOffset = 2;
    // 用於存放中間件,即generator對象
    this.middleware = [];
    this.proxy = false;
    // 得到封裝的上下文對象
    this.context = Object.create(context);
    // 獲取封裝的請求對象
    this.request = Object.create(request);
    // 獲取封裝的響應對象
    this.response = Object.create(response);
}

啓動服務

listen() {
    debug('listen');
    // 調用node原生中的建立服務
    // 其中callback()是服務建立的核心,具體見下面分析
    const server = http.createServer(this.callback());
    // 開啓服務的監聽
    return server.listen.apply(server, arguments);
}

添加中間件

app.use = function(fn){
    if (!this.experimental) {
        // es7 async functions are not allowed,
        // so we have to make sure that `fn` is a generator function
        assert(fn && 'GeneratorFunction' == fn.constructor.name, 'app.use() requires a generator function');
    }
    debug('use %s', fn._name || fn.name || '-');
    // 將輸入的fn依次push到middleware數組中
    this.middleware.push(fn);
    // 返回this,以便鏈式調用
    return this;
};

node native 建立服務

app.callback = function(){
    if (this.experimental) {
        console.error('Experimental ES7 Async Function support is deprecated. Please look into Koa v2 as the middleware signature has changed.')
    }
    // 將中間件按照加入的順序,實現yield的鏈式調用,即組織異步調用結構,詳細見下面的compose
    // co.wrap方法將generator函數轉化爲Promise
    var fn = this.experimental ? compose_es7(this.middleware) : co.wrap(compose(this.middleware));
    var self = this;

    if (!this.listeners('error').length) this.on('error', this.onerror);

    // 返回node native的請求處理函數
    return function handleRequest(req, res){
        res.statusCode = 404;
        var ctx = self.createContext(req, res);
        onFinished(res, ctx.onerror);
        fn.call(ctx).then(function handleResponse() {
            respond.call(ctx);
        }).catch(ctx.onerror);
    }
};

中間件異步構建

// 返回一個啓動函數
function compose(middleware){
    return function *(next){
        if (!next) next = noop();
        var i = middleware.length;
        // 對中間件隊列從後遍歷,逐個獲取對應的generator對象
        while (i--) {
            // 將後面的generator對象傳遞給前面中間件的generatorFunction
            next = middleware[i].call(this, next);
        }
        // 返回一個yield,next指向第一個中間件的generator
        return yield *next;
    }
}
function *noop(){}

這樣,咱們就從返回的啓動函數(generator函數)的yield處指向第一個中間件,而後從以前while循環構成的從前日後的調用鏈,依次調用下一個中間件,直至最後一箇中間件而後再返回。app

這邊咱們再次回到callback()這個啓動函數處,調用co.wrap()實現對generator函數的逐步調用。框架

相關文章
相關標籤/搜索