★ 中间件链如何执行?Next/Abort/Return 语义
结论先行
请求匹配后,gin 把「全局中间件 + 分组中间件 + 路由 handler」拼成一条 handler 链顺序执行。语义只需记三句话:c.Next() = 放行并等待后续执行完再回来;c.Abort() = 截断,不再执行后续 handler;return = 只退出当前函数,不会截断整条链。
关键语义
| 动作 | 作用 | 典型场景 |
|---|---|---|
| c.Next() | 继续执行链上剩余 handler,返回后做收尾 | 日志/计时:Next 前记开始,Next 后记耗时状态码 |
| c.Abort() | 把执行游标推到链尾,剩余 handler 不再执行 | 鉴权失败、限流命中 |
| return(普通 return) | 只结束当前中间件函数 | 结束本次逻辑;若想“短路”必须配合 Abort |
| AbortWithStatus/AbortWithStatusJSON | Abort + 立即写状态码/响应体 | 拦截类中间件的标准写法 |
容易踩的坑
- 拦截中间件里只
return不Abort:链不会停,路由 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 中间件面试的“老三样”。