当前位置: 首页 > 编程笔记 >

浅谈Node.js 中间件模式

松钊
2023-03-14
本文向大家介绍浅谈Node.js 中间件模式,包括了浅谈Node.js 中间件模式的使用技巧和注意事项,需要的朋友参考一下

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

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

常规中间件模式

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

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

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

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

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

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

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

class ZmqMiddlewareManager {
 constructor(socket) {
  this.socket = socket;
  // 两个列表分别保存两类中间件函数:接受到的信息和发送的信息。
  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);
 }
}

接下来只需要创建中间件,分别在 inbound 和 outbound 中写入中间件函数,然后执行完毕调用 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 所推广的 中间件 概念就与之类似,一个 Express 中间件一般是这样的:

function(req, res, next) { ... }

Koa2 中使用的中间件

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

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

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

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

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

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;
}

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

// 封装之前 http.createServer(app.callback()).listen(...)
listen(...args) {
 debug('listen');
 const server = http.createServer(this.callback());
 return server.listen(...args);
}

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

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

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

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);
}

从源码中可以发现, next() 中返回的是一个 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`);
 });
});

当然如果要用 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 的项目中间件是基于生成器的,需要使用 koa-convert 来进行平滑升级:

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`);
}));

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • 本文向大家介绍浅谈express.js框架中间件(middleware),包括了浅谈express.js框架中间件(middleware)的使用技巧和注意事项,需要的朋友参考一下 _express.js_作为_Node.js_的老牌框架,是现有框架中最全面的。然而在学习express除了那些api之外,对于框架最重要的就是__中间件__这个概念了。如果理解了,就可以把这个框架玩活了,项目开发肯定会

  • 本文向大家介绍浅谈Node.js中的定时器,包括了浅谈Node.js中的定时器的使用技巧和注意事项,需要的朋友参考一下 Node.js中定时器的实现 上一篇博文提到,在Node中timer并不是通过新开线程来实现的,而是直接在event loop中完成。下面通过几个JavaScript的定时器示例以及Node相关源码来分析在Node中,timer功能到底是怎么实现的。 JavaScript中定时器

  • 本文向大家介绍浅谈Node.js:理解stream,包括了浅谈Node.js:理解stream的使用技巧和注意事项,需要的朋友参考一下 Stream在node.js中是一个抽象的接口,基于EventEmitter,也是一种Buffer的高级封装,用来处理流数据。流模块便是提供各种API让我们可以很简单的使用Stream。 流分为四种类型,如下所示: Readable,可读流 Writable,可写

  • 本文向大家介绍浅谈node.js中async异步编程,包括了浅谈node.js中async异步编程的使用技巧和注意事项,需要的朋友参考一下 1.什么是异步编程? 异步编程是指由于异步I/O等因素,无法同步获得执行结果时, 在回调函数中进行下一步操作的代码编写风格,常见的如setTimeout函数、ajax请求等等。 示例: 这里大部分人会认为输出123,或者333。其实它会输出 444 这里就是我

  • 本文向大家介绍谈谈node.js中的模块系统,包括了谈谈node.js中的模块系统的使用技巧和注意事项,需要的朋友参考一下 Node.js 的模块 JavaScript 做为一门为网页添加交互功能的简单脚本语言问世,在诞生时并不包含模块系统,随着 JavaScript 解决问题越来越复杂,把所有代码写在一个文件内,用 function 区分功能单元已经不能支撑复杂应用开发了,ES6 带来了大部分高

  • 本文向大家介绍浅谈Scala模式匹配,包括了浅谈Scala模式匹配的使用技巧和注意事项,需要的朋友参考一下 一.scala模式匹配(pattern matching) pattern matching可以说是scala中十分强大的一个语言特性,当然这不是scala独有的,但这不妨碍它成为scala的语言的一大利器。 scala的pattern matching是类似这样的, 其中,变量e后面接一个