Gin中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件

Gin中的中间件必须是一个 gin.HandlerFunc 类型。

c.Next()

调用后续的处理函数

记录接口耗时的中间件

定义一个统计请求耗时的中间件 m1

go 复制代码
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"time"
)

// HandlerFunc
func indexHandler(c *gin.Context) {
	fmt.Println("index")
	c.JSON(http.StatusOK, gin.H{
		"msg": "index",
	})
}

// 定义一个中间件:统计请求处理函数的耗时
func m1(c *gin.Context) {
	fmt.Println("m1 in...")
	// 计时
	start := time.Now()
	c.Next()
	cost := time.Since(start)
	fmt.Printf("cost:%v\n", cost)
	fmt.Println("m1 out...")
}

func main() {
	r := gin.Default()
	// GET(relativePath string, handlers ...HandlerFuncs) IRouter 
	r.GET("/index", m1, indexHandler)

	r.Run(":9090")
}


全局注册1个中间件

go 复制代码
func main() {
	r := gin.Default()
	r.Use(m1) // 全局注册中间件函数m1

	// GET(relativePath string, handlers ...HandlerFuncs() IRouter
	r.GET("/index", indexHandler)
	r.GET("/shop", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "shop",
		})
	})
	r.GET("/user", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"msg": "user",
		})
	})
	r.Run(":9090")
}




全局注册2个中间件


go 复制代码
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"time"
)

// HandlerFunc
func indexHandler(c *gin.Context) {
	fmt.Println("index")
	c.JSON(http.StatusOK, gin.H{
		"msg": "index",
	})
}

// 定义一个中间件:统计请求处理函数的耗时
func m1(c *gin.Context) {
	fmt.Println("m1 in...")
	// 计时
	start := time.Now()
	c.Next() // 调用后续的处理函数
	//c.Abort() // 阻止调用后续的处理函数
	cost := time.Since(start)
	fmt.Printf("cost:%v\n", cost)
	fmt.Println("m1 out...")
}

func m2(c *gin.Context) {
	fmt.Println("m2 in...")
	c.Next()
	fmt.Println("m2 out...")
}

func main() {
	r := gin.Default()
	r.Use(m1, m2) // 全局注册中间件函数m1, m2

	// GET(relativePath string, handlers ...HandlerFuncs() IRouter
	r.GET("/index", indexHandler)
	r.Run(":9090")
}


c.Abort()

阻止调用后续的处理函数

go 复制代码
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"net/http"
	"time"
)

// HandlerFunc
func indexHandler(c *gin.Context) {
	fmt.Println("index")
	c.JSON(http.StatusOK, gin.H{
		"msg": "index",
	})
}

// 定义一个中间件:统计请求处理函数的耗时
func m1(c *gin.Context) {
	fmt.Println("m1 in...")
	// 计时
	start := time.Now()
	c.Next() // 调用后续的处理函数
	//c.Abort() // 阻止调用后续的处理函数
	cost := time.Since(start)
	fmt.Printf("cost:%v\n", cost)
	fmt.Println("m1 out...")
}

func m2(c *gin.Context) {
	fmt.Println("m2 in...")
	c.Abort()
	fmt.Println("m2 out...")
}

func main() {
	r := gin.Default()
	r.Use(m1, m2) // 全局注册中间件函数m1, m2

	// GET(relativePath string, handlers ...HandlerFuncs() IRouter
	r.GET("/index", indexHandler)
	r.Run(":9090")
}

return

go 复制代码
func m2(c *gin.Context) {
	fmt.Println("m2 in...")
	c.Abort()
	return
	fmt.Println("m2 out...")
}

高阶中间件

go 复制代码
// 接受一个布尔参数 doCheck 来决定是否进行检查
// 如果 doCheck 为 true,可以在中间件中添加检查
// 如果为 false,则直接调用 c.Next(),继续请求处理
// 使得可以灵活控制是否需要验证
func authMiddleware(doCheck bool) gin.HandlerFunc {
	// 连接数据库
	// 或者一些其他准备工作
	return func(c *gin.Context) {
		if doCheck {
			// 是否登录的判断
			// if 是登录用户
			// c.Next()
			// else
			// c.Abort()
		} else {
			c.Next()
		}
	}
}
go 复制代码
func main() {
	r := gin.Default()
	
	r.Use(m1, m2, authMiddleware(false))

	// GET(relativePath string, handlers ...HandlerFuncs() IRouter
	r.GET("/index", indexHandler)
	r.Run(":9090")
}


记录响应体的中间件

有时候可能会想要记录下某些情况下返回给客户端的响应数据,这个时候就可以编写一个中间件来搞定。

go 复制代码
type bodyLogWriter struct {
	gin.ResponseWriter               // 嵌入gin框架ResponseWriter
	body               *bytes.Buffer // 我们记录用的response
}

// Write 写入响应体数据
func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)                  // 我们记录一份
	return w.ResponseWriter.Write(b) // 真正写入响应
}

// ginBodyLogMiddleware 一个记录返回给客户端响应体的中间件
// https://stackoverflow.com/questions/38501325/how-to-log-response-body-in-gin
func ginBodyLogMiddleware(c *gin.Context) {
	blw := &bodyLogWriter{body: bytes.NewBuffer([]byte{}), ResponseWriter: c.Writer}
	c.Writer = blw // 使用我们自定义的类型替换默认的

	c.Next() // 执行业务逻辑

	fmt.Println("Response body: " + blw.body.String()) // 事后按需记录返回的响应
}

跨域中间件cors

推荐使用社区的https://github.com/gin-contrib/cors 库,一行代码解决前后端分离架构下的跨域问题。

注意: 该中间件需要注册在业务处理函数前面。

这个库支持各种常用的配置项,具体使用方法如下。

go 复制代码
package main

import (
  "time"

  "github.com/gin-contrib/cors"
  "github.com/gin-gonic/gin"
)

func main() {
  router := gin.Default()
  // CORS for https://foo.com and https://github.com origins, allowing:
  // - PUT and PATCH methods
  // - Origin header
  // - Credentials share
  // - Preflight requests cached for 12 hours
  router.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://foo.com"},  // 允许跨域发来请求的网站
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE",  "OPTIONS"},  // 允许的请求方法
    AllowHeaders:     []string{"Origin", "Authorization", "Content-Type"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    AllowOriginFunc: func(origin string) bool {  // 自定义过滤源站的方法
      return origin == "https://github.com"
    },
    MaxAge: 12 * time.Hour,
  }))
  router.Run()
}

当然可以简单的像下面的示例代码那样使用默认配置,允许所有的跨域请求。

go 复制代码
func main() {
  router := gin.Default()
  // same as
  // config := cors.DefaultConfig()
  // config.AllowAllOrigins = true
  // router.Use(cors.New(config))
  router.Use(cors.Default())
  router.Run()
}

注册中间件

在gin框架中,可以为每个路由添加任意数量的中间件。

为全局路由注册

go 复制代码
func main() {
	// 新建一个没有任何默认中间件的路由
	r := gin.New()
	// 注册一个全局中间件
	r.Use(StatCost())
	
	r.GET("/test", func(c *gin.Context) {
		name := c.MustGet("name").(string) // 从上下文取值
		log.Println(name)
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello world!",
		})
	})
	r.Run()
}

为某个路由单独注册

go 复制代码
// 给/test2路由单独注册中间件(可注册多个)
	r.GET("/test2", StatCost(), func(c *gin.Context) {
		name := c.MustGet("name").(string) // 从上下文取值
		log.Println(name)
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello world!",
		})
	})

为路由组注册中间件

为路由组注册中间件有以下两种写法。

写法1

go 复制代码
shopGroup := r.Group("/shop", StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

写法2

go 复制代码
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{
    shopGroup.GET("/index", func(c *gin.Context) {...})
    ...
}

中间件注意事项

gin默认中间件

gin.Default() 默认使用了 LoggerRecovery 中间件,其中:

  • Logger 中间件将日志写入 gin.DefaultWriter,即使配置了 GIN_MODE=release
  • Recovery 中间件会 recover 任何 panic。如果有panic的话,会写入500响应码。

如果不想使用上面两个默认的中间件,可以使用 gin.New() 新建一个没有任何默认中间件的路由。

gin中间件中使用 goroutine

当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本( c.Copy() )。

相关推荐
阿昌喜欢吃黄桃3 天前
RocketMq事务消息原理
java·中间件·消息队列·rocketmq·mq
半夜修仙4 天前
延迟队列的介绍及常见问题
java·数据库·中间件·rabbitmq
手握风云-4 天前
一条消息的旅程:RabbitMQ 学习与实践(一)
中间件·rabbitmq
RH2312114 天前
2026.6.8Linux
java·数据库·中间件
理人综艺好会5 天前
双Token机制在实际项目中的应用与实践
中间件·token
番茄去哪了6 天前
神领物流面试题(一)
java·大数据·中间件
念何架构之路6 天前
消息中间件
中间件
都说名字长不会被发现6 天前
Spring Boot Starter 中间件账号密码加密方案设计与实现
java·spring boot·后端·中间件
瀚高PG实验室6 天前
java中间件无法连接数据库
java·数据库·中间件·瀚高数据库
之歆7 天前
Day11_Express 深入解析:从中间件到项目实战
中间件·express