简单设计实现koa2中间件

简单设计实现koa2中间件

导读:根据koa2应用的中间件处理的流程、规范和特性来简单实现一下。以koa2的脚手架生成的实例工程为例:

npm i koa-generator -g
koa2 yourname
npm i & npm run dev

打开工程根目录下的app.js文件,如下。可以看到,koa2的中间件通过app.use来进行注册, 每一个中间件有固定的格式:

  1. 都是一个async函数,即一个promise.
  2. 每个中间件接收两个参数ctx和next,这个ctx就是context上下文的意思,next就是执行下一个中间件的一个方法。这里面相关的有一个洋葱圈模型,如下图,意思是说整个中间件的执行过程就像一个洋葱,一个中间件包裹了另外一个中间件。
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')

const index = require('./routes/index')
const users = require('./routes/users')

// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(__dirname + '/public'))

app.use(views(__dirname + '/views', {
  extension: 'pug'
}))

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  console.error('server error', err, ctx)
});

module.exports = app

简单设计实现koa2中间件

在实现之前,我们要想清楚koa2中间件做了什么处理:

  1. 通过app.use方法把中间件的执行串联了起来,像一个洋葱一样,一层套一层.
  2. 每一个中间件都是一个async函数,而async函数本质上是一个promise.
  3. 每个中间件接收两个参数,ctx和next,ctx就是req和res的集合,next用来执行下一个中间件。
    以koa官网的代码示例为例:
const Koa = require('koa');
const app = new Koa();

// logger
app.use(async (ctx, next) => {
  console.log('第一层洋葱开始')
  await next();
  const rt = ctx['X-Response-Time'];
  console.log(`${ctx.req.method} ${ctx.req.url} - ${rt}`);
  console.log('第一层洋葱结束')
});

// x-response-time
app.use(async (ctx, next) => {
  console.log('第二层洋葱开始')
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  ctx['X-Response-Time'] = `${ms}ms`;
  console.log('第二层洋葱结束')
});

// response
app.use(async ctx => {
  console.log('第三层洋葱开始')
  ctx.res.end('This is like koa2');
  console.log('第三层洋葱结束')
});

app.listen(8000);

使用node执行app.js打印如下:
第一层洋葱开始 => 第二层洋葱开始 => 第三层洋葱开始 => 第三层洋葱结束 => 第二层洋葱结束 => 第一层洋葱结束。

可以看出,koa2的中间件数据结构其实是一个先进后出的队列结构。

以上3条一个个来实现一下:

1. app.use

class Koa2 {
   constructor() {
        // 存储注册的中间件
        this.middlewareList = []
    }
    
    // 注册中间件
    use(fn) {
        this.middlewareList.push(fn)
    }
    
    // 很重要,用来生成一个koa2服务, 中间件的所有执行都在callback中实现
    listen(...args) {
          // 中间件的执行入口
          const server = http.createServer(this.callback())
          server.listen(...args)
   }
}

2. 实现中间件函数的嵌套调用以及参数绑定和实现(ctx和next)

这里使用了 dispatch.bind(null, i+1) 来实现中间件中的next。

const http = require('http')
class Koa2 {
   constructor() {
        // 存储注册的中间件
        this.middlewareList = []
    }
    
    // 注册中间件
    use(fn) {
        this.middlewareList.push(fn)
    }

   createContext(req, res) {
     const ctx = {req, res}
     ctx.query = req.query
     return ctx
   }
   
   recursiveMiddleware(middlewareList){
      return executionMiddleware(ctx) {
         const dispatch = (i) => {
            const fn = middlewareList[i]
            return fn(ctx, dispatch.bind(null, i+1))
        }
        return dispatch(0)
     }
   }

    callback() {
      // http server的回调函数格式
      return (req, res) => {
         const ctx = createContext(req, res)
         return this.recursiveMiddleware(this.middlewareList)(ctx)
      }
    }
    
    // 很重要,用来生成一个koa2服务, 中间件的所有执行都在callback中实现
    listen(...args) {
          // 中间件的执行入口
          const server = http.createServer(this.callback())
          server.listen(...args)
   }
}
module.exports = Koa2

继续优化

按照书写规范,中间件要求是async格式的函数,是一个promise. 所以这里还需要做一下兼容,如果不写async,也要是一个promise:

const http = require('http')
class Koa2 {
   constructor() {
        // 存储注册的中间件
        this.middlewareList = []
    }
    
    // 注册中间件
    use(fn) {
        this.middlewareList.push(fn)
    }

   createContext(req, res) {
     const ctx = {req, res}
     ctx.query = req.query
     return ctx
   }
   
   recursiveMiddleware(middlewareList){
      return executionMiddleware(ctx) {
         const dispatch = (i) => {
            const fn = middlewareList[i]
            // 这里使用try、catch来捕获promise中的reject
            try{
             return await Promise.resolve(fn(ctx, dispatch.bind(null, i+1)))
            }catch(err) {
             return Promise.reject(err)
            }
        }
        return dispatch(0)
     }
   }

    callback() {
      // http server的回调函数格式
      return (req, res) => {
         const ctx = createContext(req, res)
         return this.recursiveMiddleware(this.middlewareList)(ctx)
      }
    }
    
    // 很重要,用来生成一个koa2服务, 中间件的所有执行都在callback中实现
    listen(...args) {
          // 中间件的执行入口
          const server = http.createServer(this.callback())
          server.listen(...args)
   }
}
module.exports = Koa2

希望对你有所启发!

原文链接:https://juejin.cn/post/7212924329428795450 作者:阿里阿多

(0)
上一篇 2023年3月21日 下午8:14
下一篇 2023年3月22日 上午11:05

相关推荐

发表回复

登录后才能评论