1. 中间件
1.1. 何为中间件?
在Gin框架中,中间件(Middleware)是一个非常重要的概念。中间件本质上是一个函数,它可以在请求到达路由处理函数之前或之后执行一些公共逻辑,例如身份验证、日志记录、错误处理等。
中间件在HTTP请求处理流程中扮演着"拦截器"的角色。当请求到达路由处理函数之前,会先经过注册的中间件进行预处理,处理完成后才会到达实际的路由处理函数。同样,在路由处理函数执行完成后,中间件还可以进行后置处理。
中间件的设计模式遵循洋葱模型,请求会依次通过每一层中间件,形成一个"外-内-外"的执行流程,即请求进入时执行中间件前半部分逻辑,最后再按相反顺序执行后半部分逻辑。
1.2. 中间件与普通处理函数的区别
在深入学习中间件之前,我们需要理解中间件和普通处理函数之间的区别:
| 特性 | 中间件 | 普通处理函数 |
|---|---|---|
| 函数签名 | gin.HandlerFunc |
gin.HandlerFunc |
| 执行时机 | 在请求到达路由处理函数前/后执行 | 作为路由的最终处理逻辑 |
| 调用方式 | 通过 c.Next() 调用链中的下一个处理器 |
直接响应客户端 |
| 主要用途 | 处理公共逻辑(如认证、日志、限流等) | 实现具体的业务逻辑 |
| 执行顺序 | 可以设置执行顺序,形成处理链 | 按路由匹配执行 |
| 响应权 | 通常不直接响应,而是调用 c.Next() |
通常直接向客户端返回响应 |
| 通用性 | 通常应用于多个路由或整个应用 | 通常针对特定路由 |
重要说明:中间件与普通处理函数本质相同
从技术上讲,中间件和普通处理函数的类型完全相同,都是 gin.HandlerFunc 类型:
go
type HandlerFunc func(*Context)
这意味着:
- 普通处理函数可以作为中间件使用
- 中间件也可以作为路由处理函数使用
- 它们的唯一区别在于使用方式和设计意图
go
// 普通处理函数
func userHandler(c *gin.Context) {
c.JSON(200, gin.H{"user": "example"})
}
// 中间件
func authMiddleware(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(401, gin.H{"error": "未提供认证令牌"})
c.Abort()
return
}
c.Set("user_id", "some_user")
c.Next()
}
// 两者可以混用
r := gin.Default()
// 将普通处理函数作为中间件使用(虽然不常见)
r.Use(userHandler) // 这样会将userHandler作为全局中间件
r.GET("/test", func(c *gin.Context) {
c.String(200, "test")
})
// 将中间件作为路由处理函数使用
r.GET("/protected", authMiddleware) // 中间件作为路由处理函数,但没有后续处理
关键区别点:
-
执行流程:普通处理函数直接处理业务逻辑并返回响应,而中间件通常在业务逻辑执行前后进行预处理或后处理。
-
控制权传递 :中间件通过调用
c.Next()将控制权传递给下一个处理器,而普通处理函数通常不调用c.Next()。 -
复用性:中间件设计用于跨多个路由复用,而普通处理函数通常只处理特定路由的逻辑。
-
中断机制 :中间件可以通过
c.Abort()中断处理链,阻止后续中间件或路由处理函数执行;普通处理函数通常不使用此机制。 -
设计意图:中间件用于处理跨切面关注点(如认证、日志、限流等),而处理函数用于实现具体业务逻辑。
2. 使用中间件
在Gin框架中,使用Use方法来使用中间件,根据作用范围的不同,可以分为三种:
- 全局中间件:中间件注册到引擎,作用于所有路由。
- 路由组中间件:中间件注册到路由组,作用于路由组中的所有路由。
- 单路由中间件:中间件注册到单个路由,只作用于当前路由。
2.1. 全局中间件
在Gin引擎上注册中间件,中间件作用于所有路由。
示例:
go
// gin引擎
engine := gin.New()
// 注册内置中间件Recovery
engine.Use(gin.Recovery())
全局中间件注册后,后续注册的所有路由都会自动应用该全局中间件。
2.2. 路由组中间件
在路由组上注册中间件,中间件作用于路由组中的所有路由:
go
// 可以在路由组定义时直接设置路由组中间件
paramRouter := router.Group("/param", middleware.AuthMiddleware)
// 也可以在路由组定义后,再用Use方法设置路由组中间件
paramRouter := router.Group("/param")
paramRouter.Use(middleware.AuthMiddleware)
路由组中间件注册后,该路由组后续注册的所有路由都会自动应用该中间件。
2.3. 单个路由中间件
调用注册路由的方法是,处理函数的入参都是可变参数,一般前面的处理作为中间件,该中间件只作用于当前路由。
从这里也能看出来,处理函数和中间件本质上是相同的。
go
// 路由定义时,注册中间件
testRouter.Any("/ping", middleware.AuthMiddleware, handler.Ping)
3. 内置中间件介绍
Gin框架为我们提供了一些内置的中间件,我们开发时可以根据需要自行选择使用。
3.1. 默认中间件
当你使用 gin.Default() 创建引擎时,Gin会自动加载两个默认中间件:
- Logger:Logger中间件用于记录HTTP请求日志,包括请求方法、路径、状态码和处理时间等信息。
- Recovery:Recovery中间件用于从panic中恢复,防止程序崩溃,并返回500错误:。
go
r := gin.Default() // 自动加载 Logger 和 Recovery 中间件
r := gin.New() // 不会自动加载 Logger 和 Recovery 中间件
3.2 其他内置中间件
除了默认的两个中间件外,Gin框架还提供了其他的内置中间件供我们使用,部分中间件如下:
- BasicAuth:HTTP基础认证中间件,用于简单的用户名密码认证。
- Bind:绑定请求体到结构体,自动解析 JSON、XML 等格式的数据。
4. 自定义中间件
4.1. 自定义中间件基础
中间件本质上是一个gin.HandlerFunc方法,自定义中间件就是自定义一个gin.HandlerFunc方法。
go
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
要正确定义中间件,必须以下几个方法:
Next:调用下一个处理器或中间件。Abort:中断请求处理链,不再执行后续中间件和处理器。Set、Get:,Set 和 Get 方法是 gin.Context 接口提供的用于在请求生命周期内存储和获取数据的方法,主要用于中间件之间或中间件与处理器之间的数据传递。
4.2. 中间件基础结构
自定义中间件的基本结构如下:
go
func CustomMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 某些异常条件下可能需要中止后续调用
if condition {
c.Abort()
return
}
// 可能需要拿前面中间件的数据
c.Get()
// 可能需要向后面中间件或处理器传递数据
c.Set()
// 调用下一个中间件或路由处理函数
c.Next()
// 在请求处理后执行的逻辑
}
}
4.3. 实际示例:权限控制中间件
go
package main
import (
"github.com/gin-gonic/gin"
"strings"
)
// 权限控制中间件
func PermissionMiddleware(permissions []string) gin.HandlerFunc {
return func(c *gin.Context) {
// 从请求头获取用户权限(实际应用中可能从JWT或session中获取)
userPermissions := c.GetHeader("X-User-Permissions")
userPermList := strings.Split(userPermissions, ",")
// 检查用户是否拥有必需权限
hasPermission := false
for _, reqPerm := range permissions {
for _, userPerm := range userPermList {
if strings.TrimSpace(userPerm) == reqPerm {
hasPermission = true
break
}
}
if hasPermission {
break
}
}
if !hasPermission {
c.JSON(403, gin.H{
"error": "权限不足",
"required_permissions": permissions,
})
c.Abort()
return
}
// 设置用户权限到上下文
c.Set("user_permissions", userPermList)
c.Next()
}
}
func main() {
r := gin.Default()
// 需要admin权限的路由
admin := r.Group("/admin", PermissionMiddleware([]string{"admin"}))
{
admin.GET("/users", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "用户列表"})
})
admin.POST("/users", func(c *gin.Context) {
c.JSON(201, gin.H{"message": "创建用户"})
})
}
// 需要read权限的路由
r.GET("/data", PermissionMiddleware([]string{"read"}), func(c *gin.Context) {
c.JSON(200, gin.H{"data": "只读数据"})
})
r.Run(":8080")
}
4.4. 实际示例:请求限流中间件
go
package main
import (
"sync"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
// 限流中间件
type IPRateLimiter struct {
ips map[string]*rate.Limiter
mtx sync.RWMutex
r rate.Limit
b int
}
func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
i := &IPRateLimiter{
ips: make(map[string]*rate.Limiter),
r: r,
b: b,
}
return i
}
func (i *IPRateLimiter) AddIP(ip string) *rate.Limiter {
i.mtx.Lock()
defer i.mtx.Unlock()
limiter := rate.NewLimiter(i.r, i.b)
i.ips[ip] = limiter
return limiter
}
func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
// 先尝试使用读锁获取限流器
i.mtx.RLock()
limiter, exists := i.ips[ip]
i.mtx.RUnlock()
if !exists {
// 如果不存在,则添加新的限流器
return i.AddIP(ip)
}
return limiter
}
func RateLimitMiddleware(r rate.Limit, b int) gin.HandlerFunc {
limiter := NewIPRateLimiter(r, b)
return func(c *gin.Context) {
clientIP := c.ClientIP()
ipLimiter := limiter.GetLimiter(clientIP) // 修复:重命名局部变量
if !ipLimiter.Allow() { // 现在正确引用了限流器的 Allow 方法
c.JSON(429, gin.H{"error": "请求过于频繁,请稍后再试"})
c.Abort()
return
}
c.Next()
}
}
func main() {
r := gin.Default()
// 限制每个IP每秒最多10个请求,突发容量为20
r.Use(RateLimitMiddleware(10, 20))
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Hello World"})
})
r.Run(":8080")
}
5. 中间件执行顺序
Gin中间件遵循洋葱模型(Onion Model)的执行顺序。当请求进入时,会依次通过各个中间件,到达路由处理函数后,再按相反顺序返回。
go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
// 注册多个中间件,注意顺序
r.Use(Middleware1(), Middleware2(), Middleware3())
r.GET("/", func(c *gin.Context) {
fmt.Println("执行路由处理函数")
c.String(200, "Hello World")
})
r.Run(":8080")
}
func Middleware1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("Middleware1 - 请求前")
c.Next()
fmt.Println("Middleware1 - 请求后")
}
}
func Middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("Middleware2 - 请求前")
c.Next()
fmt.Println("Middleware2 - 请求后")
}
}
func Middleware3() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("Middleware3 - 请求前")
c.Next()
fmt.Println("Middleware3 - 请求后")
}
}
执行顺序为:
- Middleware1 - 请求前
- Middleware2 - 请求前
- Middleware3 - 请求前
- 执行路由处理函数
- Middleware3 - 请求后
- Middleware2 - 请求后
- Middleware1 - 请求后