中间件概念

在NodeJS中,中间件主要是指封装所有Http请求细节处理的方法。一次Http请求通常包含很多工作,如记录日志、ip过滤、查询字符串、请求体解析、Cookie处理、权限验证、参数验证、异常处理等,但对于Web应用而言,并不希望接触到这么多细节性的处理,因此引入中间件来简化和隔离这些基础设施与业务逻辑之间的细节,让开发者能够关注在业务的开发上,以达到提升开发效率的目的。

中间件的行为比较类似Java中过滤器的工作原理,就是在进入具体的业务处理之前,先让过滤器处理。它的工作模型下图所示。

 
                                                          中间件工作模型

中间件机制核心实现

中间件是从Http请求发起到响应结束过程中的处理方法,通常需要对请求和响应进行处理,因此一个基本的中间件的形式如下:

const middleware = (req, res, next) => {
// TODO
next()
}

以下通过两种方式的中间件机制的实现来理解中间件是如何工作的。

方式一

如下定义三个简单的中间件:

const middleware1 = (req, res, next) => {
console.log('middleware1 start')
next()
} const middleware2 = (req, res, next) => {
console.log('middleware2 start')
next()
} const middleware3 = (req, res, next) => {
console.log('middleware3 start')
next()
}
// 中间件数组
const middlewares = [middleware1, middleware2, middleware3]
function run (req, res) {
const next = () => {
// 获取中间件数组中第一个中间件
const middleware = middlewares.shift()
if (middleware) {
middleware(req, res, next)
}
}
next()
}
run() // 模拟一次请求发起

  

执行以上代码,可以看到如下结果:

middleware1 start
middleware2 start
middleware3 start

如果中间件中有异步操作,需要在异步操作的流程结束后再调用next()方法,否则中间件不能按顺序执行。改写middleware2中间件:

const middleware2 = (req, res, next) => {
console.log('middleware2 start')
new Promise(resolve => {
setTimeout(() => resolve(), 1000)
}).then(() => {
next()
})
}

执行结果与之前一致,不过middleware3会在middleware2异步完成后执行。

middleware1 start
middleware2 start
middleware3 start

有些中间件不止需要在业务处理前执行,还需要在业务处理后执行,比如统计时间的日志中间件。在方式一情况下,无法在next()为异步操作时再将当前中间件的其他代码作为回调执行。因此可以将next()方法的后续操作封装成一个Promise对象,中间件内部就可以使用next.then()形式完成业务处理结束后的回调。改写run()方法如下:

function run (req, res) {
const next = () => {
const middleware = middlewares.shift()
if (middleware) {
// 将middleware(req, res, next)包装为Promise对象
return Promise.resolve(middleware(req, res, next))
}
}
next()
}

中间件的调用方式需改写为:

const middleware1 = (req, res, next) => {
console.log('middleware1 start')
// 所有的中间件都应返回一个Promise对象
// Promise.resolve()方法接收中间件返回的Promise对象,供下层中间件异步控制
return next().then(() => {
console.log('middleware1 end')
})
}
const middleware1 = (req, res, next) => {
console.log('middleware1 start')
// 所有的中间件都应返回一个Promise对象
// Promise.resolve()方法接收中间件返回的Promise对象,供下层中间件异步控制
return next().then((res) => {
console.log("1",res)
return 'middleware1 end';
})
} const middleware2 = (req, res, next) => {
console.log('middleware2 start')
// 所有的中间件都应返回一个Promise对象
// Promise.resolve()方法接收中间件返回的Promise对象,供下层中间件异步控制
// console.log("next()",next())
return next().then((res) => {
console.log("2",res)
return 'middleware2 end'
})
}
const middleware3 = (req, res, next) => {
console.log('middleware3 start')
return next().then((res) => {
console.log("3",res)
return 'middleware3 end'
})
} const middlewares = [middleware1, middleware2, middleware3] function run (req, res) {
const next = () => {
const middleware = middlewares.shift()
if (middleware) {
// console.log("next",next)
// 将middleware(req, res, next)包装为Promise对象
return Promise.resolve(middleware(req, res, next))
}else {
return Promise.resolve("结束");
}
}
next()
}
run() // 模拟一次请求发起

结果:

async await 实现
 
const middleware1 = async (req, res, next) => {
console.log('middleware1 start')
let result = await next();
console.log("1",result)
} const middleware2 = async (req, res, next) => {
console.log('middleware2 start')
let result = await next();
console.log("2",result)
return 'middleware2 end';
}
const middleware3 = async (req, res, next) => {
console.log('middleware3 start')
let result = await next();
console.log("3",result)
return 'middleware3 end';
} const middlewares = [middleware1, middleware2, middleware3] function run (req, res) {
const next = () => {
const middleware = middlewares.shift()
if (middleware) {
// console.log("next",next)
// 将middleware(req, res, next)包装为Promise对象
return Promise.resolve(middleware(req, res, next))
}else {
return Promise.resolve("结束");
}
}
next()
}
run() // 模拟一次请求发起

以上描述了中间件机制中多个异步中间件的调用流程,实际中间件机制的实现还需要考虑异常处理、路由等。

express框架中,中间件的实现方式为方式一,并且全局中间件和内置路由中间件中根据请求路径定义的中间件共同作用,不过无法在业务处理结束后再调用当前中间件中的代码。koa2框架中中间件的实现方式为方式二,将next()方法返回值封装成一个Promise,便于后续中间件的异步流程控制,实现了koa2框架提出的洋葱圈模型,即每一层中间件相当于一个球面,当贯穿整个模型时,实际上每一个球面会穿透两次。

 
koa2中间件洋葱圈模型

koa2框架的中间件机制实现得非常简洁和优雅,这里学习一下框架中组合多个中间件的核心代码。

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!')
}
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
// index会在next()方法调用后累加,防止next()方法重复调用
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 {
// 核心代码
// 包装next()方法返回值为Promise对象
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
// 遇到异常中断后续中间件的调用
return Promise.reject(err)
}
}
}
}

koa中间件列表地址:https://github.com/koajs/koa/wiki

总结

本文主要介绍了中间件的概念、为何引入中间件以及中间件机制的核心实现。中间件机制使得Web应用具备良好的可扩展性和组合性。

在实现中间件时,单个中间件应该足够简单,职责单一。由于每个请求都会调用中间件相关代码,中间件的代码应该高效,必要的时候可以缓存重复获取的数据。在对不同的路由使用中间件时,还应该考虑到不同的中间件应用到不同的路由上。

参考来源:https://www.jianshu.com/p/81b6ebc0dd85

nodejs 中间件理解的更多相关文章

  1. 【前端知识体系-NodeJS相关】浅谈NodeJS中间件

    1. 中间件到底是个什么东西呢? [!NOTE] 中间件其是一个函数,在响应发送之前对请求进行一些操作 function middleware(req,res,next){ // 做该干的事 // 做 ...

  2. 对nodejs的理解(一)

    1.介绍一下事件驱动编程---快餐店点餐. 在基于线程的方式中(thread-based way)你到了柜台前,把你的点餐单给收银员或者给收银员直接点餐,然后等在那直到你要的食物准备好给你.收银员不能 ...

  3. nodejs中间件拦截,express不登录无法进入后台页面

    22.设置拦截 只有登录才能进入到后台页面,不登录无法进入 如果登陆成功, 写入session, 参数 uid uid=123dsfjksldfjsl 检测登陆, 请求中 session 是否包含 u ...

  4. express中的中间件理解

    什么是中间件 中间件是一个可访问请求对象(req)和响应对象(res)的函数,在 Express 应用的请求-响应循环里,下一个内联的中间件通常用变量 next 表示.中间件的功能包括: 执行任何代码 ...

  5. Django中间件理解

    一.中间件 https://www.cnblogs.com/maple-shaw/articles/9333824.html 中间件:是一个类处理django的请求和响应,本质上就是一个类,在类里面定 ...

  6. Django 中间件理解

    中间件 django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法. 应用场景,对所有 ...

  7. 77.深入理解nodejs中Express的中间件

    转自:https://blog.csdn.net/huang100qi/article/details/80220012 Express是一个基于Node.js平台的web应用开发框架,在Node.j ...

  8. 理解面向消息的中间件和 JMS

    本章知识点: 企业消息和面向消息的中间件 理解 Java Message Service(JMS) 使用 JMS APIs 发送和接收消息 消息驱动 bean 的一个例子 简介 一般来说,掌握了企业级 ...

  9. Koa - 中间件(理解中间件、实现一个验证token中间件)

    前言 Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的. 当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件.当在下游没有更多的中间件执行后 ...

随机推荐

  1. 使用remix的solidity在线编译环境部署一个faucet合约

    一.浏览器打开https://remix.ethereum.org 地址 点击"+"新建一个sol文件,命名为faucet.sol 然后在代码区输入solidity代码 选择相关插 ...

  2. 关于VIM中展示二进制字符的操作

    在网上拷贝了一段代码放到linux下变异,发现每行的行首有一堆不可识别的字符.放到windows的notepad下发现也不是空格也不是tab,权当是某种不可识别的缩进字符把 解决方法  linux c ...

  3. java8新特性学习:函数式接口

    本文概要 什么是函数式接口? 如何定义函数式接口? 常用的函数式接口 函数式接口语法注意事项 总结 1. 什么是函数式接口? 函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口 ...

  4. IPC 进程间通信方式——信号量

    信号量 本质上是共享资源的数目,用来控制对共享资源的访问. 用于进程间的互斥和同步 每种共享资源对应一个信号量,为了便于大量共享资源的操作引入了信号量集,可对多对信号量一次性操作.对信号量集中所有的操 ...

  5. Navicat导出表结构

    SQL Server导出表结构 Oracle导出表结构

  6. Confluence 6.15 修改历史(Change-History)宏

    修改历史(Change-History)宏显示了页面一个的更新历史:版本号,作者,日期和备注.这些内容将会在同一栏中进行显示. 屏幕截图:Confluence 中的修改历史(Change-Histor ...

  7. 8.JavaScript

    1.JavaScript简介 JavaScript主要运行在客户端,用户访问带有JavaScript的网页,网页里的JavaScript程序就会传给浏览器,由浏览器解释和处理.表单数据的有效性验证等互 ...

  8. flask框架(九): 请求和响应扩展以及中间件

    一:请求响应扩展 # 每一次访问都执行 # 注意请求之前按照顺序执行 # 请求之后按照书写顺序倒序执行 # 请求之前执行 @app.before_request def process_request ...

  9. nu.random.seed()如何理解

    结论: np.random.seed(a) # 按照规定的顺序生成随机数 # 参数a指定了随机数生成的起始位置: # 如果两处都采用了np.random.seed(a),且两处的参数a相同,则生成的随 ...

  10. 第06课:作用域、JS预解析机制

    从字面上理解----域就是空间.范围.区域,作用就是读.写,所以作用域我们可以简单理解为:在什么样空间或者范围内对数据进行什么样的读或写操作. 看一下代码 alert(a); // 为什么是undef ...