中间件顺序与请求上下文传递(c.Set/c.Get)

结论先行

中间件注册顺序即执行顺序:engine.Use 是全局的(作用于之后注册的所有路由),group.Use 只作用于该组及其子组,路由内联的 handler 排在最内层。执行像“洋葱”:外层中间件先执行,它 Next 之后的收尾代码最后执行。请求内共享数据用 c.Set(key, value) / c.Get(key),靠 *gin.Context.Keys 在同一条 handler 链中传递。

顺序示例

r := gin.New()
r.Use(A)        // 全局
api := r.Group("/api", B) // 分组(组内及子组)
api.Use(C)
api.GET("/x", H) // 实际执行链:A -> B -> C -> H

外层 A 的收尾代码(Next 之后)在 C、H 全部完成之后才运行——做日志、Recovery 都靠这个特性。

传递数据(c.Set / c.Get)

  • 类型:key 为 string,value 为 any。取用提供 Get/GetString/GetInt/GetBool/MustGet 等便捷方法。
  • 生命周期:仅当前请求;请求结束 Context 归还池后即失效,不能留给 goroutine 或下一个请求。
  • key 规范:不要到处写魔法字符串,用包内/共享常量,避免跨包碰撞覆盖。
  • 与 context.Context 分工:中间件间传业务值用 c.Keys;调用下游库(DB/HTTP)传取消/超时用 c.Request.Context()

代码示例

const CtxUserID = "ctx_user_id"

func Auth() gin.HandlerFunc {
    return func(c *gin.Context) {
        // ...校验 token 得到 uid...
        c.Set(CtxUserID, uid)
        c.Next()
    }
}

func Handler(c *gin.Context) {
    uid := c.GetString(CtxUserID) // 同链后续读取
    _ = uid
}

常见追问 / 记忆点

  • 追问:CORS 中间件为什么必须注册在鉴权之前?答:跨域预检 OPTIONS 若先撞上鉴权会被 401 拦掉;顺序错了整个链路表现诡异(详见 CORS 章节)。
  • 追问:c.Get 和 c.Request.Context().Value 是一回事吗?答:不是。前者查 Context.Keys(gin 内部),后者查 net/http 请求上下文,二者存储与生命周期都不同。
  • 追问:未匹配路由(404)会不会走全局中间件?答:Gin 默认全局中间件对未匹配请求同样生效(Logger 会打出 404 日志),NoRoute 注册的是链尾处理器,细节以官方文档为准。
  • 记忆点:注册顺序 = 洋葱顺序;Set/Get 只活一个请求,别跨 goroutine。
笔记加载中…