Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
定义中间件
Gin中的中间件必须是一个 gin.HandlerFunc
类型。
c.Next()
调用后续的处理函数
记录接口耗时的中间件
定义一个统计请求耗时的中间件 m1
package mainimport ("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个中间件
func main() {r := gin.Default()r.Use(m1) // 全局注册中间件函数m1// GET(relativePath string, handlers ...HandlerFuncs() IRouterr.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个中间件
package mainimport ("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() IRouterr.GET("/index", indexHandler)r.Run(":9090")
}
c.Abort()
阻止调用后续的处理函数
package mainimport ("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() IRouterr.GET("/index", indexHandler)r.Run(":9090")
}
return
func m2(c *gin.Context) {fmt.Println("m2 in...")c.Abort()returnfmt.Println("m2 out...")
}
高阶中间件
// 接受一个布尔参数 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()}}
}
func main() {r := gin.Default()r.Use(m1, m2, authMiddleware(false))// GET(relativePath string, handlers ...HandlerFuncs() IRouterr.GET("/index", indexHandler)r.Run(":9090")
}
记录响应体的中间件
有时候可能会想要记录下某些情况下返回给客户端的响应数据,这个时候就可以编写一个中间件来搞定。
type bodyLogWriter struct {gin.ResponseWriter // 嵌入gin框架ResponseWriterbody *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 库,一行代码解决前后端分离架构下的跨域问题。
注意: 该中间件需要注册在业务处理函数前面。
这个库支持各种常用的配置项,具体使用方法如下。
package mainimport ("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 hoursrouter.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()
}
当然可以简单的像下面的示例代码那样使用默认配置,允许所有的跨域请求。
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框架中,可以为每个路由添加任意数量的中间件。
为全局路由注册
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()
}
为某个路由单独注册
// 给/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
shopGroup := r.Group("/shop", StatCost())
{shopGroup.GET("/index", func(c *gin.Context) {...})...
}
写法2
shopGroup := r.Group("/shop")
shopGroup.Use(StatCost())
{shopGroup.GET("/index", func(c *gin.Context) {...})...
}
中间件注意事项
gin默认中间件
gin.Default()
默认使用了 Logger
和 Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会 recover 任何 panic。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用 gin.New()
新建一个没有任何默认中间件的路由。
gin中间件中使用 goroutine
当在中间件或 handler
中启动新的 goroutine
时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本( c.Copy()
)。