浅谈Node.js 中间件模式

中间件在 Node.js 中被广泛使用,它泛指一种特定的设计模式、一系列的处理单元、过滤器和处理程序,以函数的形式存在,连接在一起,形成一个异步队列,来完成对任何数据的预处理和后处理。

它的优点在于 灵活性 :使用中间件我们用极少的操作就能得到一个插件,用最简单的方法就能将新的过滤器和处理程序扩展到现有的系统上。

常规中间件模式

中间件模式中,最基础的组成部分就是 中间件管理器 ,我们可以用它来组织和执行中间件的函数,如图所示:

要实现中间件模式,最重要的实现细节是:

  1. 可以通过调用use()函数注册新的中间件,通常,新的中间件只能被添加到高压包带的末端,但不是严格要求这么做;
  2. 当接收到需要处理的新数据时,注册的中间件在意不执行流程中被依次调用。每个中间件都接受上一个中间件的执行结果作为输入值;
  3. 每个中间件都可以停止数据的进一步处理,只需要简单地不调用它的毁掉函数或者将错误传递给回调函数。当发生错误时,通常会触发执行另一个专门处理错误的中间件。

至于怎么处理传递数据,目前没有严格的规则,一般有几种方式

  1. 通过添加属性方法来增强;
  2. 使用某种处理的结果来替换 data;
  3. 保证原始要处理的数据不变,永远返回新的副本作为处理的结果。

而具体的处理方式取决于 中间件管理器 的实现方式以及中间件本身要完成的任务类型。

举一个来自于 《Node.js 设计模式 第二版》 的一个为消息传递库实现 中间件管理器 的例子:

{ 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,err => {
if(err) {
return console.log('There was an error: ' + err.message);
}
iterator.call(this,++index);
});
}
iterator.call(this,0);
}
}

接下来只需要创建中间件,分别在 inbound 和 outbound 中写入中间件函数,然后执行完毕调用 next() 就好了。比如:

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 所推广的 中间件 概念就与之类似,一个 Express 中间件一般是这样的:

Koa2 中使用的中间件

前面展示的中间件模型使用回调函数实现的,但是现在有一个比较时髦的 Node.js 框架 Koa2 的中间件实现方式与之前描述的有一些不太相同。 Koa2 中的中间件模式移除了一开始使用 ES2015 中的生成器实现的方法,兼容了回调函数、 convert 后的生成器以及 async 和 await 。

在 Koa2 官方文档中给出了一个关于中间件的 洋葱模型 ,如下图所示:

从图中我们可以看到,先进入 inbound 的中间件函数在 outbound 中被放到了后面执行,那么究竟是为什么呢?带着这个问题我们去读一下 Koa2 的源码。

在 koa/lib/applications.js 中,先看构造函数,其它的都可以不管,关键就是 this.middleware ,它是一个 inbound 队列:

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() 来把中间件放入队列中:

接着我们看框架对端口监听进行了一个简单的封装:

中间件的管理关键就在于 this.callback() ,看一下这个方法

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 方法实际上是 Koa2 的一个核心模块 koa-compose (nofollow" target="_blank" href="https://github.com/koajs/compose">https://github.com/koajs/compose),在这个模块中封装了中间件执行的方法

/**

  • @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 通过递归对中间件队列进行了 反序遍历 ,生成了一个 Promise 链,接下来,只需要调用 Promise 就可以执行中间件函数了:

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

从源码中可以发现, next() 中返回的是一个 Promise ,所以通用的中间件写法是:

{ const start = new Date(); return next().then(() => { const ms = new Date() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); }); });

当然如果要用 async 和 await 也行:

{ const start = new Date(); await next(); const ms = new Date() - start; console.log(`${ctx.method} ${ctx.url} - ${ms}ms`); });

由于还有很多 Koa1 的项目中间件是基于生成器的,需要使用 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);
}));

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。

相关文章

现在的js代码都是这种高级点的方式语法 , 就是ts语法 ,要使用ts语法那就要先安装一下 先安装nodejs最...
nvm是node版本管理工具 为了解决node各种版本存在不兼容现象 nvm是让你在同一台机器上安装和切换不同版...
1、安装nodejs 2、在项目文件夹目录下创建一个js文件,命名server.js(自定义名称),内容如下 3、打开命令...
1.连接路径:path.join([path1][, path2][, ...]) path.join()方法可以连接任意多个路径字符串。要连接...
简介 1.aes加密简单来说,在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个...
定义 Object.keys 定义:返回一个对象可枚举属性的字符串数组; Object.getOwnPropertyNames 定义:返回...