美文网首页
Koa中间件的使用和实现原理

Koa中间件的使用和实现原理

作者: 指尖轻敲 | 来源:发表于2023-11-28 20:54 被阅读0次

    中间件使用

    了解原理之前先来看一下Koa中间件怎么使用

    import Koa from 'koa'
    import koaBody from 'koa-body'
    import checkToken from '@middleware/checkToken'
    
    const app = new Koa()
    app.use(koaBody({
        multipart: true,
        formidable: {
            maxFileSize: 50 * 1024 * 1024
        }
    ))
    app.use(checkToken())
    app.listen(port, () => {
        console.log('server running')
    })
    

    通过Koa实例的use方法,把中间件函数的调用作为参数传递进去(调用的时候可以传递一些参数)。所以中间件函数的返回值是一个函数。Koa会根据use调用中间件的先后顺序执行中间件函数。接下来看一下中间件的实现。

    中间件实现

    export const checkToken = () => {
      return async (ctx, next) => {
        const { authorization } = ctx.request.headers // 在此中间件之前使用了koa-body中间件
        try {
          const data = await check(authorization)
          ctx.userInfo = data.user_info
        } catch (e) {
          ctx.body = {
            code: -1,
            err: 'token校验失败'
          }
          return
        }
        await next()
      }
    }
    

    中间件的返回函数可以接收两个参数,第一个参数是当前请求的上下文,第二个参数可以暂时理解为下一个中间件函数,自己去控制后面中间件的调用时机。可以在最后调用,也可以在调用之后再做其它逻辑处理。类似这样:

    xxx // 此中间件逻辑
    await next()
    xxx // 此中间件逻辑
    

    那么为什么中间函数的返回函数可以接收到这两个参数呢?接下来就需要了解Koa的中间件实现原理了。

    中间件原理

    先来看一下use方法大概是做了什么(与源码的变量名可能有差异,便于理解):

    class Koa{
        constructor (){
            this.middlewares = []
        }
        use(middleware){
            if (typeof middleware !== 'function') throw new TypeError('middleware must be a function!')
            this.middlewares.push(middleware)
            return this
        }
    }
    

    校验代码就先忽略掉了,use主要是把中间件函数push到一个队列面,然后返回this是为了链式调用。

    另外还需要了解一下Koa的执行流程,从listen开始:

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

    这里使用了node的原生模块http,把接收到的参数直接传递给原生模块的listen方法。重点是传递给createServerthis.callback是什么。查看http模块的api发现crerateServer接收的是一个requestListener请求监听函数。接下来看下callback函数的实现。

    callback(){
        const fn = this.compose(this.middlewares) // 关键,中间的执行
        // ... 
        const handleRequest = (req, res) => {
            const ctx = this.createContext(req, res) // 通过createContext把原生的req和res生成ctx上下文对象
            return this.handleRequest(ctx, fn) // 注意:这个handleRequest是类的方法,不是这个局部的方法
        }
        return handleRequest // 返回监听函数
    }
    

    上面出现了三个函数,compose、createContext、类上的。先来看下compose:来自koa-compose包。以下是源码:

    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!')
        }
        // 这个函数就是返回给handleRequest的主要中间件调用逻辑
        return function (context, next) {
            // ... 删掉一些边界判断,简化一下代码,更清晰
            return dispatch(0)
            function dispatch (i) {
                let fn = middleware[i] // 从队列最开始取出一个中间件函数
                try {
                    // 向中间件函数fn中传递两个参数,一个context,第二个是递归下一个中间件
                    // 也就是前面编写中间件时的两个参数,ctx和next
                    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
                } catch (err) {
                    return Promise.reject(err)
                }
            }
        }
    }
    

    createContext这里不关心,来看handleRequest函数:

    handleRequest(ctx, fnMiddleware){
        // ...
        const onerror = err => ctx.onerror(err)
        const handleResponse = () => respond(ctx)
        // ...
        return fnMiddleware(ctx).then(handleResponse).catch(onerror) // 执行上面compose中返回的函数
    }
    

    相关的代码大致就这些,通过一个流程图来让整体调用流程更加一目了然吧。


    无标题流程图.drawio.png

    相关文章

      网友评论

          本文标题:Koa中间件的使用和实现原理

          本文链接:https://www.haomeiwen.com/subject/owsxgdtx.html