JavaScript 設計模式學習第二十九篇- 中間件

(Middleware),又稱中介層,是提供系統軟件和應用軟件之間連接的軟件,以便於軟件各部件之間的溝通,特別是應用軟件對於系統軟件的集中的邏輯。中間件在企業架構中表示各種軟件套件,有助於抽象底層機制,比如操作系統 API、網絡通信、內存管理等,開發者只需要關注應用中的業務模塊。

從更廣義的角度來看,中間件也可以定義爲鏈接底層服務和應用的軟件層。後文我們主要使用 Node.js 裏最近很熱門的框架 Koa2 裏的中間件概念爲例,並且自己實現一箇中間件來加深理解。

1. 什麼是中間件

在 Express、Koa2 中,中間件代表一系列以管道形式被連接起來,以處理 HTTP 請求和響應的函數。換句話說,中間件其實就是一個函數,一個執行特定邏輯的函數。前端中類似的概念還有攔截器、Vue 中的過濾器、vue-router 中的路由守衛等。

工作原理就是進入具體業務之前,先對其進行預處理(在這一點上有點類似於裝飾器模式),或者在進行業務之後,對其進行後處理。

示意圖如下:

當接受到一個請求,對這個請求的處理過程可以看作是一個串聯的管道,比如對於每個請求,我們都想插入一些相同的邏輯比如權限驗證、數據過濾、日誌統計、參數驗證、異常處理等功能。對於開發者而言,自然不希望對於每個請求都特殊處理,因此引入中間件來簡化和隔離這些基礎設施與業務邏輯之間的細節,讓開發者能夠關注在業務的開發上,以達到提升開發效率的目的。

 

2. Koa 裏的中間件

2.1. Koa2 裏的中間件使用

Koa2 中的中間件形式爲:

app.use(async function middleware(context, next){ 
    // ... 前處理
    await next() // 下一個中間件
    // ... 後處理
})

其中第一個參數 context作爲上下文封裝了request 和 response 信息,我們可以通過它來訪問request 和 response;next 是下一個中間件,當一箇中間件處理完畢,調用 next() 就可以執行下一個中間件,下一個中間件處理完再使用 next(),從而實現中間件的管道化,對消息的依次處理。

一般中間件模式都約定有個 use 方法來註冊中間件,Koa2 也是如此。千言萬語不及一行代碼,這裏寫一個簡單的中間件:

const koa = require('koa')
const app = new koa()

// 沒錯,這就是中間件
app.use((ctx, next) => {      
    console.log('in 中間件1')
})

app.listen(10001)
// in 中間件1

Koa2 中的中間件有多種類型:

1. 應用級中間件;

2. 路由級中間件;

3. 錯誤處理中間件;

4. 第三方中間件;

除了使用第三方中間件比如 koa-router、koa-bodyparser、koa-static、koa-logger 等提供一些通用的路由、序列化、反序列化、日誌記錄等功能外,我們還可以編寫自己的應用級中間件,來完成業務相關的邏輯。

1. request和 response 的解析和處理;

2. 生成訪問日誌;

3. 管理 session、cookie 等;

4. 提供網絡安全防護;

2.2. 洋蔥模型

在使用多箇中間件時,引用一張著名的洋蔥模型圖:

圖片描述

 

正如上面的洋蔥圖所示,請求在進入業務邏輯時,會依次經過一系列中間件,對數據進行有序處理,業務邏輯之後,又像棧的先入後出一樣,倒序經過之前的中間件。洋蔥模型允許當應用執行完主要邏輯之後進行一些後處理,再將響應返回給用戶。

使用如下:

const Koa = require('koa')
const app = new Koa()

// 中間件1
app.use(async (ctx, next) => {
    console.log('in 中間件1')
    await next()
    console.log('out 中間件1')
})

// 中間件2
app.use(async (ctx, next) => {
    console.log('in 中間件2')
    await next()
    console.log('out 中間件2')
})

// response
app.use(async ctx => { ctx.body = 'Hello World' })

app.listen(10001)
console.log('app started at port http://localhost:10001')

// in  中間件1
// in  中間件2
// out 中間件2
// out 中間件1

我們可以引入 setTimeout 來模擬異步請求的過程:

const Koa = require('koa')
const app = new Koa()

// 中間件1
app.use(async (ctx, next) => {
    console.log('in 中間件1')
    await next()
    console.log('out 中間件1')
})

// 中間件2
app.use(async (ctx, next) => {
    console.log('in 中間件2')
    await new Promise((resolve, reject) => {
          ctx.zjj_start2 = Date.now()
          setTimeout(() => resolve(), 1000 + Math.random() * 1000)
      }
    )
    await next()
    const duration = Date.now() - ctx.zjj_start2
    console.log('out 中間件2 耗時:' + duration + 'ms')
})

// 中間件3
app.use(async (ctx, next) => {
    console.log('in 中間件3')
    await new Promise((resolve, reject) => {
          ctx.zjj_start3 = Date.now()
          setTimeout(() => resolve(), 1000 + Math.random() * 1000)
      }
    )
    await next()
    const duration = Date.now() - ctx.zjj_start3
    console.log('out 中間件3 耗時:' + duration + 'ms')
})

// response
app.use(async ctx => {
    console.log(' ... 業務邏輯處理過程 ... ')
})

app.listen(10001)
console.log('app started at port http://localhost:10001')

效果如下:

圖片描述

 

在使用多箇中間件時,特別是存在異步的場景,一般要 await來調用 next來保證在異步場景中,中間件仍按照洋蔥模型的順序來執行,因此別忘了 next 也要通過 await 調用。

更多關於Koa的使用,可以瀏覽 Koa與常用中間件的使用 這篇文章

參考文檔:

1. Koa中文文檔

2. Koa 框架教程 - 阮一峯

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章