中间件嵌套与执行顺序
理解中间件嵌套和执行顺序是掌握 Gin 请求处理的关键。
洋葱模型
Gin 中间件采用洋葱模型,请求从外向内穿透,响应从内向外返回:
Go
请求 → M1(前) → M2(前) → M3(前) → Handler → M3(后) → M2(后) → M1(后) → 响应
基本执行顺序
Go
func middleware1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("M1 前")
c.Next()
fmt.Println("M1 后")
}
}
func middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("M2 前")
c.Next()
fmt.Println("M2 后")
}
}
func middleware3() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("M3 前")
c.Next()
fmt.Println("M3 后")
}
}
func main() {
r := gin.New()
r.Use(middleware1())
r.Use(middleware2())
r.Use(middleware3())
r.GET("/test", func(c *gin.Context) {
fmt.Println("Handler")
c.String(200, "ok")
})
}
// 输出顺序:
// M1 前 → M2 前 → M3 前 → Handler → M3 后 → M2 后 → M1 后
路由组嵌套执行顺序
Go
func main() {
r := gin.New()
// 全局
r.Use(globalMiddleware())
// 第一层路由组
v1 := r.Group("/v1")
v1.Use(v1Middleware())
// 第二层嵌套
api := v1.Group("/api")
api.Use(apiMiddleware())
// 第三层嵌套
admin := api.Group("/admin")
admin.Use(adminMiddleware())
admin.GET("/users", handler)
// 执行顺序:
// global(前) → v1(前) → api(前) → admin(前) → handler → admin(后) → api(后) → v1(后) → global(后)
}
混合注册顺序
Go
func main() {
r := gin.New()
r.Use(m1())
r.Use(m2())
group := r.Group("/api")
group.Use(m3())
r.GET("/public", m4(), publicHandler) // 全局路由带中间件
group.GET("/test", m5(), handler) // 组内路由带中间件
// /public 执行:m1 → m2 → m4 → publicHandler → m4 → m2 → m1
// /api/test 执行:m1 → m2 → m3 → m5 → handler → m5 → m3 → m2 → m1
}
不调用 c.Next() 的行为
Go
func blockingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("Blocking 开始")
// 不调用 c.Next()
c.JSON(200, gin.H{"blocked": true})
fmt.Println("Blocking 结束")
// 后续中间件和处理函数不执行
}
}
r.Use(blockingMiddleware())
r.Use(m2())
r.GET("/test", handler)
// 输出:Blocking 开始 → Blocking 结束
// m2 和 handler 不执行
Abort 后的执行顺序
Go
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("Auth 前")
if !isAuthenticated(c) {
c.AbortWithStatus(401)
fmt.Println("Auth 终止")
return
}
c.Next()
fmt.Println("Auth 后")
}
}
func logMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("Log 前")
c.Next()
fmt.Println("Log 后") // 仍会执行
}
}
r.Use(logMiddleware())
r.Use(authMiddleware())
r.GET("/test", handler)
// 认证失败时:
// Log 前 → Auth 前 → Auth 终止 → Log 后
// handler 不执行
执行顺序可视化
Go
正常流程:
┌─────────────────────────────────────────────┐
│ M1 前 │
│ ┌─────────────────────────────────────────┐ │
│ │ M2 前 │ │
│ │ ┌───────────────────────────────────┐ │ │
│ │ │ Handler │ │ │
│ │ └───────────────────────────────────┘ │ │
│ │ M2 后 │ │
│ └─────────────────────────────────────────┘ │
│ M1 后 │
└─────────────────────────────────────────────┘
Abort 流程:
┌─────────────────────────────────────────────┐
│ M1 前 │
│ ┌─────────────────────────────────────────┐ │
│ │ M2 前 → Abort │ │
│ └─────────────────────────────────────────┘ │
│ M1 后 ← 继续执行 │
└─────────────────────────────────────────────┘
执行顺序控制技巧
条件跳过后续
Go
func conditionalMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if shouldSkip(c) {
c.Next() // 直接跳过当前中间件的后置处理
return
}
// 正常流程
c.Next()
// 后置处理
}
}
手动控制跳转
text
func manualControlMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("开始")
// 条件性调用后续
if shouldProceed(c) {
c.Next()
}
fmt.Println("结束")
}
}
实际应用场景
text
func main() {
r := gin.New()
// 日志(最外层)
r.Use(requestLogger())
// 异常恢复
r.Use(recovery())
// CORS
r.Use(cors())
// 认证 API
auth := r.Group("/auth")
auth.Use(authMiddleware())
auth.Use(permissionMiddleware())
{
auth.GET("/users", handler)
}
// 公开 API(不经过认证)
public := r.Group("/public")
{
public.GET("/info", handler)
}
// 执行顺序:
// 公开:logger → recovery → cors → handler → cors → recovery → logger
// 认证:logger → recovery → cors → auth → permission → handler → permission → auth → cors → recovery → logger
}
Abort 后外层中间件的后置代码仍会执行,可用于记录失败请求日志。
要点总结
- 中间件按注册顺序从外向内执行,形成洋葱结构
c.Next()调用后续处理,完成后返回当前函数- 不调用
c.Next()时后续中间件和处理函数不执行 c.Abort()阻止后续执行,外层后置代码仍执行- 路由组嵌套增加中间件层级,按层级顺序执行
- 合理设计中间件顺序,日志放最外层,认证放业务前
📝 发现内容有误?点击此处直接编辑