Golang Gin框架中间件的用法详解
作者:景天科技苑
一、中间件的基本概念
中间件是一个函数,它在HTTP请求处理的生命周期中的某个特定点被调用,可以对请求和响应进行预处理或后处理。中间件的主要功能包括日志记录、身份验证、权限控制、跨域资源共享(CORS)、参数处理、错误处理等。
日志记录:记录请求的详细信息,如请求路径、请求体等。
身份验证:验证用户的身份,如检查令牌、Cookie等。
权限控制:确定用户是否有权访问特定的资源或执行特定的操作。
跨域资源共享(CORS):允许不同域之间的资源共享。
参数处理:提取URL中的参数或请求体中的数据。
错误处理:捕获并处理请求处理过程中的错误。
在Gin框架中,中间件必须是一个gin.HandlerFunc类型的函数。中间件函数接受一个*gin.Context参数,并可以选择返回一个gin.HandlerFunc。中间件函数的典型结构如下:
func myHandler() gin.HandlerFunc { return func(c *gin.Context) { // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值 c.Set("userSession", "userid-1") //c.Next() // 放行,默认就会放行 c.Abort() // 拦截,到这里就不会往下执行请求了 fmt.Println("HandlerFunc-info") } }
二、中间件的两个专属方法
1. ctx.Next() 继续
在程序进入中间件的时候需要先进行一些处理,然后去 执行核心业务,在执行完核心业务之后再回来执行该中间件。
2. ctx.Abort() 中断
在程序进入中间件之后我们进行了一些操作,判断该用户不满足访问这个请求的条件,这个时候我们就需要终止这个请求,不让其继续执行,这个时候就使用到了Abort
三、注册中间件
在Gin框架中,可以通过全局注册或单个路由中注册,路由组注册的方式来使用中间件。
1、定义一个我们自己的 HandlerFunc 如上
2、注册全局路由
所有的请求都会经过这里来处理
全局中间件会被应用到所有的路由上。使用r.Use()函数来注册全局中间件。
package main import ( "fmt" "github.com/gin-gonic/gin" "log" "net/http" ) // 定义一个中间件函数 func myHandler() gin.HandlerFunc { //返回一个gin.HandlerFunc函数 return func(c *gin.Context) { // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值 // func (c *Context) Set(key string, value any) c.Set("userSession", "userid-1") //c.Next() // 放行,默认就会放行 //c.Abort() // 拦截,到这里就不会往下执行请求了 可以通过Abort做判定,控制请求的走向 fmt.Println("HandlerFunc-info") } } func main() { ginServer := gin.Default() // 注册 全局的 HandlerFunc // func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes // 注意,这里面的参数是我们定义的中间件函数的执行 ginServer.Use(myHandler()) // 接收请求 ginServer.GET("/test", func(c *gin.Context) { // 从上下文取值 拿到我们在中间件中设置的值 name := c.MustGet("userSession").(string) log.Println(name) c.JSON(http.StatusOK, gin.H{ "myname": name, }) }) //再加个post请求,验证全局中间件 ginServer.POST("/test", func(c *gin.Context) { namePost := c.MustGet("userSession").(string) log.Println(namePost) c.JSON(http.StatusOK, gin.H{ "myname": namePost, }) }) err := ginServer.Run() if err != nil { return } }
get请求拿到数据
post请求也拿到数据
3、为某个路由单独注册
为单个路由注册的中间件,只能在该路由中生效
package main import ( "fmt" "github.com/gin-gonic/gin" "log" "net/http" ) // 定义一个中间件函数 func myHandler() gin.HandlerFunc { //返回一个gin.HandlerFunc函数 return func(c *gin.Context) { // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值 // func (c *Context) Set(key string, value any) c.Set("userSession", "userid-1") //c.Next() // 放行,默认就会放行 //c.Abort() // 拦截,到这里就不会往下执行请求了 可以通过Abort做判定,控制请求的走向 fmt.Println("HandlerFunc-info") } } func main() { ginServer := gin.Default() // 单路由注册中间件 ,只能在该路由中生效 ginServer.GET("/test", myHandler(), func(c *gin.Context) { // 从上下文取值 拿到我们在中间件中设置的值 name := c.MustGet("userSession").(string) log.Println(name) c.JSON(http.StatusOK, gin.H{ "myname": name, }) }) //再加个post请求,post请求没有注册中间件,中间件在该路由中不生效 ginServer.POST("/test", func(c *gin.Context) { //当没有在该路由中注册中间件时,获取中间件中的变量值,获取不到会报panic namePost := c.MustGet("userSession").(string) log.Println(namePost) c.JSON(http.StatusOK, gin.H{ "myname": namePost, }) }) err := ginServer.Run() if err != nil { return } }
当没有在该路由中注册中间件时,获取中间件中的变量值,获取不到会报panic
拿不到值报异常
4、为路由组注册中间件
路由组中间件只会被应用到该路由组下的路由上。通过创建一个路由组,并使用v1.Use()函数为该路由组注册中间件。
package main import ( "fmt" "github.com/gin-gonic/gin" "log" "net/http" ) // 定义一个中间件函数 func myHandler() gin.HandlerFunc { //返回一个gin.HandlerFunc函数 return func(c *gin.Context) { // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值 // func (c *Context) Set(key string, value any) c.Set("userSession", "userid-1") //c.Next() // 放行,默认就会放行 //c.Abort() // 拦截,到这里就不会往下执行请求了 可以通过Abort做判定,控制请求的走向 fmt.Println("HandlerFunc-info") } } func main() { ginServer := gin.Default() //创建路由组 v1 := ginServer.Group("/v1") // 创建一个名为/v1的路由组 //路由组应用中间件 v1.Use(myHandler()) // 路由组中间件 { v1.GET("/test", func(c *gin.Context) { // 从上下文取值 拿到我们在中间件中设置的值 name := c.MustGet("userSession").(string) log.Println(name) c.JSON(http.StatusOK, gin.H{ "myname": name, }) }) //再加个post请求,post请求没有注册中间件,中间件在该路由中不生效 v1.POST("/test", func(c *gin.Context) { //当没有在该路由中注册中间件时,获取中间件中的变量值,获取不到会报panic namePost := c.MustGet("userSession").(string) log.Println(namePost) c.JSON(http.StatusOK, gin.H{ "myname": namePost, }) }) } err := ginServer.Run() if err != nil { return } }
在上述代码中,我们创建了一个名为/v1的路由组,并为这个路由组注册了一个中间件。这个中间件只会被应用到/v1下的路由上。
路由组中间件也可以直接应用在Group方法中
v1 := r.Group("/test", myHandler()) { shopGroup.GET("/index", func(c *gin.Context) {...}) ... }
5、gin默认中间件
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover任何panic
。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
三、实际应用案例
以下将通过几个实际案例来演示Gin框架中间件的用法。
1. 日志记录中间件
日志记录中间件用于记录请求的详细信息,如请求路径、请求体、响应状态码等。
package middleware import ( "github.com/gin-gonic/gin" "log" "time" ) // LoggerMiddleware 是一个日志记录中间件 func LoggerMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 记录请求开始时的信息 startTime := time.Now() // 调用后续的处理函数 c.Next() // 记录请求结束时的信息 endTime := time.Now() latency := endTime.Sub(startTime) // 记录请求的IP地址和端口号 ipAddress := c.ClientIP() // 记录请求的URL requestUrl := c.Request.URL.Path // 记录请求的方法 httpMethod := c.Request.Method // 记录请求的状态码 statusCode := c.Writer.Status() // 记录日志信息 log.Printf("Request from %s to %s took %s with method %s and status code %d\n", ipAddress, requestUrl, latency, httpMethod, statusCode) } }
在main.go中使用该中间件:
package main import ( "github.com/gin-gonic/gin" "jingtian/jt_gin/middleware" "net/http" ) func main() { r := gin.Default() // 创建一个默认的Gin引擎 // 注册中间件 r.Use(middleware.LoggerMiddleware()) // 定义路由 r.GET("/test", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Hello, World!"}) }) err := r.Run(":8080") if err != nil { return } // 启动服务器 }
请求
可以看到日志中间件打印出的日志
2. 身份验证中间件
身份验证中间件用于验证用户的身份,如检查令牌、Cookie等。
package middleware import ( "fmt" "github.com/gin-gonic/gin" "net/http" ) // AuthMiddleware 是一个身份验证中间件 func AuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 假设我们使用Bearer令牌进行身份验证 token := c.GetHeader("Authorization") //查看token fmt.Println("查看token:", token) if token != "Bearer your_token_here" { //认证未通过,返回401状态码和错误消息 c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized access"}) c.Abort() return } // 调用后续的处理函数 c.Next() } }
在main.go中使用该中间件:
package main import ( "github.com/gin-gonic/gin" "jingtian/jt_gin/middleware" "net/http" ) func main() { r := gin.Default() // 创建一个默认的Gin引擎 // 注册中间件 r.Use(middleware.AuthMiddleware()) // 定义路由 r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Hello, Authenticated User!"}) }) r.Run(":8080") // 启动服务器 }
3. 限流中间件
限流中间件用于限制特定时间段内允许的请求数量,以防止服务器过载。
package main import ( "github.com/gin-gonic/gin" "time" ) var ( limiter = NewLimiter(10, 1*time.Minute) // 设置限流器,允许每分钟最多请求10次 ) // NewLimiter 创建限流器 func NewLimiter(limit int, duration time.Duration) *Limiter { return &Limiter{ limit: limit, duration: duration, timestamps: make(map[string][]int64), } } // Limiter 限流器 type Limiter struct { limit int // 限制的请求数量 duration time.Duration // 时间窗口 timestamps map[string][]int64 // 请求的时间戳 } // Middleware 限流中间件 func (l *Limiter) Middleware(c *gin.Context) { ip := c.ClientIP() // 获取客户端IP地址 // 检查请求时间戳切片是否存在 if _, ok := l.timestamps[ip]; !ok { l.timestamps[ip] = make([]int64, 0) } now := time.Now().Unix() // 当前时间戳 // 移除过期的请求时间戳 for i := 0; i < len(l.timestamps[ip]); i++ { if l.timestamps[ip][i] < now-int64(l.duration.Seconds()) { l.timestamps[ip] = append(l.timestamps[ip][:i], l.timestamps[ip][i+1:]...) i-- } } // 检查请求数量是否超过限制 if len(l.timestamps[ip]) >= l.limit { c.JSON(429, gin.H{ "message": "Too Many Requests", }) c.Abort() return } // 添加当前请求时间戳到切片 l.timestamps[ip] = append(l.timestamps[ip], now) // 继续处理请求 c.Next() } func main() { r := gin.Default() // 使用限流中间件 r.Use(limiter.Middleware) r.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "Hello World", }) }) r.Run(":8080") }
每分钟内访问超过10次,就限流了
以上就是Golang Gin框架中间件的用法详解的详细内容,更多关于Golang Gin框架中间件的资料请关注脚本之家其它相关文章!