Gin-中间件:Gin框架的作者为开发者们提供的一种机制,可以让开发者自定义请求执行的Hook函数,中间件函数(或者叫Hook函数、钩子函数)适合处理很多重复的操作的场景(比如登录认证,权限校验,数据分页,耗时统计,记录日志等) ,如果仅仅是一个页面的独有的逻辑直接放到对应的路由下的Handler函数处理即可,
定义中间件
Gin的中间件必须是gin.HandlerFunc类型(就是包含请求上下文参数*gin.Context的函数)
例如:下面的indexHandler就是一个中间件:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func indexHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
func main() {
r := gin.Default()
r.GET("/index", indexHandler)
r.Run(":9090")
}
再比如,我们还可以这样定义一个中间件:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定义一个中间件m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
}
func main() {
r := gin.Default()
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", m1, indexHandler)
r.Run(":9090")
}
c.Next()
但上面这样写中间件并没有什么实际意义,我们可以尝试写一个程序计时的功能,这里介绍一下,c.Next()可以调用后续的处理函数:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定义一个中间件m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
start := time.Now()
c.Next() //调用后续的处理函数
//c.Abort() //组织调用后续的处理函数
cost := time.Since(start)
fmt.Printf("cost: %v\n ", cost)
fmt.Println("m1 is done")
}
func main() {
r := gin.Default()
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", m1, indexHandler)
r.Run(":9090")
}
r.Use()
当然,中间件的设计初衷是为了更多的复用,比如使用Use(middle_ware...)函数进行全局注册,比如,我有几个路由,想在访问这几个路由的时候都调用m1计时器的功能:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定义一个中间件m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
start := time.Now()
c.Next() //调用后续的处理函数
//c.Abort() //组织调用后续的处理函数
cost := time.Since(start)
fmt.Printf("cost: %v\n ", cost)
fmt.Println("m1 is done")
}
func main() {
r := gin.Default()
//全局注册中间件
r.Use(m1)
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", indexHandler)
r.GET("/game", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/food", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "food",
})
})
r.GET("/ad", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "ad",
})
})
r.Run(":9090")
}
c.Abort()
这里介绍一下,c.Abort()可以剥夺所有后续的处理函数运行的权利,直接跳过去,例子如下:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定义一个中间件m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
start := time.Now()
c.Next() //调用后续的处理函数
//c.Abort() //组织调用后续的处理函数
cost := time.Since(start)
fmt.Printf("cost: %v\n ", cost)
fmt.Println("m1 is done")
}
//定义一个中间件m2
func m2(c *gin.Context) {
fmt.Println("this is middle-ware m2~")
c.Abort() //组织调用后续的处理函数
fmt.Println("m2 is done")
}
func main() {
r := gin.Default()
//全局注册中间件
r.Use(m1, m2)
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", indexHandler)
r.GET("/game", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/food", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "food",
})
})
r.GET("/ad", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "ad",
})
})
r.Run(":9090")
}
登录鉴权的中间件
再比如,我们想写一个登录鉴权的中间件,伪代码就可以写成:
//定义一个登录中间件
func authMiddleware(c *gin.Context) {
//if 是登录用户
// c.Next()
//else
// c.Abort()
}
不过,为了更方便进行一些准备工作(比如连接数据库等),通常我们更加推荐将中间件写成闭包的形式:
//定义一个登录中间件
func authMiddleware(doChck bool) gin.HandlerFunc {
//写成闭包的形式,就可以在这里进行数据库的连接,或其他的准备工作
return func(c *gin.Context) { //这里存放校验参数的逻辑
if doChck{
//if 是登录用户
// c.Next()
//else
// c.Abort()
} else {
c.Next()
}
}
}
将这部分代码纳入整体代码如下:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
//定义一个中间件m1
func m1(c *gin.Context) {
fmt.Println("this is middle-ware m1~")
start := time.Now()
c.Next() //调用后续的处理函数
//c.Abort() //组织调用后续的处理函数
cost := time.Since(start)
fmt.Printf("cost: %v\n ", cost)
fmt.Println("m1 is done")
}
//定义一个中间件m2
func m2(c *gin.Context) {
fmt.Println("this is middle-ware m2~")
c.Abort() //组织调用后续的处理函数
fmt.Println("m2 is done")
}
//定义一个登录中间件
func authMiddleware(doChck bool) gin.HandlerFunc {
//写成闭包的形式,就可以在这里进行数据库的连接,或其他的准备工作
return func(c *gin.Context) { //这里存放校验参数的逻辑
if doChck {
//if 是登录用户
// c.Next()
//else
// c.Abort()
} else {
c.Next()
}
}
}
func main() {
r := gin.Default()
//全局注册中间件
r.Use(m1, m2, authMiddleware(true))
//GET(relativePath string, handlers ...HandlerFunc)
r.GET("/index", indexHandler)
r.GET("/game", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "shop",
})
})
r.GET("/food", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "food",
})
})
r.GET("/ad", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "ad",
})
})
r.Run(":9090")
}
当然,路由组也可以进行全局注册,例如:
billGroup := r.Group("/bill")
billGroup.Use(authMiddleware(true))
{
billGroup.GET("/index1", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "lokaloka1"})
})
billGroup.GET("/index2", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "lokaloka2"})
})
billGroup.GET("/index3", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg": "lokaloka3"})
})
}
中间件之间通信 c.Set()与c.Get()
比如在中间件m2中设置一个键值对,想在后面的indexHandler中间件中根据key取得value,可以这样写:
//定义一个中间件m2
func m2(c *gin.Context) {
fmt.Println("this is middle-ware m2~")
//c.Abort() //组织调用后续的处理函数
c.Set("name", "lokays")
fmt.Println("m2 is done")
}
func indexHandler(c *gin.Context) {
fmt.Println("this is index")
name, ok := c.Get("name")
if !ok {
name = "匿名用户"
}
c.JSON(http.StatusOK, gin.H{
"msg": name,
})
}
注意
注意:gin.Default()
会自动加载Logger中间件和Recovery中间件:
- Logger中间件会将日志写入gin.DefaultWriter,无论是否配置了GIN_Mode
- Recovery中间件会Recover所有的panic,如果有panic会写入500响应码
所以,如果不想使用任何中间件, 可以用gin.New()新建一个路由。
- 在中间件或者Handler中启动新的goroutine的时候,不能使用上下文的c *gin.Context,只能使用只读副本c.Copy()
参考:bilibili