理解 Koa 框架中介軟體原理
ofollow,noindex">原文連結
Node 主要用在開發 Web 應用,koa 是目前 node 裡最流行的 web 框架。
在 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
模組,http
的createServer
方法建立了一個http.Server
的例項。 -
server
監聽3000
埠。 - 我們傳入到
createServer
裡的函式實際是監聽request
事件的回撥,每當請求進來,監聽函式就會執行。 -
request
事件的監聽函式,其函式接受兩個引數,分別是req
和res
。其中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
先上碼
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); } } }; } 複製程式碼
我試圖去簡化一下這個方法,但方法本身已經足夠簡潔。
程式碼很簡潔。
通過 next()傳遞
實現中介軟體呼叫, 結合 Promise
採用 遞迴呼叫 的通知機制。
看圖

這種形式的控制流讓整個 Koa 框架中介軟體的訪問呈現出 自上而下的中介軟體流
+ 自下而上的 response 資料流
的形式。
Koa 本身做的工作僅僅是定製了中介軟體的編寫規範,而不內建任何中介軟體。一個 web request
會通過 Koa 的中介軟體棧,來動態完成 response
的處理。
koa 在中介軟體語法上面採用了 async
+ await
語法來生成 Promise
形式的程式控制流。
總結
koa 是非常精簡的框架, 其中的精粹思想就是洋蔥模型(中介軟體模型), koa 框架的中介軟體模型非常好用並且簡潔, 但是也有自身的缺陷, 一旦中介軟體陣列過於龐大, 效能會有所下降,我們需要結合自身的情況與業務場景作出最合適的選擇.