面試官:說說對中間件概念的理解,如何封裝 node 中間件?
本文轉(zhuǎn)載自微信公眾號「JS每日一題」,作者灰灰。轉(zhuǎn)載本文請聯(lián)系JS每日一題公眾號。
一、是什么
中間件(Middleware)是介于應(yīng)用系統(tǒng)和系統(tǒng)軟件之間的一類軟件,它使用系統(tǒng)軟件所提供的基礎(chǔ)服務(wù)(功能),銜接網(wǎng)絡(luò)上應(yīng)用系統(tǒng)的各個部分或不同的應(yīng)用,能夠達到資源共享、功能共享的目的
在NodeJS中,中間件主要是指封裝http請求細節(jié)處理的方法
例如在express、koa等web框架中,中間件的本質(zhì)為一個回調(diào)函數(shù),參數(shù)包含請求對象、響應(yīng)對象和執(zhí)行下一個中間件的函數(shù)
在這些中間件函數(shù)中,我們可以執(zhí)行業(yè)務(wù)邏輯代碼,修改請求和響應(yīng)對象、返回響應(yīng)數(shù)據(jù)等操作
二、封裝
koa是基于NodeJS當前比較流行的web框架,本身支持的功能并不多,功能都可以通過中間件拓展實現(xiàn)。通過添加不同的中間件,實現(xiàn)不同的需求,從而構(gòu)建一個 Koa 應(yīng)用
Koa 中間件采用的是洋蔥圈模型,每次執(zhí)行下一個中間件傳入兩個參數(shù):
ctx :封裝了request 和 response 的變量
next :進入下一個要執(zhí)行的中間件的函數(shù)
下面就針對koa進行中間件的封裝:
Koa的中間件就是函數(shù),可以是async 函數(shù),或是普通函數(shù)
- // async 函數(shù)
- app.use(async (ctx, next) => {
- const start = Date.now();
- await next();
- const ms = Date.now() - start;
- console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
- });
- // 普通函數(shù)
- 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 函數(shù)驗證 token,并獲取用戶相關(guān)信息
- 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等
下面再來看看它們的大體的簡單實現(xiàn):
koa-bodyparser
koa-bodyparser 中間件是將我們的 post 請求和表單提交的查詢字符串轉(zhuǎn)換成對象,并掛在 ctx.request.body 上,方便我們在其他中間件或接口處取值
- // 文件:my-koa-bodyparser.js
- const querystring = require("querystring");
- module.exports = function bodyParser() {
- return async (ctx, next) => {
- await new Promise((resolve, reject) => {
- // 存儲數(shù)據(jù)的數(shù)組
- let dataArr = [];
- // 接收數(shù)據(jù)
- ctx.req.on("data", data => dataArr.push(data));
- // 整合數(shù)據(jù)并使用 Promise 成功
- ctx.req.on("end", () => {
- // 獲取請求數(shù)據(jù)的類型 json 或表單
- let contentType = ctx.get("Content-Type");
- // 獲取數(shù)據(jù) Buffer 格式
- let data = Buffer.concat(dataArr).toString();
- if (contentType === "application/x-www-form-urlencoded") {
- // 如果是表單提交,則將查詢字符串轉(zhuǎn)換成對象賦值給 ctx.request.body
- ctx.request.body = querystring.parse(data);
- } else if (contentType === "applaction/json") {
- // 如果是 json,則將字符串格式的對象轉(zhuǎn)換成對象賦值給 ctx.request.body
- ctx.request.body = JSON.parse(data);
- }
- // 執(zhí)行成功的回調(diào)
- resolve();
- });
- });
- // 繼續(xù)向下執(zhí)行
- await next();
- };
- };
koa-static
koa-static 中間件的作用是在服務(wù)器接到請求時,幫我們處理靜態(tài)文件
- const fs = require("fs");
- const path = require("path");
- const mime = require("mime");
- const { promisify } = require("util");
- // 將 stat 和 access 轉(zhuǎn)換成 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);
- // 如果是文件,則設(shè)置文件類型并直接響應(yīng)內(nèi)容,否則當作文件夾尋找 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");
- // 如果不存在該文件則執(zhí)行 catch 中的 next 交給其他中間件處理
- await access(filename);
- // 存在設(shè)置文件類型并響應(yīng)內(nèi)容
- ctx.set("Content-Type", "text/html;charset=utf8");
- ctx.body = fs.createReadStream(filename);
- }
- } catch (e) {
- await next();
- }
- }
- }
三、總結(jié)
在實現(xiàn)中間件時候,單個中間件應(yīng)該足夠簡單,職責(zé)單一,中間件的代碼編寫應(yīng)該高效,必要的時候通過緩存重復(fù)獲取數(shù)據(jù)
koa本身比較簡潔,但是通過中間件的機制能夠?qū)崿F(xiàn)各種所需要的功能,使得web應(yīng)用具備良好的可拓展性和組合性
通過將公共邏輯的處理編寫在中間件中,可以不用在每一個接口回調(diào)中做相同的代碼編寫,減少了冗雜代碼,過程就如裝飾者模式
參考文獻
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