中间件详解
Gin 框架中间件是其核心特性之一,主要用于对 HTTP 请求的处理进行前置或后置的逻辑插入,例如日志记录、身份认证、错误处理等。
我们在创建默认的gin引擎时:r := gin.Default()
go">func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery()) // 默认注册的两个中间件return engine
}
通过Use()函数注册了Logger
中间件和Recovery
中间件,Use()函数:
go">// 也就是说,通过 Use() 连接的中间件将被
// 包含在每个请求的处理程序链中。即使是 404、405、静态文件...
// 例如,这里适合放置日志记录器或错误管理中间件。
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {engine.RouterGroup.Use(middleware...)engine.rebuild404Handlers()engine.rebuild405Handlers()return engine
}
实际上还是调用的RouterGroup的Use函数:
go">// 使用将中间件添加到组中。
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {group.Handlers = append(group.Handlers, middleware...)return group.returnObj()
}
注册中间件其实就是将中间件函数追加到group.Handlers
中。
go">type RouterGroup struct {Handlers HandlersChainbasePath stringengine *Engineroot bool
}type HandlersChain []HandlerFunc// HandlerFunc 定义了 gin 中间件作为返回值使用的处理程序。
type HandlerFunc func(*Context)
跟踪这个结构找到HandlersChain
,这是 Gin 中保存中间件和处理函数的核心数据结构,中间件和最终处理函数都以 HandlerFunc
的形式存储在链中。
Context
是 Gin 中非常重要的对象,负责在中间件之间传递数据:
go">type Context struct {Request *http.Request // 当前的 HTTP 请求Writer http.ResponseWriter // 响应输出handlers HandlersChain // 当前请求的中间件链index int8 // 当前执行到的中间件索引Keys map[string]any // 自定义上下文数据存储// 其他字段...
}
核心字段:
注册路由时,会将对应路由的函数和之前的中间件函数结合到一起:
handlers = group.combineHandlers(handlers) // 将处理请求的函数与中间件函数结合
中间件执行
Gin 中间件分为全局中间件和路由级中间件两种,最终都保存在 HandlersChain
中。
全局中间件
全局中间件通过 Engine.Use()
方法注册,作用于所有路由。
go">func (group *RouterGroup) Use(middleware ...HandlerFunc) *RouterGroup {group.Handlers = append(group.Handlers, middleware...) // 注册中间件return group
}r := gin.Default() // gin.Default() 内置了 Logger 和 Recovery 全局中间件
路由级中间件
路由级中间件通过在特定路由上链式调用 Use()
方法注册。
go">func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {handlers = group.combineHandlers(handlers) // 合并路由级中间件和处理函数group.engine.addRoute("GET", path, handlers)return group.returnObj()
}
运行流程
中间件的链式执行
Gin 使用 Context
中的 index
字段来控制中间件的链式调用:
go">func (c *Context) Next() {c.index++for c.index < int8(len(c.handlers)) {c.handlers[c.index](c) // 执行下一个中间件或处理函数c.index++}
}
通过索引遍历HandlersChain
链条,从而实现依次调用该路由的每一个函数(中间件或处理请求的函数)。
以一个请求路径为例,假设 /hello
路径注册了两个中间件和一个处理函数:
go">r.GET("/hello", middleware1, middleware2, finalHandler)
执行顺序如下:
- 进入
middleware1
:
- 如果调用
c.Next()
,继续执行下一个中间件。
- 进入
middleware2
:
- 如果调用
c.Next()
,执行finalHandler
。
- 返回时按链路逆序执行剩余代码。
中断流程
某个中间件不调用 c.Next()
:
go">func middleware1(c *gin.Context) {c.JSON(401, gin.H{"error": "unauthorized"})c.Abort() // 停止后续中间件执行
}
- 调用
c.Abort()
会终止后续中间件或处理函数的执行。 - 标志位
c.index
被设置为最大值。
代码示例
- 全局中间件
go">package mainimport ("github.com/gin-gonic/gin"
)func Logger() gin.HandlerFunc {return func(c *gin.Context) {// 执行前fmt.Println("Logger - Before Request")c.Next() // 执行下一个中间件// 执行后fmt.Println("Logger - After Request")}
}func main() {r := gin.New()// 注册全局中间件r.Use(Logger())r.GET("/hello", func(c *gin.Context) {c.String(200, "Hello World")})r.Run(":8080")
}
执行流程:
Logger - Before Request
Hello World
Logger - After Request
- 路由级中间件
go">package mainimport ("github.com/gin-gonic/gin"
)func Middleware1() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("Middleware1 - Before")c.Next()fmt.Println("Middleware1 - After")}
}func Middleware2() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("Middleware2 - Before")c.Next()fmt.Println("Middleware2 - After")}
}func main() {r := gin.New()r.GET("/test", Middleware1(), Middleware2(), func(c *gin.Context) {fmt.Println("Final Handler")c.String(200, "OK")})r.Run(":8080")
}
执行流程:
Middleware1 - Before
Middleware2 - Before
Final Handler
Middleware2 - After
Middleware1 - After
- 流程中断
go">func AuthMiddleware() gin.HandlerFunc {return func(c *gin.Context) {token := c.GetHeader("Authorization")if token == "" {c.JSON(401, gin.H{"error": "unauthorized"})c.Abort() // 中断流程return}c.Next()}
}r.GET("/secure", AuthMiddleware(), func(c *gin.Context) {c.String(200, "Secure Data")
})
gin_277">gin框架总结
Engine
是 Gin 框架的核心数据结构,负责路由注册、HTTP 请求处理,以及中间件的全局管理。
go">type Engine struct {RouterGroup // 继承了 RouterGroup,用于路由分组和中间件管理handlers HandlersChain // 全局中间件链methodTrees methodTrees // 路由树存储,不同 HTTP 方法对应一棵 Radix 树ContextPool sync.Pool // 上下文对象池,复用 Context 提高性能
}
RouterGroup
:- 用于路由分组。
- 支持分组级别的中间件注册。
methodTrees
:- 保存路由信息的核心,
methodTrees
是一组 Radix 树,不同 HTTP 方法(如 GET、POST)对应一棵独立的路由树。 - 每个树节点存储路由路径的部分信息,并链接到处理函数。
- 保存路由信息的核心,
handlers
:ContextPool
:- 通过对象池复用
Context
,减少内存分配和垃圾回收的开销。
- 通过对象池复用
路由树构建:methodTrees
是路由信息存储的核心结构,不同 HTTP 方法各自维护一棵 Radix 树。
go">type methodTrees []methodTreetype methodTree struct {method string // HTTP 方法,如 GET、POSTroot *node // Radix 树的根节点
}
-
Gin 在注册路由时,使用路径分段递归构建 Radix 树。
-
每个路由的处理函数和中间件保存在树节点的
handlers
字段中。
go">func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {// 找到对应的 Radix 树root := engine.methodTrees.get(method).rootroot.addRoute(path, handlers)
}
RouterGroup
用于路由的分组和中间件管理,通过分组可以为一组路由统一添加中间件或前缀。
go">type RouterGroup struct {Handlers HandlersChain // 路由分组级中间件basePath string // 分组路径前缀engine *Engine // 指向顶层的 Engine
}
HandlersChain
是一个中间件链表,保存所有的中间件和处理函数,按顺序执行。
go">type HandlersChain []HandlerFunctype HandlerFunc func(*Context)
-
接收
*Context
参数,在 HTTP 请求的生命周期中共享数据。 -
可通过调用
c.Next()
继续执行下一个中间件。
Context
是 Gin 中最重要的组件之一,用于在中间件和处理函数之间传递数据。
go">type Context struct {Request *http.Request // 当前请求Writer http.ResponseWriter // 当前响应handlers HandlersChain // 当前中间件链index int8 // 当前中间件执行位置Keys map[string]any // 用于存储用户自定义数据// 其他字段...
}
中间件链控制:
- 调用
c.Next()
执行下一个中间件。 - 调用
c.Abort()
中断执行链。
响应输出:
- 通过
c.JSON()
、c.String()
等方法生成 HTTP 响应。
自定义数据存储:
c.Set()
和c.Get()
用于在中间件间共享数据。
Gin 的中间件机制非常灵活,支持全局、分组、路由级中间件。
总的流程:
- 路由匹配:
- 根据 HTTP 方法,从
methodTrees
中选择对应的 Radix 树。 - 使用路径递归查找匹配的节点。
- 中间件执行:
- 处理函数执行:
- 执行完所有中间件后,最终调用路由处理函数。
- 返回响应:
- 通过
Context
的方法生成 HTTP 响应。