nodejs 中间件理解
中间件概念
在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框架的中间件机制实现得非常简洁和优雅,这里学习一下框架中组合多个中间件的核心代码。
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 中间件理解的更多相关文章
- 【前端知识体系-NodeJS相关】浅谈NodeJS中间件
1. 中间件到底是个什么东西呢? [!NOTE] 中间件其是一个函数,在响应发送之前对请求进行一些操作 function middleware(req,res,next){ // 做该干的事 // 做 ...
- 对nodejs的理解(一)
1.介绍一下事件驱动编程---快餐店点餐. 在基于线程的方式中(thread-based way)你到了柜台前,把你的点餐单给收银员或者给收银员直接点餐,然后等在那直到你要的食物准备好给你.收银员不能 ...
- nodejs中间件拦截,express不登录无法进入后台页面
22.设置拦截 只有登录才能进入到后台页面,不登录无法进入 如果登陆成功, 写入session, 参数 uid uid=123dsfjksldfjsl 检测登陆, 请求中 session 是否包含 u ...
- express中的中间件理解
什么是中间件 中间件是一个可访问请求对象(req)和响应对象(res)的函数,在 Express 应用的请求-响应循环里,下一个内联的中间件通常用变量 next 表示.中间件的功能包括: 执行任何代码 ...
- Django中间件理解
一.中间件 https://www.cnblogs.com/maple-shaw/articles/9333824.html 中间件:是一个类处理django的请求和响应,本质上就是一个类,在类里面定 ...
- Django 中间件理解
中间件 django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法. 应用场景,对所有 ...
- 77.深入理解nodejs中Express的中间件
转自:https://blog.csdn.net/huang100qi/article/details/80220012 Express是一个基于Node.js平台的web应用开发框架,在Node.j ...
- 理解面向消息的中间件和 JMS
本章知识点: 企业消息和面向消息的中间件 理解 Java Message Service(JMS) 使用 JMS APIs 发送和接收消息 消息驱动 bean 的一个例子 简介 一般来说,掌握了企业级 ...
- Koa - 中间件(理解中间件、实现一个验证token中间件)
前言 Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的. 当一个中间件调用 next() 则该函数暂停并将控制传递给定义的下一个中间件.当在下游没有更多的中间件执行后 ...
随机推荐
- zeus部署
1.下载zeus 阿里在github上已经不维护zeus了,在网上找到一个别人贡献的 https://github.com/michael8335/zeus2 下载下来 通过shell rz命令上传到 ...
- Windows netsh命令的使用
Windows netsh命令 netsh(也被称为网络壳层),是一个存在于自微软 Windows 20000开始的所有Windows NT系列中的命令行工具. netsh允许本地或远程配置网络设备. ...
- 5 LAMP配置管理:模块(state、file、pkg、service)、jinja模板、job管理、redis主从
1. 配置管理:state和file https://docs.saltstack.com/en/latest/topics/states/index.html Full list of states ...
- Linux下nc或scp命令来实现文件传输
很实用的小技巧, 可以使用nc或者是scp nc命令,转载自:https://www.cnblogs.com/xuybin/archive/2013/09/27/3343098.html 发送端:ca ...
- TTTTTTTTTTTTTT hdu 5763 Another Meaning 哈希+dp
Another Meaning Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)T ...
- Gym 100971A Treasure Island BFS 思维题
A - Treasure Island Time Limit:2000MS Memory Limit:262144KB 64bit IO Format:%I64d & %I64 ...
- 数位dp入门 HDU 2089 HDU 3555
最基本的一类数位dp题,题目大意一般是在a~b的范围,满足某些要求的数字有多少个,而这些要求一般都是要包含或者不包含某些数字,或者一些带着数字性质的要求,一般来说暴力是可以解决这一类问题,可是当范围非 ...
- python创建文件夹方法
def mkdir(path): # 引入模块 import os # 去除首位空格 path = path.strip() # 去除尾部 \ 符号 path = path.rstrip(" ...
- CF762F Tree nesting
题目连接 问题分析 可以给小树钦定一个根, \(Dp[i][j]\) 表示大树上的点 \(i\) 对应到小树上的点 \(j\) 的可能的方案数.然后每一步转移都是一个状压DP(将小树是否被匹配状压,然 ...
- AcWing:148. 合并果子(哈夫曼树)
在一个果园里,达达已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆. 达达决定把所有的果子合成一堆. 每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和. 可以看出 ...