【GO基础学习】Gin 框架中间件的详解
文章目录
- 中间件详解
- 中间件执行
- 全局中间件
- 路由级中间件
- 运行流程
- 中间件的链式执行
- 中断流程
- 代码示例
- gin框架总结
中间件详解
Gin 框架中间件是其核心特性之一,主要用于对 HTTP 请求的处理进行前置或后置的逻辑插入,例如日志记录、身份认证、错误处理等。
我们在创建默认的gin引擎时:r := gin.Default()
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery()) // 默认注册的两个中间件
return engine
}
通过Use()函数注册了Logger
中间件和Recovery
中间件,Use()函数:
// 也就是说,通过 Use() 连接的中间件将被
// 包含在每个请求的处理程序链中。即使是 404、405、静态文件...
// 例如,这里适合放置日志记录器或错误管理中间件。
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
实际上还是调用的RouterGroup的Use函数:
// 使用将中间件添加到组中。
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
注册中间件其实就是将中间件函数追加到group.Handlers
中。
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}
type HandlersChain []HandlerFunc
// HandlerFunc 定义了 gin 中间件作为返回值使用的处理程序。
type HandlerFunc func(*Context)
跟踪这个结构找到HandlersChain
,这是 Gin 中保存中间件和处理函数的核心数据结构,中间件和最终处理函数都以 HandlerFunc
的形式存储在链中。
Context
是 Gin 中非常重要的对象,负责在中间件之间传递数据:
type Context struct {
Request *http.Request // 当前的 HTTP 请求
Writer http.ResponseWriter // 响应输出
handlers HandlersChain // 当前请求的中间件链
index int8 // 当前执行到的中间件索引
Keys map[string]any // 自定义上下文数据存储
// 其他字段...
}
核心字段:
handlers
: 保存当前路由的中间件链。index
: 标记当前执行到第几个中间件。Keys
: 用于存储自定义的上下文数据,在中间件之间共享。
注册路由时,会将对应路由的函数和之前的中间件函数结合到一起:
handlers = group.combineHandlers(handlers) // 将处理请求的函数与中间件函数结合
中间件执行
Gin 中间件分为全局中间件和路由级中间件两种,最终都保存在 HandlersChain
中。
全局中间件
全局中间件通过 Engine.Use()
方法注册,作用于所有路由。
- 调用
Use()
方法,将中间件追加到Engine
的默认handlers
中。 - 在每次请求处理时,默认中间件会首先执行。
func (group *RouterGroup) Use(middleware ...HandlerFunc) *RouterGroup {
group.Handlers = append(group.Handlers, middleware...) // 注册中间件
return group
}
r := gin.Default() // gin.Default() 内置了 Logger 和 Recovery 全局中间件
路由级中间件
路由级中间件通过在特定路由上链式调用 Use()
方法注册。
- 路由级中间件注册时,保存在当前路由的
HandlersChain
中。 - 在匹配到具体路由时,这些中间件会被加入到
Context
的handlers
中,并按顺序执行。
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
字段来控制中间件的链式调用:
index
表示当前执行到的中间件索引。- 每个中间件需要调用
c.Next()
才能执行下一个中间件。 - 如果某个中间件不调用
c.Next()
,后续的中间件和处理函数将不会执行(终止流程)。
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c) // 执行下一个中间件或处理函数
c.index++
}
}
通过索引遍历HandlersChain
链条,从而实现依次调用该路由的每一个函数(中间件或处理请求的函数)。
以一个请求路径为例,假设 /hello
路径注册了两个中间件和一个处理函数:
r.GET("/hello", middleware1, middleware2, finalHandler)
执行顺序如下:
- 进入
middleware1
:
- 如果调用
c.Next()
,继续执行下一个中间件。
- 进入
middleware2
:
- 如果调用
c.Next()
,执行finalHandler
。
- 返回时按链路逆序执行剩余代码。
中断流程
某个中间件不调用 c.Next()
:
func middleware1(c *gin.Context) {
c.JSON(401, gin.H{"error": "unauthorized"})
c.Abort() // 停止后续中间件执行
}
- 调用
c.Abort()
会终止后续中间件或处理函数的执行。 - 标志位
c.index
被设置为最大值。
代码示例
- 全局中间件
package main
import (
"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
- 路由级中间件
package main
import (
"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
- 流程中断
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框架总结
Engine
是 Gin 框架的核心数据结构,负责路由注册、HTTP 请求处理,以及中间件的全局管理。
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 树。
type methodTrees []methodTree
type methodTree struct {
method string // HTTP 方法,如 GET、POST
root *node // Radix 树的根节点
}
-
Gin 在注册路由时,使用路径分段递归构建 Radix 树。
-
每个路由的处理函数和中间件保存在树节点的
handlers
字段中。
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
// 找到对应的 Radix 树
root := engine.methodTrees.get(method).root
root.addRoute(path, handlers)
}
RouterGroup
用于路由的分组和中间件管理,通过分组可以为一组路由统一添加中间件或前缀。
type RouterGroup struct {
Handlers HandlersChain // 路由分组级中间件
basePath string // 分组路径前缀
engine *Engine // 指向顶层的 Engine
}
- 分组路由:通过
Group()
方法创建子分组,支持嵌套分组。 - 注册中间件:分组级中间件通过
Use()
方法注册,作用范围为该分组及其子分组。
HandlersChain
是一个中间件链表,保存所有的中间件和处理函数,按顺序执行。
type HandlersChain []HandlerFunc
type HandlerFunc func(*Context)
-
接收
*Context
参数,在 HTTP 请求的生命周期中共享数据。 -
可通过调用
c.Next()
继续执行下一个中间件。
Context
是 Gin 中最重要的组件之一,用于在中间件和处理函数之间传递数据。
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 的中间件机制非常灵活,支持全局、分组、路由级中间件。
- 中间件链以
HandlersChain
的形式保存。 - 链式调用,每个中间件通过
c.Next()
调用下一个中间件。
总的流程:
- 路由匹配:
- 根据 HTTP 方法,从
methodTrees
中选择对应的 Radix 树。 - 使用路径递归查找匹配的节点。
- 中间件执行:
- 匹配成功后,合并全局中间件、分组中间件和路由级中间件,形成完整的
HandlersChain
。 - 按链式顺序调用中间件。
- 处理函数执行:
- 执行完所有中间件后,最终调用路由处理函数。
- 返回响应:
- 通过
Context
的方法生成 HTTP 响应。