Koa2中间件实现的原理剖析

储备知识

node.js v7.6.0开始完全支持async/await,koa2 node环境需要7.6.0以上

前言

实际工作中,我们并没有那么多的时间去理解每个框架本身后面的知识,大多时候都是直接去官方,看着文档直接撸。写了koa项目也一段时间了 ,阅读了一些其他作者的笔记,来总结一下自己对koa2中间件的实现理解。

koa2中间件的使用和运行机制,可以去百度一下一大堆,简单来说就是一个洋葱模型。

在这里插入图片描述

这不是我们的主要目标,本文我们希望能够得到的是理解koa中间件的原理,并实现中间件的功能。

—————————————————————————————————————————

官网中有这么一个例子:
const Koa = require('koa');
const app = new Koa();

// logger
app.use(async (ctx, next) => {
  await next();
  const rt = ctx.response.get('X-Response-Time');
  console.log(`${ctx.method} ${ctx.url} - ${rt}`);
});

// x-response-time
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx.set('X-Response-Time', `${ms}ms`);
});

// response
app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);
根据中间件的执行顺序,我们可以得到一下步骤:

1.logger中间件 执行了 await next()将执行权移交给了下一个中间件x-response-time
2.x-response-time中间件 记录了当前的时间,把执行权移交给了下一个中间件response
3.response中间件 返回了’Hello World’,把执行权还给了x-response-time
4.x-response-time 记录了当前时间 - 执行开始时间,并在respones header中记录下来,把执行权移交给了logger中间件
5.logger中间件在response中获取到X-Response-Time,并打印相关结果。

思考?为什么使用app.use结合await next()可以实现洋葱模型的中间件工作原理?

我们一步一步来把步骤拆分开来。

实现app.use

app.use的作用就是注册中间件。我们可以这样来组织,新建一个koaMiddleWare.js

class koaMiddleWare {
    constructor(){
        this.middlewareList = []
    }

    //use方法
    use(fn){
        this.middlewareList.push(fn)
        return this
    }
}

1.定义一个类,在构造函数中初始化middlewareList数组,用于存放所以需要注册的中间件函数。
2.定义一个use函数,用来接收中间件,然后放到middlewareList数组中
3.return this 是为了实现链式操作,可以根据实际需求 返回

实现app.listen

const http = require('http')
class koaMiddleWare {
    constructor() {
        this.middlewareList = []
    }

    //use方法
    use(fn) {
        this.middlewareList.push(fn)
        return this
    }

    //整合req,res
    createContext(req, res) {
        //此处只做简单的模拟
        return ctx = {
            req,
            res
        }
    }

    //生成http.createServe所需的回调函数
    callback() {
        return (req, res) => { const ctx = this.createContext(req, res) }
    }
}

listen(...args){
    const server = http.createServer(this.callback())
    return server.listen(...args)
}

1.nodejs 原生的 http.createServer 需要传入一个回调函数,在 callback() 中返回。
2.示例代码中中间件函数的第一个参数都是 ctx ,其实可以简答理解为 res 和 req 的集合,通过 createContext 合并一下即可。

compose 组合中间件

上文一开始使用 use来注册中间件,再就是用 listen 去启动并监听服务,即刚开始就直接结束了。其实中间漏下很重要的一个步骤 —— 中间件组合,即如何让中间有 next机制,将中间件一个一个的串起来。

//传入中间件列表
function compose(middlewareList){
    //返回一个函数 接收ctx
    return function(ctx){
        //定义一个派发器,内部实现了next机制
        function dispatch(i){
            //获取当前中间件
            const fn = middlewareList[i]
            try{
                return Promise.resolve(
                    //通过i+1获取下一个中间件
                    fn(ctx,dispatch.bind(null,i+1))
                )
            }catch(err){
                return Promise.reject(err)
            }
        }
        //开始派发第一个中间件
        return dispatch(0)
    }
}

1.定义 compose 函数,并接收中间件列表。
2.compose 函数中返回一个函数,该函数接收 ctx ,下文会用这个返回的函数。
3.再往内部,定义了一个 dispatch 函数,就是一个中间件的派发器,参数 i 就代表派发第几个中间件。执行 dispatch(0) 就是开发派发第一个中间件。
4.派发器内部,通过 i 获取当前的中间件,然后执行。执行时传入的第一个参数是 ctx ,第二个参数是 dispatch.bind(null, i + 1) 即下一个中间件函数 —— 也正好对应到示例代码中中间件的 next 参数。
5.用 Promise.resolve 封装起来,是为了保证函数执行的结果必须是 Promise 类型。

完善 callback

有了 compose 之后,callback 即可被完善起来,相关代码(并不是全部的代码)如下,其中新增的 handleRequest 看看注释应该也能明白了。

//处理中间件的http请求
handleRequest(ctx,middleWare){
    //这个middlWare就是compose函数返回的fn
    //执行middleWare(ctx) 其实就是执行中间件函数,然后再用Promise.then封装返回
    return middleWare(ctx)
}

callback(){
    const fn = compose(this.middleWareList)

    return (req,res) => {
        const ctx = this.createContext(req,res)
        return this.handleRequest(ctx,fn)
    }
}

完整代码

const http = require('http');

// 组合中间件
function compose(middlewareList) {
    return function (ctx) {
        function dispatch(i) {
            const fn = middlewareList[i]
            try {
                return Promise.resolve(
                    fn(ctx, dispatch.bind(null, i + 1))
                )
            } catch (err) {
                return Promise.reject(err)
            }
        }
        return dispatch(0)
    }
}

class koaMiddleWare {
    constructor() {
        this.middlewareList = []
    }

    // 核心方法
    use(fn) {
        this.middlewareList.push(fn)
        return this
    }

    // 处理中间件的 http 请求
    handleRequest(ctx, middleWare) {
        // 这个 middleWare 就是 compose 函数返回的 fn
        // 执行 middleWare(ctx) 其实就是执行中间件函数,然后再用 Promise.resolve 封装并返回
        return middleWare(ctx)
    }

    // 将 req res 组合成为 ctx
    createContext(req, res) {
        // 简单模拟 koa 的 ctx ,不管细节了
        const ctx = {
            req,
            res
        }
        return ctx
    }

    callback() {
        const fn = compose(this.middlewareList)

        return (req, res) => {
            const ctx = this.createContext(req, res)
            return this.handleRequest(ctx, fn)
        }
    }

    listen(...args) {
        const server = http.createServer(this.callback())
        return server.listen(...args);
    }
}

module.exports = koaMiddleWare
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值