1. 程式人生 > >理解 Koa 框架中介軟體原理-看懂了compose方法,也就看懂了 Koa

理解 Koa 框架中介軟體原理-看懂了compose方法,也就看懂了 Koa

Node 主要用在開發 Web 應用,koa 是目前 node 裡最流行的 web 框架。

一個簡單的 http 服務

在 Node 開啟一個 http 服務簡直易如反掌,下面是官網 demo。

const http = require("http");

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader("Content-Type", "text/plain");
  res.end("Hello World\n");
});

const hostname =
"127.0.0.1"; const port = 3000; server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); });
  • 引入http模組,httpcreateServer方法建立了一個http.Server 的例項。
  • server監聽3000埠。
  • 我們傳入到createServer裡的函式實際是監聽request事件的回撥,每當請求進來,監聽函式就會執行。
  • request事件的監聽函式,其函式接受兩個引數,分別是reqres
    。其中 req 是一個可讀流,res 是一個可寫流。我們通過 req 獲取 http 請求的所有資訊,同時將資料寫入到 res 來對該請求作出響應。

Koa 應用

Koa 如何建立一個 server, 直接上個官網的例子

const Koa = require("koa");
const app = new Koa();

// x-response-time
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.
set("X-Response-Time", `${ms}ms`); }); // logger app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}`); }); // response app.use(async (ctx) => { ctx.body = "Hello World"; }); app.listen(3000);

中介軟體概念在程式設計中使用廣泛, 不管是前端還是後端, 在實際程式設計或者框架設計中都有使用到這種實用的模型。

基本上,Koa 所有的功能都是通過中介軟體實現的。

每個中介軟體預設接受兩個引數,第一個引數是Context物件,第二個引數是next函式。只要呼叫next函式,就可以把執行權轉交給下一個中介軟體。

如果中介軟體內部沒有呼叫next函式,那麼執行權就不會傳遞下去。

多箇中間件會形成一個棧結構(middle stack),以“先進後出”(first-in-last-out)的順序執行。整個過程就像,先是入棧,然後出棧的操作。

上面程式碼的執行順序是:
請求 -> x-response-time 中介軟體 -> logger 中介軟體 -> response 中介軟體 -> logger 中介軟體 -> response-time 中介軟體 -> 響應

Koa 的中介軟體機制(原始碼分析)

閱讀原始碼,化繁為簡,我們看看 koa 的中介軟體系統是如何實現的。

class Application extends Emitter {
  constructor() {
    super();
    this.middleware = [];
  },

  use(fn) {
    this.middleware.push(fn);
    return this;
  },

  callback() {
    const fn = compose(this.middleware);

    return function(req, res) {
      return fn(ctx);
    };
  },

  listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }
}

好了,精簡結束,一不小心,去枝末節,最後只剩下不到 20 行程式碼。

這就是框架的核心,簡化後的程式碼非常清晰,有點不可思議,但核心就是這麼簡單。

我們先分析以上程式碼做了什麼事。

  • 我們定義了一個middleware陣列來儲存中介軟體。
  • 我們定一個了一個use方法來註冊一箇中間件。其實就是簡單的push到自身的mideware這個陣列中。
  • 我們還使用了一個compose方法,傳入middleware,應該是做了一些處理,返回了一個可執行的方法。

你一定對中間的compose方法很好奇,初此之外的程式碼都容易理解,唯獨這個compose不太知道究竟做了什麼。其實,compose就是整個中介軟體框架的核心。

compose之外,程式碼已經很清楚的定義了

  • 中介軟體的儲存
  • 中介軟體的註冊

compose方法做了最為重要的一件事

  • 中介軟體的執行

compose,顧名思義,相當於把所有註冊的中介軟體組合,它規定了中介軟體的執行順序,返回一個“大”中介軟體,這個大中介軟體,就在 node 的 http 服務onRequest事件的時候執行。

核心 compose 方法

這是 compose 方法的原始碼,程式碼很簡潔。

function compose(middleware) {
  return function(context, next) {
    // last called middleware #
    let index = -1;
    return dispatch(0);
    function dispatch(i) {
      if (i <= index)
        return Promise.reject(new Error("next() called multiple times"));
      index = i;
      let fn = middleware[i];
      if (i === middleware.length) fn = next;
      if (!fn) return Promise.resolve();
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err);
      }
    }
  };
}

我試圖去簡化一下這個方法,但方法本身已經足夠簡潔。

通過compose方法,可以看出next指的就是下一個中介軟體,也就是我們所編寫的中介軟體方法的第二個引數,Koa 通過next()傳遞實現中介軟體呼叫,結合 Promise 採用 遞迴呼叫 的通知機制,最終完成整個中介軟體棧的執行。

Koa 在中介軟體語法上採用了async+await語法來生成Promise形式的程式控制流。

這種形式的控制流讓整個 Koa 框架中介軟體的訪問呈現出 自上而下的中介軟體流 + 自下而上的 response 資料流 的形式。

看懂了compose方法,也就看懂了 Koa,你大概不願相信如此簡單怎麼足以稱為框架,但情況就是這樣。

Koa 本身做的工作僅僅是定製了中介軟體的編寫規範,而不內建任何中介軟體。一個 webrequest會通過 Koa 的中介軟體棧,來動態完成response的處理。

總結

koa 是非常精簡的框架,其中的精粹思想就是洋蔥模型(中介軟體模型),koa 框架的中介軟體模型非常好用並且簡潔,但是也有自身的缺陷,一旦中介軟體陣列過於龐大,效能會有所下降,使用的時候要結合業務場景適當選擇。