★ 中间件链如何执行?Next/Abort/Return 语义

结论先行

请求匹配后,gin 把「全局中间件 + 分组中间件 + 路由 handler」拼成一条 handler 链顺序执行。语义只需记三句话:c.Next() = 放行并等待后续执行完再回来;c.Abort() = 截断,不再执行后续 handler;return = 只退出当前函数,不会截断整条链。

关键语义

动作作用典型场景
c.Next()继续执行链上剩余 handler,返回后做收尾日志/计时:Next 前记开始,Next 后记耗时状态码
c.Abort()把执行游标推到链尾,剩余 handler 不再执行鉴权失败、限流命中
return(普通 return)只结束当前中间件函数结束本次逻辑;若想“短路”必须配合 Abort
AbortWithStatus/AbortWithStatusJSONAbort + 立即写状态码/响应体拦截类中间件的标准写法

容易踩的坑

  • 拦截中间件里只 returnAbort:链不会停,路由 handler 照样执行——鉴权直接失效。
  • 在 Next 之后又调用 Next:第二次是无害空操作(游标已到链尾)。
  • Abort 不“回滚”已经执行过的中间件,也不阻止当前函数里 Abort 之后的代码运行。
  • 中间件数量受游标类型上限约束,注册过多会触发 debug 提示,相关常量在 gin 源码 context 中(以源码为准)。

代码示例

// 拦截型:失败必须 Abort + 写响应 + return
func Auth() gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.GetHeader("Authorization") == "" {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "no token"})
            return // 短路,路由 handler 不再执行
        }
        c.Next()
    }
}

// 增强型:Next 前后包夹
func Cost() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        log.Printf("%s %s cost=%v", c.Request.Method, c.Request.URL.Path, time.Since(start))
    }
}

常见追问 / 记忆点

  • 追问:中间件里 return 但没 Abort,请求会怎样?答:当前函数结束,链继续往下走——很多鉴权漏洞就是这么来的。
  • 追问:Recovery 为什么能捕获 handler 的 panic?答:它内部 defer+recover 包住 c.Next(),panic 沿调用栈冒泡到它。
  • 记忆点:Next = 放行并等待;Abort = 截断链;return = 只退出自己。三者是 gin 中间件面试的“老三样”。
笔记加载中…