Chapter 05

中间件

理解洋葱模型与 handler 链、c.Next/c.Abort 的控制流、Logger/Recovery 原理,并学会写自定义中间件与多级挂载。

5.1 中间件相关名词

中间件(Middleware)
在业务 handler 前后执行的 gin.HandlerFunc,用于横切关注点:日志、鉴权、限流、CORS、恢复 panic。
handler 链(Handlers Chain)
一次请求匹配到的所有中间件 + 最终 handler 组成的有序切片,Gin 依次执行。
洋葱模型(Onion Model)
中间件像洋葱层层包裹:c.Next() 之前是「进入」逻辑,之后是「返回」逻辑,形成 A→B→handler→B→A 的执行顺序。
c.Next()
显式调用链中的下一个 handler;调用后的代码在内层全部执行完再运行(用于计时、后置处理)。
c.Abort()
终止后续 handler 的执行(当前函数体仍会跑完),常配合鉴权失败时使用。
c.Set / c.Get
在中间件与 handler 之间通过 Context 传递值(如把解析出的用户 ID 存进去)。
Use()
注册中间件的方法,可挂在 Engine(全局)、Group(分组)或单条路由。

5.2 中间件的本质

中间件就是一个 func(c *gin.Context)。它的行为分两半,以 c.Next() 为分界:

func Timing() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()          // ① 进入:前置逻辑

        c.Next()                      // ② 执行链中后续 handler

        cost := time.Since(start)   // ③ 返回:后置逻辑
        log.Printf("%s %s cost=%v status=%d",
            c.Request.Method, c.Request.URL.Path, cost, c.Writer.Status())
    }
}

5.3 洋葱模型执行顺序

r.Use(A(), B())   // 顺序注册 A、B
r.GET("/x", handler)

// 执行顺序(洋葱):
// A 前置  ->  B 前置  ->  handler  ->  B 后置  ->  A 后置
//
//   ┌───────── A ─────────┐
//   │  ┌────── B ──────┐  │
//   │  │   handler     │  │
//   │  └───────────────┘  │
//   └─────────────────────┘

5.4 c.Next 与 c.Abort

func AuthRequired() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")
        if token == "" {
            // AbortWithStatusJSON = 写响应 + Abort
            c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})
            return // 记得 return,Abort 不会自动跳出函数
        }
        userID := parseToken(token)
        c.Set("userID", userID) // 传给后续 handler
        c.Next()
    }
}

// handler 中取值
func profile(c *gin.Context) {
    uid, _ := c.Get("userID")
    c.JSON(200, gin.H{"uid": uid})
}
Abort 之后必须 return

c.Abort() 只是设置标志阻止后续 handler,不会中断当前函数体。忘了 return 会继续往下执行,可能重复写响应导致 "headers already written" 报错。

5.5 内置 Logger 与 Recovery

r := gin.New()
r.Use(gin.Logger())    // 打印 method/path/status/latency
r.Use(gin.Recovery())  // recover panic,返回 500,防止进程崩溃

// 自定义 Recovery 处理
r.Use(gin.CustomRecovery(func(c *gin.Context, err any) {
    log.Printf("panic recovered: %v", err)
    c.AbortWithStatusJSON(500, gin.H{"error": "internal error"})
}))

5.6 三种挂载级别

// ① 全局:所有路由
r.Use(Timing(), CORS())

// ② 分组:仅该组
api := r.Group("/api", AuthRequired())
api.Use(RateLimit())

// ③ 单路由:仅这一条
r.GET("/admin", AuthRequired(), AdminOnly(), adminHandler)

5.7 常见自定义中间件:RequestID

func RequestID() gin.HandlerFunc {
    return func(c *gin.Context) {
        id := c.GetHeader("X-Request-ID")
        if id == "" {
            id = uuid.NewString()
        }
        c.Set("requestID", id)
        c.Header("X-Request-ID", id) // 回写响应头,便于链路追踪
        c.Next()
    }
}
中间件工厂模式

约定俗成写成「返回 HandlerFunc 的函数」,好处是可以传参配置(如 RateLimit(100)),并在闭包里持有状态(如令牌桶、限流器实例)。

5.8 顺序很关键

中间件顺序决定行为,典型顺序:

  1. Recovery——最外层,兜住一切 panic。
  2. Logger / RequestID——记录每条请求。
  3. CORS——跨域头要尽早设置。
  4. RateLimit——限流应在鉴权前,先挡流量。
  5. Auth——鉴权,失败即 Abort。
  6. 业务 handler——最内层。

5.9 小结