Koa 中間件詳解

中間件是 Koa 中的核心概念赛不,必須完全掌握碑宴。

一裆甩、什么是中間件

先來看下面這段代碼:

app.use(async (ctx, next) => {
  await next();
  ctx.response.type = "text/html";
  ctx.response.body = "<h1>Hello World</h1>";
});

上述代碼的作用是:每收到一個 HTTP 請求之斯,Koa 應(yīng)用服務(wù)端都會調(diào)用 async 箭頭函數(shù)進(jìn)行響應(yīng)光涂,然后返回響應(yīng)內(nèi)容給客戶端庞萍。

在上述代碼中,app 是一個 Koa 實例對象忘闻,它用 use 方法注冊了一個函數(shù)钝计,這個函數(shù)就是中間件。所以齐佳,我們可以下定義私恬,中間件就是一個函數(shù),而且 Koa 應(yīng)用自動給這個函數(shù)傳入了兩個默認(rèn)參數(shù)炼吴。那么本鸣,Koa 應(yīng)用程序其實就是一個包含一組中間件函數(shù)的對象。

中間件參數(shù)

  • 第一個參數(shù):ctx

這是一個上下文對象硅蹦,用于處理 HTTP 請求和響應(yīng)荣德。

  • 第二個參數(shù):next

這是一個函數(shù),執(zhí)行后返回一個 Promise 對象提针。它的作用是將程序的執(zhí)行權(quán)交給下一個中間件命爬,等下一個以及后面的中間件全部執(zhí)行結(jié)束后,再回到當(dāng)前中間件繼續(xù)執(zhí)行辐脖。

二饲宛、中間件的最佳實踐

2.1 命名中間件

我們可以給中間件進(jìn)行命名,這樣在 Debug 時就會顯示函數(shù)名嗜价,有助于定位調(diào)試艇抠。

// 定義中間件,命名為 logger
async function logger(ctx, next) {
    // do something
  };
}

// 注冊中間件
app.use(logger)

2.2 中間件選項

在創(chuàng)建公共中間件時久锥,可以將中間件包裝在另外一個函數(shù)中家淤,這有利于功能擴(kuò)展。

// 定義 logger 函數(shù)瑟由,用于擴(kuò)展功能
function logger(format) {
  format = format || ":method :url";

  // 返回一個中間件函數(shù)
  return async function (ctx, next) {
    const str = format
      .replace(":method", ctx.method)
      .replace(":url", ctx.url);

    console.log(str);

    await next();
  };
}

// 注冊中間件
app.use(logger());
app.use(logger(":method"));
app.use(logger(":method :url"));

問題:運行上述代碼絮重,控制臺會輸出哪些內(nèi)容?

三、執(zhí)行順序

下面通過具體代碼來演示中間件的執(zhí)行順序青伤,代碼如下:

const Koa = require("koa");
const app = new Koa();

// the first middleware
app.use(async function (ctx, next) {
  console.log(">> one");  // 1
  await next();           // 2
  console.log("<< one");  // 3
});

// the second middleware
app.use(async function (ctx, next) {
  console.log(">> two");  // 4
  ctx.body = "two";       // 5
  await next();           // 6
  console.log("<< two");  // 7
});

// the third middleware
app.use(async function (ctx, next) {
  console.log(">> three");// 8
  await next();           // 9
  console.log("<< three");// 10
});

app.listen(3000, () => {
  console.log("[demo] server is running at http://localhost:3000");
});

用 Postman 訪問 http://localhost:3000督怜,控制臺輸出結(jié)果如下:

>> one
>> two
>> three
<< three
<< two
<< one

通過輸入結(jié)果發(fā)現(xiàn),三個中間件函數(shù)體的具體執(zhí)行順序如下:
1 ?? 2 ?? 4 ?? 5 ?? 6 ?? 8 ?? 9 ?? 10 ?? 7 ?? 3
問題:將第二個中間件中的 next 那以上代碼注釋后再次運行狠角,控制臺輸出結(jié)果是什么号杠?
解析:執(zhí)行順序為 1 ?? 2 ?? 4 ?? 5 ?? 7 ?? 3,第三個中間件沒有執(zhí)行到

提示
使用瀏覽器訪問丰歌,會發(fā)現(xiàn)控制臺輸出了兩次結(jié)果姨蟋,這是因為訪問 http://localhost:3000 后,瀏覽器還自動請求了 http://localhost:3000/favicon.ico

通過上述代碼演示立帖,我們知道了中間件的處理流程大致分為三部分:

  1. 前期處理
  2. 交給其他中間件處理并等待(這一步就是調(diào)用了 next 方法)
  3. 后期處理

Koa 應(yīng)用程序由一組中間件組成眼溶,所以整個的處理過程就類似于先進(jìn)后出的堆棧結(jié)構(gòu),可以用如下這張洋蔥切面圖形象地來解釋多個不同功能的中間件的執(zhí)行順序厘惦。

洋蔥模型示意圖

四偷仿、中間件組合

有時候需要將多個中間件組合起來作為一個中間件,以便于重用和導(dǎo)出宵蕉,這就需要使用 koa-compose

提示
koa 依賴 koa-compose酝静,安裝 koa 時已經(jīng)自動下載到 node_modules 中,無須單獨安裝

下面看如下代碼:

const compose = require("koa-compose");

// random 中間件
async function random(ctx, next) {
  if ("/random" == ctx.path) {
    ctx.body = Math.floor(Math.random() * 10);
  } else {
    console.log("random middleware");
    await next();
  }
}

// backwards 中間件
async function backwards(ctx, next) {
  if ("/backwards" == ctx.path) {
    ctx.body = "sdrawkcab";
  } else {
    console.log("backwards middleware");
    await next();
  }
}

// pi 中間件
async function pi(ctx, next) {
  if ("/pi" == ctx.path) {
    ctx.body = String(Math.PI);
  } else {
    console.log("backwards middleware");
    await next();
  }
}

// 將三個中間件組合成一個中間件
const all = compose([random, backwards, pi]);

// 注冊中間件
app.use(all);
  1. 用瀏覽器訪問 http://localhost:3000羡玛,控制臺輸出結(jié)果如下:
random middleware
backwards middleware
pi middleware
  1. 用瀏覽器訪問 http://localhost:3000/backwards别智,控制臺輸出結(jié)果如下:
random middleware
  1. 當(dāng)變換組合順序,比如 compose([random, pi, backwards])稼稿,然后再次訪問上述兩個 URL 地址薄榛,控制臺輸出結(jié)果是什么们颜?

五锌介、實戰(zhàn)演練

在實際應(yīng)用中咒锻,經(jīng)常需要記錄服務(wù)器的響應(yīng)時間也物,即服務(wù)器從接收到 HTTP 請求到返回響應(yīng)內(nèi)容給客戶端所耗的時長。下面使用 Koa 的中間件機(jī)制實現(xiàn)這一功能舔清,具體代碼如下:

const Koa = require("koa");
const app = new Koa();

// logger
app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.get("X-Response-Time");
  console.log(`${ctx.method} ${ctx.url} - 響應(yīng)時間: ${rt}`);
});

// x-response-time
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.response.set("X-Response-Time", `${ms}ms`);
});

// response
app.use(ctx => {
  ctx.body = "<h1>Hello World</h1>";
});

app.listen(3000, () => {
  console.log("[demo] server is running at http://localhost:3000")
})

按照業(yè)務(wù)需求炫彩,上述代碼中使用了三個中間件对途,各自的都有不同的功能:

  • logger 中間件:用于控制臺打印請求的響應(yīng)時間
  • x-response-time 中間件:用于設(shè)置響應(yīng)頭信息
  • response 中間件:用于返回響應(yīng)內(nèi)容給客戶端

用瀏覽器訪問 http://loacalhost:3000/user改执,控制臺輸出結(jié)果如下:

GET /user - 響應(yīng)時間: 0ms

參考鏈接

  1. Koa 官網(wǎng)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末啸蜜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辈挂,更是在濱河造成了極大的恐慌衬横,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件终蒂,死亡現(xiàn)場離奇詭異蜂林,居然都是意外死亡遥诉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門噪叙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來突那,“玉大人,你說我怎么就攤上這事构眯。” “怎么了早龟?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵惫霸,是天一觀的道長。 經(jīng)常有香客問我葱弟,道長壹店,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任芝加,我火速辦了婚禮硅卢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘藏杖。我一直安慰自己将塑,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布蝌麸。 她就那樣靜靜地躺著点寥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪来吩。 梳的紋絲不亂的頭發(fā)上敢辩,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天,我揣著相機(jī)與錄音弟疆,去河邊找鬼戚长。 笑死,一個胖子當(dāng)著我的面吹牛怠苔,可吹牛的內(nèi)容都是我干的同廉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嘀略,長吁一口氣:“原來是場噩夢啊……” “哼恤溶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帜羊,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤咒程,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后讼育,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帐姻,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡稠集,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了饥瓷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剥纷。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖呢铆,靈堂內(nèi)的尸體忽然破棺而出晦鞋,到底是詐尸還是另有隱情,我是刑警寧澤棺克,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布悠垛,位于F島的核電站,受9級特大地震影響娜谊,放射性物質(zhì)發(fā)生泄漏确买。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一纱皆、第九天 我趴在偏房一處隱蔽的房頂上張望湾趾。 院中可真熱鬧,春花似錦派草、人聲如沸搀缠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胡嘿。三九已至,卻和暖如春钳踊,著一層夾襖步出監(jiān)牢的瞬間衷敌,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工拓瞪, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留缴罗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓祭埂,卻偏偏與公主長得像面氓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蛆橡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,969評論 2 355

推薦閱讀更多精彩內(nèi)容