您好,登錄后才能下訂單哦!
Node.js 中怎么實(shí)現(xiàn)中間件模式,針對(duì)這個(gè)問題,這篇文章詳細(xì)介紹了相對(duì)應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡(jiǎn)單易行的方法。
常規(guī)中間件模式
中間件模式中,最基礎(chǔ)的組成部分就是 中間件管理器 ,我們可以用它來組織和執(zhí)行中間件的函數(shù),如圖所示:
要實(shí)現(xiàn)中間件模式,最重要的實(shí)現(xiàn)細(xì)節(jié)是:
可以通過調(diào)用use()函數(shù)來注冊(cè)新的中間件,通常,新的中間件只能被添加到高壓包帶的末端,但不是嚴(yán)格要求這么做;
當(dāng)接收到需要處理的新數(shù)據(jù)時(shí),注冊(cè)的中間件在意不執(zhí)行流程中被依次調(diào)用。每個(gè)中間件都接受上一個(gè)中間件的執(zhí)行結(jié)果作為輸入值;
每個(gè)中間件都可以停止數(shù)據(jù)的進(jìn)一步處理,只需要簡(jiǎn)單地不調(diào)用它的毀掉函數(shù)或者將錯(cuò)誤傳遞給回調(diào)函數(shù)。當(dāng)發(fā)生錯(cuò)誤時(shí),通常會(huì)觸發(fā)執(zhí)行另一個(gè)專門處理錯(cuò)誤的中間件。
至于怎么處理傳遞數(shù)據(jù),目前沒有嚴(yán)格的規(guī)則,一般有幾種方式
通過添加屬性和方法來增強(qiáng);
使用某種處理的結(jié)果來替換 data;
保證原始要處理的數(shù)據(jù)不變,永遠(yuǎn)返回新的副本作為處理的結(jié)果。
而具體的處理方式取決于 中間件管理器 的實(shí)現(xiàn)方式以及中間件本身要完成的任務(wù)類型。
舉一個(gè)來自于 《Node.js 設(shè)計(jì)模式 第二版》 的一個(gè)為消息傳遞庫(kù)實(shí)現(xiàn) 中間件管理器 的例子:
class ZmqMiddlewareManager { constructor(socket) { this.socket = socket; // 兩個(gè)列表分別保存兩類中間件函數(shù):接受到的信息和發(fā)送的信息。 this.inboundMiddleware = []; this.outboundMiddleware = []; socket.on('message', message => { this.executeMiddleware(this.inboundMiddleware, { data: message }); }); } send(data) { const message = { data }; this.excuteMiddleware(this.outboundMiddleware, message, () => { this.socket.send(message.data); }); } use(middleware) { if(middleware.inbound) { this.inboundMiddleware.push(middleware.inbound); } if(middleware.outbound) { this.outboundMiddleware.push(middleware.outbound); } } exucuteMiddleware(middleware, arg, finish) { function iterator(index) { if(index === middleware.length) { return finish && finish(); } middleware[index].call(this, arg, err => { if(err) { return console.log('There was an error: ' + err.message); } iterator.call(this, ++index); }); } iterator.call(this, 0); } }
接下來只需要?jiǎng)?chuàng)建中間件,分別在 inbound 和 outbound 中寫入中間件函數(shù),然后執(zhí)行完畢調(diào)用 next() 就好了。比如:
const zmqm = new ZmqMiddlewareManager(); zmqm.use({ inbound: function(message, next) { console.log('input message: ', message.data); next(); }, outbound: function(message, next) { console.log('output message: ', message.data); next(); } });
Express 所推廣的 中間件 概念就與之類似,一個(gè) Express 中間件一般是這樣的:
function(req, res, next) { ... }
Koa2 中使用的中間件
前面展示的中間件模型使用回調(diào)函數(shù)實(shí)現(xiàn)的,但是現(xiàn)在有一個(gè)比較時(shí)髦的 Node.js 框架 Koa2 的中間件實(shí)現(xiàn)方式與之前描述的有一些不太相同。 Koa2 中的中間件模式移除了一開始使用 ES2015 中的生成器實(shí)現(xiàn)的方法,兼容了回調(diào)函數(shù)、 convert 后的生成器以及 async 和 await 。
在 Koa2 官方文檔中給出了一個(gè)關(guān)于中間件的 洋蔥模型 ,如下圖所示:
從圖中我們可以看到,先進(jìn)入 inbound 的中間件函數(shù)在 outbound 中被放到了后面執(zhí)行,那么究竟是為什么呢?帶著這個(gè)問題我們?nèi)プx一下 Koa2 的源碼。
在 koa/lib/applications.js 中,先看構(gòu)造函數(shù),其它的都可以不管,關(guān)鍵就是 this.middleware ,它是一個(gè) inbound 隊(duì)列:
constructor() { super(); this.proxy = false; this.middleware = []; this.subdomainOffset = 2; this.env = process.env.NODE_ENV || 'development'; this.context = Object.create(context); this.request = Object.create(request); this.response = Object.create(response); }
和上面一樣,在 Koa2 中也是用 use() 來把中間件放入隊(duì)列中:
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; }
接著我們看框架對(duì)端口監(jiān)聽進(jìn)行了一個(gè)簡(jiǎn)單的封裝:
// 封裝之前 http.createServer(app.callback()).listen(...) listen(...args) { debug('listen'); const server = http.createServer(this.callback()); return server.listen(...args); }
中間件的管理關(guān)鍵就在于 this.callback() ,看一下這個(gè)方法:
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; }
這里的 compose 方法實(shí)際上是 Koa2 的一個(gè)核心模塊 koa-compose (https://github.com/koajs/compose),在這個(gè)模塊中封裝了中間件執(zhí)行的方法:
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) } } } }
可以看到, compose 通過遞歸對(duì)中間件隊(duì)列進(jìn)行了 反序遍歷 ,生成了一個(gè) Promise 鏈,接下來,只需要調(diào)用 Promise 就可以執(zhí)行中間件函數(shù)了:
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); }
從源碼中可以發(fā)現(xiàn), next() 中返回的是一個(gè) Promise ,所以通用的中間件寫法是:
app.use((ctx, next) => { const start = new Date(); return next().then(() => { const ms = new Date() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); }); });
當(dāng)然如果要用 async 和 await 也行:
app.use((ctx, next) => { const start = new Date(); await next(); const ms = new Date() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); });
由于還有很多 Koa1 的項(xiàng)目中間件是基于生成器的,需要使用 koa-convert 來進(jìn)行平滑升級(jí):
const convert = require('koa-convert'); app.use(convert(function *(next) { const start = new Date(); yield next; const ms = new Date() - start; console.log(`${this.method} ${this.url} - ${ms}ms`); }));
關(guān)于Node.js 中怎么實(shí)現(xiàn)中間件模式問題的解答就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識(shí)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。