前言中间件是介于应用系统和系统软件之间的一类软件。它利用系统软件提供的基本服务(功能)将网络上的应用系统的各个部分或不同的应用连接起来,从而达到资源共享和功能共享的目的。在NodeJS中,中间件主要是指对http请求的详细处理进行封装的方法。我们都知道在http请求中经常会涉及到很多动作,如下:IP过滤查询字符串传递请求体解析cookie信息处理权限验证日志会话管理中间件(session)gzip压缩中间件(如compress)*错误处理当然,有许多自定义处理操作。对于Web应用,我们并不想了解每一个处理工作的细节,而是希望专注于业务开发,以达到提高开发效率的目的,因此引入了Node中间件来对这些基本的逻辑处理细节进行简化和封装。Node中间件本质上是让特定的过滤器在进入特定的业务处理之前进行处理。如下图所示:目前我们所见的主流nodejs框架,connect、koa、express、egg、nest等,都离不开中间件的设计理念,所以为了让大家更深入的了解纵观nodejs世界,我们会对中间件的实现原理进行大量的对比研究。了解了节点中间件的概念后,我们就手动实现中间件。最后简单分析一下koa中中间件的实现思路。文章大纲如下:node中间件核心原理实现koa中间件关键实现方法*使用koa中间件机制实现一个自己的koa中间件node中间件核心原理实现从上面的介绍我们可以知道中间件是从http请求开始到响应结束的处理逻辑,通常需要处理请求和响应。在实现节点中间件模型时我们需要考虑的另一个问题是多个中间件的共存。我们需要思考如何将多个中间件自动化执行,否则在request到response的过程中只会执行最开始的中间件,所以我们的基本中间件形式如下:constmiddleware=(req,res,next)=>{//请求处理逻辑next()}接下来写一个简单的案例看看中间件是如何实现的//定义几个中间函数constm1=(req,res,next)=>{console.log('m1run')next()}constm2=(req,res,next)=>{console.log('m2run')next()}constm3=(req,res,next)=>{console.log('m3run')next()}//中间件集合constmiddlewares=[m1,m2,m3]functionuseApp(req,res){constnext=()=>{//获取第一个中间件constmiddleware=middlewares.shift()if(middleware){middleware(req,res,next)}}next()}//第一个请求流进入useApp()从上面的代码不难发现next的函数,它是实现自动调用的关键参数中间件链。打印结果如下:上面的m1runm2runm3run实现了基本中间件模式的执行,但是我们还需要考虑异步问题,如果中间件还依赖第三个模块或者API支持支持,比如验证,识别等服务,我们需要在异步中间件的回调中执行next,保证正常的调用执行顺序,如下代码所示:constm2=(req,res,next)=>{fetch('/xxxxx').then(res=>{next()})}还有一个中间件场景,比如日志中间件,请求监听中间件,会在业务处理前后执行相关逻辑。这就要求我们需要能够对下一个函数进行二次处理。我们可以将next的返回值包装成一个promise,让它在业务处理完成后,通过then回调继续处理中间件逻辑。如下图:functionuseApp(req,res){constnext=()=>{constmiddleware=middlewares.shift()if(middleware){//将返回值包装成Promise对象returnPromise.resolve(middleware(req,res,next))}else{returnPromise.resolve("end")}}next()}此时我们可以使用如下方法调用:constm1=(req,res,next)=>{console.log('m1start')returnext().then(()=>{console.log('m1end')})}上面我们实现了一个基本的中间件设计模式,当然我们也可以使用async和await来实现,写法会更加优雅和简单。作者这里是copy简单例子:constm1=async(req,res,next)=>{//something...letresult=awaitnext();}constm2=async(req,res,next)=>{//something...letresult=awaitnext();}constm3=async(req,res,next)=>{//something...letresult=awaitnext();returnresult;}constmiddlewares=[m1,m2,m3];functionuseApp(req,res){constnext=()=>{constmiddleware=middlewares.shift()if(middleware){returnPromise.resolve(middleware(req,res,next))}else{returnPromise.resolve("end")}}next()}//启动中间件useApp()在koa2框架中,中间件的实现方式是将next()方法的返回值封装成一个Promise对象,实现其提出的洋葱圈模型,如下图所示:Web前端开发项目课程可以免费领取,帮助成长工厂开发工程师已经将koa中间件实现方法作废。koa2框架的中间件实现原理非常优雅。我觉得很有必要研究一下。这里是核心思想:functioncompose(middleware){//提前判断中间件的类型,防止后续出错beafunctiontypeif(typeoffn!=='function')thrownewTypeError('Middlewaremustbecomposedoffunctions!')}returnfunction(context,next){//使用闭包缓存索引实现调用计数letindex=-1returndispatch(0)functiondispatch(i){//防止next()方法重复调用if(i<=index)returnPromise.reject(newError('next()被多次调用'))index=iletfn=middleware[i]if(i===middleware.length)fn=nextif(!fn)returnPromise.resolve()try{//wrapnext()返回值对于Promise对象returnPromise.resolve(fn(context,dispatch.bind(null,i+1)));}catch(err){//异常处理returnPromise.reject(err)}}}}使用koa中间件机制实现自己的koa中间件。了解了中间件的设计机制和原理后,我们是不是要马上写一个中间件呢?这里我给大家举个例子。在H5-Dooring项目的服务端代码中,我们需要分配用户登录权限。这时候我们提供一个统一的中间件来处理,如下代码所示://模拟数据库操作consttoken=db.user();//router或者koa中间件接下来必须使用await来处理,否则会无法正常响应数据)if(uidArr.length>1){uid=uidArr.pop().trim()}if(token[uid]&&token[uid][1]===t){awaitnext()}else{ctx.status=403;ctx.body={state:403,msg:'您没有权限操作'}}}上面代码实现了用户登录状态的处理,如果用户没有登录询问任何需要登录的接口in会返回权限不足或者重定向到请求库中的登录页面。那么,今天你又长知识了吗?
