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

中间件使用

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

javascript 复制代码
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调用中间件的先后顺序执行中间件函数。接下来看一下中间件的实现。

中间件实现

js 复制代码
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()
  }
}

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

js 复制代码
xxx // 此中间件逻辑
await next()
xxx // 此中间件逻辑

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

中间件原理

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

javascript 复制代码
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开始:

javascript 复制代码
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函数的实现。

js 复制代码
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包。以下是源码:

js 复制代码
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函数:

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

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

相关推荐
她的男孩7 分钟前
一行配置,把 Spring Boot 后台变成 AI Agent 的工具箱:Forge MCP Server 插件源码拆解
人工智能·后端
寒水馨27 分钟前
Windows下载、安装electron-v43.2.0(附安装包electron-v43.2.0-win32-x64.zip)
javascript·windows·typescript·electron·跨平台·桌面应用·chromium
user_lwl40 分钟前
Oracle 数据库备份还原工具
后端
lczllx1 小时前
从 TCP 90μs 到 SHM 28μs:我的 RPC 框架零拷贝优化历程
linux·后端
用户938515635071 小时前
从零构建 React Todo 应用:组件化设计与数据流深度解析
前端·javascript
IT_陈寒1 小时前
Python线程池把我坑惨了,这些盲区你不踩?
前端·人工智能·后端
mONESY1 小时前
从 DeepSeek WebGPU 输入框,吃透 React+TS 四大核心开发实践
javascript·后端
程序员爱钓鱼1 小时前
Rust HashMap 详解:键值存储、查询、更新与统计
后端·面试·rust
程序员爱钓鱼1 小时前
第一个 Go 程序:Hello World
前端·后端·go