介绍
拦截器也叫中间件
什么是Gin拦截器
首先,我们来了解一下什么是拦截器。在Gin框架中,拦截器可以理解为一组函数,用于在处理HTTP请求之前或之后执行一些公共的逻辑。比如,在每次请求到达服务器之前,你可能想要记录请求的信息,或者进行权限校验。这时候,你可以使用Gin的拦截器来实现。
Gin拦截器的使用方法
在Gin中,你可以通过调用Use()
方法来注册一个全局的拦截器。请注意,拦截器的注册顺序很重要,因为它们会按照注册的顺序依次执行。示例如下:
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
// 在请求之前打印日志
log.Println("Request URL:", c.Request.URL)
// 继续执行后续的处理函数
c.Next()
// 在请求之后打印日志
log.Println("Response Status:", c.Writer.Status())
}
}
func main() {
r := gin.Default()
// 注册全局拦截器
r.Use(Logger())
// 设置路由和处理函数
r.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Hello, Gin!"})
})
r.Run(":8080")
}
在上述示例代码中,我们定义了一个名为Logger的拦截器。在每次请求到达服务器之前,Logger会打印出请求的URL;而在请求处理完成后,Logger会打印出响应的状态码。要注意的是,我们通过gin.Default()
方法创建了一个默认的Gin引擎,并通过调用其Use()
方法注册了Logger作为全局的拦截器。
Gin拦截器的执行流程
Gin框架的拦截器是基于洋葱模型(Onion Model)的执行流程。这也是典型的Pipeline设计模式。在洋葱模型中,请求先进入一系列的前置拦截器,然后再回到每个拦截器,执行相应处理函数,最后再回到每个拦截器,执行后置处理函数。 具体而言,当一个请求到达服务器时,Gin首先会调用所有注册的前置拦截器的处理函数。然后,Gin会调用相应的路由处理函数,处理请求的业务逻辑。最后,Gin又会调用所有注册的后置拦截器的处理函数。这样,整个流程就形成了一个环。 需要注意的是,拦截器中的c.Next()
函数是用于将请求继续传递给下一个处理函数的关键点。如果你忽略了这个调用,后续的处理函数将无法被执行,从而导致错误。
Gin拦截器的特别之处
相比于其他web框架,Gin的拦截器具有一些特别之处: 1. 高性能:Gin的拦截器是通过高效的函数调用实现的,因此它们对性能的影响非常小。 2. 异常处理:Gin提供了Abort()
函数和Aborted()
方法,用于在拦截器中检测和处理异常。你可以在拦截器中使用这两个函数来实现自定义的异常处理逻辑。 3. 分组拦截器:Gin支持将拦截器分组应用于指定的路由组。这样,你可以将不同类型的拦截器应用于不同的路由组,更好地控制业务逻辑的处理流程。
拦截器的注意事项
在使用拦截器时,请务必注意以下几点: 1. 拦截器的注册顺序非常重要,一定要按照业务逻辑的需要注册拦截器。 2. 拦截器可以读取和修改请求的上下文,但请注意不要过度依赖拦截器中的状态。因为拦截器是全局共享的,可能会被多个请求同时访问。 3. 尽量避免在拦截器中进行大量的计算或IO操作,这样会降低性能。如果有需要,可以将这些逻辑移动到处理函数中。
实现一个校验token的请求拦截器
拦截器:
/*
token格式:
"phone": phone,
"exp": time.Now().Add(time.Hour * 24).Unix(), // 设置过期时间为一天
*/
// UserKey is the key used to store user info in the context
const UserKey = "user"
// TokenIntercept Token校验拦截器
func TokenIntercept() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
claims, err := utils.ParseJwt(token) // 解析token
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, err.Error()) // 发生错误时结束本次拦截器处理
return // 结束本次请求处理
}
// 校验是否过期
exp, _ := claims.GetExpirationTime()
if !exp.After(time.Now()) {
c.AbortWithStatusJSON(http.StatusUnauthorized, "登录过期")
return
}
// 获取用户信息并存储到context中
phone, _ := claims.GetSubject()
u := user.SelectUserByPhone(phone)
c.Set(UserKey, u)
// 更新token过期时间并设置响应头
newToken, _ := utils.GenerateJwt(jwt.MapClaims{
"phone": phone,
"exp": time.Now().Add(time.Hour * 24).Unix(), // 设置过期时间为一天
})
c.Header("Authorization", newToken)
// 继续处理请求
c.Next()
}
}
测试:
func testConnect() {
r.GET(Pre+"test", intercept.TokenIntercept(), func(c *gin.Context) {
c.String(http.StatusOK, "Token校验通过")
})
}
关于Next()&Abort()
Abort()
c.Abort()
是 Gin 框架中 *gin.Context
的一个方法,它的作用是立即终止当前的中间件链处理,并阻止执行后续的中间件或路由处理器。这通常用在中间件中,当你需要基于某些条件(比如认证失败、请求参数不正确等)提前结束请求处理流程时。
以下是 c.Abort()
方法的一些关键点:
- 终止处理: 当调用
c.Abort()
后,当前正在执行的中间件会立即停止处理,并且不会执行任何后续的中间件或处理器。 - 不写入响应: 默认情况下,
c.Abort()
不会向客户端发送任何响应。如果你想在终止请求的同时发送响应,可以使用c.AbortWithStatus()
或c.AbortWithStatusJSON()
等变体。 - 保持请求上下文: 尽管处理被终止,但请求的上下文(
*gin.Context
)仍然保持有效,你可以在调用c.Abort()
后访问和修改它,例如设置响应头或状态码。 - 错误处理: 通常在调用
c.Abort()
之前,你会设置错误处理逻辑,比如记录日志、发送错误响应等。 - No-op 在路由处理器中: 如果在路由处理器中调用
c.Abort()
,它不会影响其他路由处理器的执行,但会立即终止当前处理器的执行。
Next()
在 Gin 框架中,c.Next()
是 *gin.Context
的一个方法,它用于调用中间件链中的下一个中间件或路由处理器。c.Next()
通常在中间件中使用,允许中间件在执行完自己的逻辑后,将控制权传递给下一个处理函数。
以下是 c.Next()
方法的一些关键点:
- 继续执行:
c.Next()
调用后,Gin 框架会继续执行中间件链中的下一个中间件或路由处理器。 - 执行顺序: 在中间件链中,
c.Next()
决定了执行的顺序。调用c.Next()
后,Gin 会按照定义的顺序执行下一个中间件。 - 可以在任何中间件中调用: 你可以在中间件链中的任何一个中间件里调用
c.Next()
,以继续执行链中的下一个中间件。 - 可以多次调用: 虽然通常每个中间件只调用一次
c.Next()
,但在某些情况下,你可能需要根据条件逻辑多次调用c.Next()
。 - 与
c.Abort()
相对: 当你调用c.Abort()
时,当前中间件链的执行会被终止,而c.Next()
则是用来继续执行中间件链。 - 在路由处理器中无效: 在路由处理器中调用
c.Next()
是无效的,因为路由处理器不是中间件链的一部分。