日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

Koa 中间件的执行

發(fā)布時(shí)間:2024/7/5 223 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Koa 中间件的执行 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Node.js 中請(qǐng)求的處理

討論 Koa 中間件前,先看原生 Node.js 中是如何創(chuàng)建 server 和處理請(qǐng)求的。

node_server.js

const http = require("http"); const PORT = 3000;const server = http.createServer((req, res) => {res.end("hello world!"); });server.listen(PORT); console.log(`server started at http://localhost:${PORT}`);

Koa 中請(qǐng)求的處理

Koa 也是通過(guò)上面的 http.createServer 創(chuàng)建服務(wù)器處理請(qǐng)求的返回 res。 但在 Koa 的封裝體系下,其提供了十分好用的中間件系統(tǒng),可對(duì)請(qǐng)求 req 及返回 res 進(jìn)行便捷地處理。

koa/lib/application.js#L64

listen(...args) {debug('listen'); + const server = http.createServer(this.callback());return server.listen(...args);}

Koa 中的 hello world:

server.js

const Koa = require("koa"); const app = new Koa();app.use(async ctx => {ctx.body = "Hello World"; });app.listen(3000);

Koa 中,涉及到對(duì)請(qǐng)求返回處理都是通過(guò)中間件完成的,像上面為樣,返回頁(yè)面一個(gè) Hello World 文本,也是調(diào)用 app.use 向 Application 對(duì)象注冊(cè)了個(gè)中間件來(lái)完成。

Koa 中間件編寫及使用

Koa 中中間件即一個(gè)處理請(qǐng)求的方法,通過(guò)調(diào)用 app.use(fn) 后,中間件 fn 被保存到了內(nèi)部一個(gè)中間件數(shù)組中。

koa/lib/application.js#L105

use(fn) {if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');if (isGeneratorFunction(fn)) {deprecate('Support for generators will be removed in v3. ' +'See the documentation for examples of how to convert old middleware ' +'https://github.com/koajs/koa/blob/master/docs/migration.md');fn = convert(fn);}debug('use %s', fn._name || fn.name || '-');this.middleware.push(fn);return this;}

通過(guò)上面的代碼可看到,注冊(cè)的中間件被壓入 Application 對(duì)象的 this.middleware 數(shù)組。這里有對(duì)傳入的方法進(jìn)行判斷,區(qū)分是否為生成器([generator])方法,因?yàn)檩^早版本的 Koa 其中間件是通過(guò)生成器來(lái)實(shí)現(xiàn)的,后面有 async/await 語(yǔ)法后轉(zhuǎn)向了后者,所以更推薦使用后者,因此這里有廢棄生成器方式的提示。

因?yàn)橹虚g件中需要進(jìn)行的操作是不可控的,完全有可能涉及異步操作,比如從遠(yuǎn)端獲取數(shù)據(jù)或從數(shù)據(jù)庫(kù)查詢數(shù)據(jù)后返回到 ctx.body,所以理論上中間件必需是異步函數(shù)。

比如實(shí)現(xiàn)計(jì)算一個(gè)請(qǐng)求耗時(shí)的中間件,以下分別是通過(guò)普通函數(shù)配合 Promise 以及使用 async/await 方式實(shí)現(xiàn)的版本:

來(lái)自官方 README 中使用 Promise 實(shí)現(xiàn)中間件的示例代碼

// Middleware normally takes two parameters (ctx, next), ctx is the context for one request, // next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.app.use((ctx, next) => {const start = Date.now();return next().then(() => {const ms = Date.now() - start;console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);}); });

來(lái)自官方 README 中使用 async/await 實(shí)現(xiàn)中間件的示例代碼

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

可以看到,一個(gè)中間件其簽名是 (ctx,next)=>Promise,其中 ctx 為請(qǐng)求上下文對(duì)象,而 next 是這樣一個(gè)函數(shù),調(diào)用后將執(zhí)行流程轉(zhuǎn)入下一個(gè)中間件,如果當(dāng)前中間件中沒有調(diào)用 next,整個(gè)中間件的執(zhí)行流程則會(huì)在這里終止,后續(xù)中間件不會(huì)得到執(zhí)行。以下是一個(gè)測(cè)試。

server.js

app.use(async (ctx, next) => {console.log(1);next(); }); app.use(async (ctx, next) => {console.log(2); }); app.use(async (ctx, next) => {console.log(3);ctx.body = "Hello, world!"; });

執(zhí)行后控制臺(tái)輸出:

$ node server.js 1 2

訪問(wèn)頁(yè)面也不會(huì)看到 Hello, world! 因?yàn)樵O(shè)置響應(yīng)的代碼 ctx.body = "Hello, world!"; 所在的中間件沒有被執(zhí)行。

compose

下面來(lái)看當(dāng)多次調(diào)用 app.use 注冊(cè)中間件后,這些中間件是如何被順次執(zhí)行的。

中間件的執(zhí)行是跟隨一次請(qǐng)求的。當(dāng)一個(gè)請(qǐng)求來(lái)到后臺(tái),中間件被順次執(zhí)行,在各中間件中對(duì)請(qǐng)求 request 及 resposne 進(jìn)行各種處理。

所以從 Koa 中處理請(qǐng)求的地方出發(fā),找到中間件執(zhí)行的源頭。

通過(guò)查看 lib/application.js 中相關(guān)代碼:

lib/application.js#L127

callback() { + const fn = compose(this.middleware);if (!this.listenerCount('error')) this.on('error', this.onerror);const handleRequest = (req, res) => {const ctx = this.createContext(req, res);return this.handleRequest(ctx, fn);};return handleRequest;}

可定位到存儲(chǔ)在 this.middleware 中的中間件數(shù)組會(huì)傳遞給 compose 方法來(lái)處理,處理后得到一個(gè)函數(shù) fn,即這個(gè) compose 方法處理后,將一組中間件函數(shù)處理成了一個(gè)函數(shù),最終在 handleRequest 處被調(diào)用,開啟了中間件的執(zhí)行流程。

lib/application.js#L151

handleRequest(ctx, fnMiddleware) {const res = ctx.res;res.statusCode = 404;const onerror = err => ctx.onerror(err);const handleResponse = () => respond(ctx);onFinished(res, onerror); + return fnMiddleware(ctx).then(handleResponse).catch(onerror);}

即 compose 的簽名長(zhǎng)這樣:compose([a, b, c, ...]),它來(lái)自另一個(gè)單獨(dú)的倉(cāng)庫(kù) koajs/compose,其代碼也不復(fù)雜:

koajs/compose/index.js

function compose(middleware) {if (!Array.isArray(middleware))throw new TypeError("Middleware stack must be an array!");for (const fn of middleware) {if (typeof fn !== "function")throw new TypeError("Middleware must be composed of functions!");}/** * @param {Object} context * @return {Promise} * @api public */return function(context, next) {// last called middleware #let index = -1;return dispatch(0);function dispatch(i) {if (i <= index)return Promise.reject(new Error("next() called multiple times"));index = i;let fn = middleware[i];if (i === middleware.length) fn = next;if (!fn) return Promise.resolve();try {return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));} catch (err) {return Promise.reject(err);}}}; }

這個(gè)方法只做了兩件事,

  • 定義了一個(gè) dispatch 方法,
  • 然后調(diào)用它 dispatch(0)

這里中間件從數(shù)組中取出并順次執(zhí)行的邏輯便在 dispatch 函數(shù)中。

整體方法體中維護(hù)了一個(gè)索引 index 其初始值為 -1,后面每調(diào)用一次 dispatch 會(huì)加 1。當(dāng)執(zhí)行 dispatch(0) 時(shí),從中間件數(shù)組 middleware 中取出第 0 個(gè)中間件并執(zhí)行,同時(shí)將 dispatch(i+1) 作為 next 傳遞到下一次執(zhí)行。

let fn = middleware[i]; return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));

所以這里就能理解,為什么中間件中必需調(diào)用 next,否則后續(xù)中間件不會(huì)執(zhí)行。

這樣一直進(jìn)行下去直到所有中間件執(zhí)行完畢,此時(shí) i === middleware.length,最后一個(gè)中間件已經(jīng)執(zhí)行完畢,next 是沒有值的,所以直接 resolve 掉結(jié)束中間件執(zhí)行流程。

if (i === middleware.length) fn = next; if (!fn) return Promise.resolve();

回到中間件被喚起的地方:

lib/application.js

fnMiddleware(ctx).then(handleResponse).catch(onerror);

中間件完成后,流程到了 handleResponse。

總結(jié)

從中間件執(zhí)行流程可知道:

  • 中間件之間存在順序的問(wèn)題,先注冊(cè)的先執(zhí)行。
  • 中間件中需要調(diào)用 next 以保證后續(xù)中間件的執(zhí)行。當(dāng)然,如果你的中間件會(huì)根據(jù)一些情況阻止掉后續(xù)中間件的執(zhí)行,那可以不調(diào)用 next,比如一個(gè)對(duì)請(qǐng)求進(jìn)行權(quán)限校驗(yàn)的中間件可以這么寫:
app.use(async (ctx, next) => {// 獲取權(quán)限數(shù)據(jù)相關(guān)的操作...if (valid) {await next();} else {ctx.throw(403, "沒有權(quán)限!");} });

相關(guān)資源

  • Koa documentation
  • Node.js Documentation - HTTP Class: http.Server
  • MDN - function*
  • koajs/compose

轉(zhuǎn)載于:https://www.cnblogs.com/Wayou/p/koa_middleware.html

總結(jié)

以上是生活随笔為你收集整理的Koa 中间件的执行的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。