前言 在 Gin 中实现一个中间件是一件非常轻量的事情,几乎没有“生命周期钩子”这种复杂概念,也不需要学习多少新 API。只需要返回一个 gin.HandlerFunc 类型的函数即可。
得益于 Gin 的“洋葱模型”执行链路机制,我们可以利用 ctx.Abort 中断请求,也可以利用 ctx.Next 控制中间件在请求前/请求后两个阶段执行逻辑,从而轻松实现日志、鉴权、限流等能力。
本篇将基于简单示例逐步展开,帮助你真正理解 Gin 中间件的执行顺序、结构与背后的设计思路。
实现一个中间件 如前言所说,实现中间件非常简单,只需要返回一个 gin.HandlerFunc 即可。
定义中间件函数 gin.HandlerFunc 本质上就是一个函数类型,它只接收一个参数:
下面实现一个最简单的打印日志的中间件:
1 2 3 4 5 6 7 func MyLoggerMiddleware () gin.HandlerFunc { return func (ctx *gin.Context) { fmt.Println("========================" ) fmt.Println("my logger middleware" ) fmt.Println("========================" ) } }
就是这么直接,没有额外的结构体、没有必须实现的接口。
使用中间件 使用方式也同样简单,直接调用 r.Use:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport ( "fmt" "github.com/gin-gonic/gin" )func MyLoggerMiddleware () gin.HandlerFunc { return func (ctx *gin.Context) { fmt.Println("========================" ) fmt.Println("my logger middleware" ) fmt.Println("========================" ) } }func main () { r := gin.Default() r.Use(MyLoggerMiddleware()) r.GET("/test" , func (ctx *gin.Context) { ctx.JSON(200 , gin.H{ "message" : "hello world" , }) }) r.Run() }
请求 /test 后即可在控制台看到日志输出。
中断和继续 Gin 的中间件机制依赖两个关键方法:
ctx.Abort():中断整个执行链
ctx.Next():决定“继续执行下一个环节”
下面分别解析。
Abort:中断请求 我们在刚才的中间件中加入 ctx.Abort():
1 2 3 4 5 6 7 8 func MyLoggerMiddleware () gin.HandlerFunc { return func (ctx *gin.Context) { fmt.Println("========================" ) fmt.Println("my logger middleware" ) fmt.Println("========================" ) ctx.Abort() } }
效果如下:
请求会到达当前中间件
中间件调用 Abort() 后
控制器 Handler 不会再执行
后续的中间件也不会执行
但当前中间件本身仍然已执行
下面是实际示例截图,可以看到请求被中断,没有返回响应体:
Abort 经常用于“拦截场景”,如权限校验、IP 白名单、请求频率限制等。
Next:区分请求前 / 请求后执行 我们再定义一个新的中间件,继续观察 Gin 的执行顺序:
1 2 3 4 5 6 7 func MyLoggerAfterMiddleware () gin.HandlerFunc { return func (ctx *gin.Context) { fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>>>" ) fmt.Println("my logger after middleware" ) fmt.Println(">>>>>>>>>>>>>>>>>>>>>>>>>" ) } }
现在注册两个:
1 r.Use(MyLoggerMiddleware(), MyLoggerAfterMiddleware())
默认情况下会按顺序执行:
MyLoggerMiddleware
MyLoggerAfterMiddleware
如下图:
加入 ctx.Next 接下来对 MyLoggerMiddleware 做简单调整:
1 2 3 4 5 6 7 8 func MyLoggerMiddleware () gin.HandlerFunc { return func (ctx *gin.Context) { fmt.Println("========================" ) ctx.Next() fmt.Println("my logger middleware" ) fmt.Println("========================" ) } }
这时执行顺序会发生变化:
MyLoggerMiddleware(Next 前)
MyLoggerAfterMiddleware
控制器 Handler
MyLoggerMiddleware(Next 后)
如下图所示:
ctx.Next() 的作用是让出执行权 ,让 Gin 继续执行后续中间件和最终控制器,然后再回到当前位置继续往下执行。
IP 过滤器中间件实现 下面我们实现一个最简单的 IP 白名单过滤器。 逻辑很简单:
通过 c.ClientIP() 获取客户端 IP
IP 是否在设定的白名单中
如果不在,则直接返回 403,并 Abort() 阻拦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func IPAuthMiddleware () gin.HandlerFunc { return func (c *gin.Context) { ipWhiteList := []string {"127.0.0.1" } flag := false clientIP := c.ClientIP() fmt.Println("clientIP" , clientIP) for _, ip := range ipWhiteList { if clientIP == ip { flag = true break } } if !flag { c.JSON(403 , gin.H{ "message" : "IP not allowed" , }) c.Abort() return } } }
本机访问正常:
虚拟机访问时被拦截:
控制台输出请求 IP:
将虚拟机 IP 加入白名单后即可恢复访问:
官方示例解析 官方示例其实很经典,准确展示了:
中间件“前置逻辑”(Next 前执行)
中间件“后置逻辑”(Next 后执行)
核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func Logger () gin.HandlerFunc { return func (c *gin.Context) { t := time.Now() c.Set("example" , "12345" ) c.Next() latency := time.Since(t) log.Print(latency) status := c.Writer.Status() log.Println(status) } }
请求:
去掉 c.Next() 后:
你会发现状态码和耗时都不正确,因为:
如果没有 Next,控制器 Handler 根本没执行,因此也没有正常返回。
这说明了 Gin 中间件的核心结构是 “洋葱模型”:
1 2 3 4 5 MiddlewareA 前 MiddlewareB 前 Handler MiddlewareB 后 MiddlewareA 后