一杯茶的時間搞清楚中間件概念
一、是什麼
中間件(Middleware)是介於應用系統和系統軟件之間的一類軟件,它使用系統軟件所提供的基礎服務(功能),銜接網絡上應用系統的各個部分或不同的應用,能夠達到資源共享、功能共享的目的
在NodeJS
中,中間件主要是指封裝http
請求細節處理的方法
例如在express
、koa
等web
框架中,中間件的本質爲一個回調函數,參數包含請求對象、響應對象和執行下一個中間件的函數
在這些中間件函數中,我們可以執行業務邏輯代碼,修改請求和響應對象、返回響應數據等操作
二、封裝
koa
是基於NodeJS
當前比較流行的web
框架,本身支持的功能並不多,功能都可以通過中間件拓展實現。通過添加不同的中間件,實現不同的需求,從而構建一個 Koa
應用
Koa
中間件採用的是洋蔥圈模型,每次執行下一個中間件傳入兩個參數:
-
ctx :封裝了 request 和 response 的變量
-
next :進入下一個要執行的中間件的函數
下面就針對koa
進行中間件的封裝:
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`);
});
});
下面則通過中間件封裝http
請求過程中幾個常用的功能:
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)
}
}
日誌模塊
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')
}
Koa
存在很多第三方的中間件,如koa-bodyparser
、koa-static
等
下面再來看看它們的大體的簡單實現:
koa-bodyparser
koa-bodyparser
中間件是將我們的 post
請求和表單提交的查詢字符串轉換成對象,並掛在 ctx.request.body
上,方便我們在其他中間件或接口處取值
// 文件:my-koa-bodyparser.js
const querystring = require("querystring");
module.exports = function bodyParser() {
return async (ctx, next) => {
await new Promise((resolve, reject) => {
// 存儲數據的數組
let dataArr = [];
// 接收數據
ctx.req.on("data", data => dataArr.push(data));
// 整合數據並使用 Promise 成功
ctx.req.on("end", () => {
// 獲取請求數據的類型 json 或表單
let contentType = ctx.get("Content-Type");
// 獲取數據 Buffer 格式
let data = Buffer.concat(dataArr).toString();
if (contentType === "application/x-www-form-urlencoded") {
// 如果是表單提交,則將查詢字符串轉換成對象賦值給 ctx.request.body
ctx.request.body = querystring.parse(data);
} else if (contentType === "applaction/json") {
// 如果是 json,則將字符串格式的對象轉換成對象賦值給 ctx.request.body
ctx.request.body = JSON.parse(data);
}
// 執行成功的回調
resolve();
});
});
// 繼續向下執行
await next();
};
};
koa-static
koa-static
中間件的作用是在服務器接到請求時,幫我們處理靜態文件
const fs = require("fs");
const path = require("path");
const mime = require("mime");
const { promisify } = require("util");
// 將 stat 和 access 轉換成 Promise
const stat = promisify(fs.stat);
const access = promisify(fs.access)
module.exports = function (dir) {
return async (ctx, next) => {
// 將訪問的路由處理成絕對路徑,這裏要使用 join 因爲有可能是 /
let realPath = path.join(dir, ctx.path);
try {
// 獲取 stat 對象
let statObj = await stat(realPath);
// 如果是文件,則設置文件類型並直接響應內容,否則當作文件夾尋找 index.html
if (statObj.isFile()) {
ctx.set("Content-Type", `${mime.getType()};charset=utf8`);
ctx.body = fs.createReadStream(realPath);
} else {
let filename = path.join(realPath, "index.html");
// 如果不存在該文件則執行 catch 中的 next 交給其他中間件處理
await access(filename);
// 存在設置文件類型並響應內容
ctx.set("Content-Type", "text/html;charset=utf8");
ctx.body = fs.createReadStream(filename);
}
} catch (e) {
await next();
}
}
}
三、總結
在實現中間件時候,單箇中間件應該足夠簡單,職責單一,中間件的代碼編寫應該高效,必要的時候通過緩存重複獲取數據
koa
本身比較簡潔,但是通過中間件的機制能夠實現各種所需要的功能,使得web
應用具備良好的可拓展性和組合性
通過將公共邏輯的處理編寫在中間件中,可以不用在每一個接口回調中做相同的代碼編寫,減少了冗雜代碼,過程就如裝飾者模式
參考文獻
-
https://segmentfault.com/a/1190000017897279
-
https://www.jianshu.com/p/81b6ebc0dd85
-
https://baike.baidu.com/item/%E4%B8%AD%E9%97%B4%E4%BB%B6
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Qin2kLvELYfn9aY2tYs-0w