Gin - 自定义中间件

前言

在 Gin 中实现一个中间件是一件非常轻量的事情,几乎没有“生命周期钩子”这种复杂概念,也不需要学习多少新 API。只需要返回一个 gin.HandlerFunc 类型的函数即可。

得益于 Gin 的“洋葱模型”执行链路机制,我们可以利用 ctx.Abort 中断请求,也可以利用 ctx.Next 控制中间件在请求前/请求后两个阶段执行逻辑,从而轻松实现日志、鉴权、限流等能力。

本篇将基于简单示例逐步展开,帮助你真正理解 Gin 中间件的执行顺序、结构与背后的设计思路。

实现一个中间件

如前言所说,实现中间件非常简单,只需要返回一个 gin.HandlerFunc 即可。

定义中间件函数

gin.HandlerFunc 本质上就是一个函数类型,它只接收一个参数:

  • ctx *gin.Context

下面实现一个最简单的打印日志的中间件:

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 main

import (
"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 - 自定义中间件

中断和继续

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 不会再执行
    • 后续的中间件也不会执行
    • 但当前中间件本身仍然已执行

下面是实际示例截图,可以看到请求被中断,没有返回响应体:

Gin - 自定义中间件

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())

默认情况下会按顺序执行:

  1. MyLoggerMiddleware
  2. MyLoggerAfterMiddleware

如下图:

Gin - 自定义中间件

加入 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("========================")
}
}

这时执行顺序会发生变化:

  1. MyLoggerMiddleware(Next 前)
  2. MyLoggerAfterMiddleware
  3. 控制器 Handler
  4. MyLoggerMiddleware(Next 后)

如下图所示:

Gin - 自定义中间件

ctx.Next() 的作用是让出执行权,让 Gin 继续执行后续中间件和最终控制器,然后再回到当前位置继续往下执行。

IP 过滤器中间件实现

下面我们实现一个最简单的 IP 白名单过滤器。
逻辑很简单:

  1. 通过 c.ClientIP() 获取客户端 IP
  2. IP 是否在设定的白名单中
  3. 如果不在,则直接返回 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
}
}
}

本机访问正常:

Gin - 自定义中间件

虚拟机访问时被拦截:

Gin - 自定义中间件

控制台输出请求 IP:

Gin - 自定义中间件

将虚拟机 IP 加入白名单后即可恢复访问:

Gin - 自定义中间件

官方示例解析

官方示例其实很经典,准确展示了:

  • 中间件“前置逻辑”(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)
}
}

请求:

Gin - 自定义中间件

去掉 c.Next() 后:

Gin - 自定义中间件

你会发现状态码和耗时都不正确,因为:

如果没有 Next,控制器 Handler 根本没执行,因此也没有正常返回。

这说明了 Gin 中间件的核心结构是 “洋葱模型”:

1
2
3
4
5
MiddlewareA
MiddlewareB 前
Handler
MiddlewareB 后
MiddlewareA 后

Gin - 自定义中间件
https://blog.pangcy.cn/2025/11/25/后端编程相关/go/gin/Gin - 自定义中间件/
作者
子洋
发布于
2025年11月25日
许可协议