Koa 系列 — 如何編寫屬於本身的 Koa 中間件

Koa 是一個由 Express 原班人馬打造的新的 web 框架,Koa 自己並無捆綁任何中間件,只提供了應用(Application)、上下文(Context)、請求(Request)、響應(Response)四個模塊。本來 Express 中的路由(Router)模塊已經被移除,改成經過中間件的方式實現。相比較 Express,Koa 能讓使用者更大程度上構建個性化的應用。

1. 中間件簡介

Koa 是一箇中間件框架,自己沒有捆綁任何中間件。自己支持的功能並很少,功能均可以經過中間件拓展實現。經過添加不一樣的中間件,實現不一樣的需求,從而構建一個 Koa 應用。javascript

Koa 的中間件就是函數,能夠是 async 函數,或是普通函數,如下是官網的示例:前端

// async 函數
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

// 普通函數
app.use((ctx, next) => {
  const start = Date.now();
  return next().then(() => {
    const ms = Date.now() - start;
    console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
  });
});

中間件能夠經過官方維護的倉庫查找獲取,也能夠根據需求編寫屬於本身的中間件。java

2. 中間件原理

2.1 示例

下面是一個的 Koa 應用,簡單演示了中間件的執行順序:git

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

// 最外層的中間件
app.use(async (ctx, next) => {
    await console.log(`第 1 個執行`);
    await next();
    await console.log(`第 8 個執行`);
});

// 第二層中間件
app.use(async (ctx, next) => {
    await console.log(`第 2 個執行`);
    await console.log(`第 3 個執行`);
    await next();
    await console.log(`第 6 個執行`);
    await console.log(`第 7 個執行`);
});

// 最裏層的中間件
app.use(async (ctx, next) => {
    await console.log(`第 4 個執行`);
    ctx.body = "Hello world.";
    await console.log(`第 5 個執行`);
});

app.listen(3000, () => {
    console.log(`Server port is 3000.`);
})

2.2 原理

從上面的示例中能夠看出,中間件的執行順序並非從頭至尾,而是相似於前端的事件流。事件流是先進行事件捕獲,到達目標,而後進行事件冒泡。中間件的實現過程也是同樣的,先從最外面的中間件開始執行,next() 後進入下一個中間件,一路執行到最裏面的中間件,而後再從最裏面的中間件開始往外執行。github

Koa 中間件採用的是洋蔥圈模型,每次執行下一個中間件傳入兩個參數 ctx 和 next,參數 ctx 是由 koa 傳入的封裝了 request 和 response 的變量,能夠經過它訪問 request 和 response,next 就是進入下一個要執行的中間件。web

koa-middleware

3. 編寫屬於本身的中間件

3.1 token 驗證的 middleware

先後端分離開發,咱們常採用 JWT 來進行身份驗證,其中 token 通常放在 HTTP 請求中的 Header Authorization 字段中,每次請求後端都要進行校驗,如 Java 的 Spring 框架能夠在過濾器中對 token 進行統一驗證,而 Koa 則經過編寫中間件來實現 token 驗證。後端

// token.js
// token 中間件
module.exports = (options) => async (ctx, next) {
  try {
    // 獲取 token
    const token = ctx.header.authorization
    if (token) {
      try {
          // verify 函數驗證 token,並獲取用戶相關信息
          await verify(token)
      } catch (err) {
        console.log(err)
      }
    }
    // 進入下一個中間件
    await next()
  } catch (err) {
    console.log(err)
  }
}
// app.js
// 引入 token 中間件
const Koa = require('Koa');
const app = new Koa();
const token = require('./token')

app.use(token())

app.listen(3000, () => {
    console.log(`Server port is 3000.`);
})

3.2 log 的 middleware

日誌模塊也是線上不可缺乏的一部分,完善的日誌系統能夠幫助咱們迅速地排查出線上的問題。經過 Koa 中間件,咱們能夠實現屬於本身的日誌模塊app

// logger.js
// logger 中間件
const fs = require('fs')
module.exports = (options) => async (ctx, next) => {
  const startTime = Date.now()
  const requestTime = new Date()
  await next()
  const ms = Date.now() - startTime;
  let logout = `${ctx.request.ip} -- ${requestTime} -- ${ctx.method} -- ${ctx.url} -- ${ms}ms`;
  // 輸出日誌文件
  fs.appendFileSync('./log.txt', logout + '\n')
}
// app.js
// 引入 logger 中間件
const Koa = require('Koa');
const app = new Koa();
const logger = require('./logger')

app.use(logger())

app.listen(3000, () => {
    console.log(`Server port is 3000.`);
})

能夠結合 log4js 等包來記錄更詳細的日誌框架

4. 總結

至此,咱們已經瞭解中間件的原理,以及如何實現一個本身的中間件。前後端分離

中間件的代碼一般比較簡單,咱們能夠經過閱讀官方維護的倉庫中優秀中間件的源碼,來加深對中間件的理解和運用。

  • 本文首發於公衆號,更多內容歡迎關注個人公衆號:阿誇漫談
相關文章
相關標籤/搜索