Koa2源碼設計之中間件和屬性代理

前言

nodeJS自從2009年Ryan Dahl開始基於V8引擎在github上發布了最開始的版本,

發展的勢頭迅速蔓延到前端(瀏覽器)領域及後端web框架領域。

正如java中的spring框架,python中的flask框架一樣,nodeJS自出世後就迅速在開源社區掀起了造輪子熱潮。

相比total.js、Flatiron這類不溫不火的框架,Express和Koa框架可謂是其中的領頭羊,

在技術社區不僅口碑良好,而且讓開發者可以開啟easy模式進行後端開發。

筆者在這個月開始採用Koa2來開發公司的一個node項目,在開發過程中閱讀了一遍Koa2源碼。

令我感到欣喜的是,在閱讀Koa2的源碼的過程中,我感受到的不僅是代碼的簡短,更是Koa團隊執筆的清晰思路。

下面就寫下個人對koa2框架中屬性代理和中間件語法的理解。

一個簡單的http伺服器

首先,使用下面的幾行代碼,就可以輕易的搭建一個nodeJS的http伺服器。

const http = require(http);n http.createServer((req,res)=>{n //做你想做的動作n res.writeHead(200,{Content-Type:"text/plain"});n res.end(ok)n }).listen(3000)n

我們可以看到,一個經典的web應用模型最重要的兩塊數據模型就是request和response。 而編寫一個web框架最先要解決的就是對這兩塊的處理和設計規範了。

下面我們就以這個為基礎開始探討Koa2在web框架上的設計理念、屬性代理、中間件編寫方式。

Koa的設計理念

Koa是一個輕量級的、極富表現力的http開發框架。

一個web request會通過Koa的中間件棧,來動態完成response的處理。

同時,Koa2採用了async和await的語法來增強中間件的表現力。

Koa本身做的工作僅僅是定製了中間件的編寫規範,而不內置任何中間件。

做過大型開源項目或者多人協作項目的同學應該都很有體會,

好的代碼規範和設計方式的制定往往比寫出好的代碼更重要和更可控。

koa的設計理念,讓這個框架很容易發展龐大的中間件生態圈和保持高可維護性。

這是top-level的事情。

Koa的中間件編寫形式

一個簡單的Koa2中間件通過下面幾行代碼就可以實現,我們就基於下面的代碼展開討論

app.use(async (ctx, next) => {n try {n await next(); // next is now a functionn } catch (err) {n ctx.body = { message: err.message };n ctx.status = err.status || 500;n }n });n

1. 語法層面syntax(async + await)

async (ctx,next)=>{n await next();n // 做你的工作n }n

koa2在中間件語法上面採用了async+await語法來生成Promise形式的程序控制流。

這種形式的控制流讓整個Koa框架中間件的訪問呈現出自上而下的中間件流 + 自下而上的response數據流的形式。

官方對控制流的gif描述要比我形象許多,在這裡我直接貼下官方的gif圖片描述。

同時,值得一提的是async和await的內部技術實現上是採用的Promise的形式,所以return Promise可以直接掛到await後面

var p = await new Promise(resolve => {n setTimeout(() => {n resolve(10);n }, 2000);n console.log(p) //2秒後列印10n

2. 數據流層面next(指針信使)

koa2在實現如圖所示的中間件調用方式上是通過next()來傳遞的。

和傳統的(如redux)在中間件執行方式上不同,Koa2採用了koa-compose模塊在結合Promise後採用的是遞歸調用的通知機制形式,我在這邊直接貼下koa-compose的核心代碼。

// return Promisen function (context, next) {n // last called middleware #n let index = -1n return dispatch(0)n function dispatch (i) {n if (i <= index) n return Promise.reject(new Error(next() called multiple times))n index = in let fn = middleware[i] //調用下一個middlewaren if (i === middleware.length) fn = nextn if (!fn) return Promise.resolve()n try {n //在middleware中傳遞next方法n return Promise.resolve(fn(context, function next () {n return dispatch(i + 1)n }))n } catch (err) {n return Promise.reject(err)n }n }n }n

根據我的注釋,可以看到Koa-compose基本就是個中間件dispatch的遞歸調用,每一個middlewareFunction的第二個參數(也就是next)是動態傳遞進去的信使,它會調取下一次的中間件執行dispatch(index)。

這個過程導致了中間件函數的不斷嵌套調用,使得所有的中間件能夠自上而下被執行。

同時,由於中間件採用的async+await的寫法在編譯後,返回的是根據context._send的switch調用,所以在調用await next()後的代碼又會呈現自下而上執行的現象。

3. 屬性代理層面之getter,setter,delegates

在koa2中另一個必須提及的點就是屬性代理。

這種模式結合了setter和getter(好處參見Vue.js的vm屬性digest的設計)和delegate方式(比對前端框架中常用的事件代理),讓開發者直接操作ctx.status=500即可完成ctx.response.status=500的工作,大大簡化了開發者的代碼量和邏輯工作。

如下,便是源碼中實現statusCode屬性代理的部分。

/**n * //文件位置 koa/lib/response.jsn * //代碼作用 set response status code.n */n set status(code) {n this._explicitStatus = true;n this.res.statusCode = code;n this.res.statusMessage = statuses[code];n if (this.body && statuses.empty[code]) this.body = null;n },n /**n * //文件位置 koa/lib/context.jsn * //代碼作用 Response delegationn */n var delegate =require(delegates);n delegate(ctx, response)n .access(status);n

總體而言,這個設計思想還是很犀利的,而且對開發者足夠友好。

為了方便大家熟悉koa的屬性代理,我把源碼中屬性代理的一部分,根據method/access/getter分類貼在了下面。

/**n * 請求處理的代理列表n * Response delegationn */n delegate(proto, response)n method:[ attachment , redirect , n remove ,vary ,set ,n append ,flushHeaders ],n access:[status , message , n body ,length ,type,n lastModified ,etag ],n getter:[ headerSent ,writable ]n /**n * 請求的代理列表[Incoming message]n * Request delegationn */n delegate(proto, request)n method:[ acceptsLanguages , acceptsEncodings , n acceptsCharsets ,accepts ,n get ,is ],n access:[ querystring ,idempotent , n socket ,search ,method ,n query ,path ,url ],n getter:[ origin , href ,subdomains ,n protocol ,host ,hostnamen header ,headers ,secure ,n stale ,fresh ,ips ,ip ]n

結語

難得寫篇文章工作量還是蠻大的,這次關於Koa2就先分析到這邊,希望對大家有所裨益。

原文地址參見我的github博客

最後,安利下最近在我在公司寫的一個koa2的boilerplate提供給大家作為快速開發的模板項目。 這個項目是個比較典型的MVC(middleware+ view + controller)項目模板。

它集成了日誌系統、router、promise network、error handling和不同的開發環境配置,整體我覺得功能和設計上還是不錯的。

koa2版node項目腳手架

參考資料

  1. get set
  2. async await

推薦閱讀:

node.js教程7--KOA入門&發送郵件
Vue 全家桶 + Express 實現的博客(後端API全部自己手寫,很適合剛學習vue以及express的小夥伴學習)
用nvm改變node版本的時候全局模塊找不到
[譯]擴展 Node.js 應用

TAG:前端开发 | Nodejs | koa |