上篇教程主要讲了gin的路由以及参数获取,这篇主要讲解gin的中间件。
中间件可以在我们接受到一个http请求时,在handle之前或者handle之后做一些处理。通常,在handle之前,我们可以通过中间件很方便地进行校验,如果再handle之后,我们可以对response进行一些调整。
基本用法
使用
// 创建一个不包含中间件的路由器
gin.New()
// 使用自定义中间件或者gin提供的中间件
gin.use(gin.Logger())
复制代码
代替
gin.Default()
复制代码
其实gin默认使用了Logger和Recovery两个中间件,然后也是在内部调用了New:
// gin.go
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery()) // 使用了Logger和Recovery两个中间件
return engine
}
复制代码
我们对这两个中间件做一个简单的了解:
- Logger中间件可以让我们做打印的一些自定义配置
- Recovery中间件可以让我们从崩溃中恢复
func main() {
logfile, _ := os.Create("./logs/gin.log")
// 这里将log输出到指定文件
// 注意这个配置一定要在gin.Default()之前
gin.DefaultWriter = io.MultiWriter(logfile, os.Stdout)
router := gin.Default()
// 这里分别使用两个中间件
router.Use(gin.Logger())
router.Use(gin.Recovery())
router.POST("/test", func(context *gin.Context) {
var person Person
if err := context.ShouldBind(&person); err != nil {
context.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
context.JSON(http.StatusOK, gin.H{
"success": true,
})
})
router.Run(":3000")
}
复制代码
自定义中间件
要自己实现中间件,不妨先看一下官方定义的Recovery中间件是如何实现的即可。
// recovery.go
这里只要返回一个HandlerFunc类型即可
func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter)
}
// gin.go
HandlerFunc就是一个参数为*context的函数
type HandlerFunc func(*Context)
复制代码
看懂了中间件的大概思路,那么我们自己来手动实现一个。
我们来写一个IP鉴权的中间件,假设我们的需求是只有白名单中的ip才可以访问服务器,那么我们可以这么实现:
// ipauth.go
func Auth() gin.HandlerFunc {
return func(context *gin.Context) {
// 定义ip白名单
whiteList := []string{
"127.0.0.1",
}
ip := context.ClientIP()
flag := false
for _, host := range whiteList {
if ip == host {
flag = true
break
}
}
if !flag {
context.String(http.StatusNetworkAuthenticationRequired, "your ip is not trusted: %s", ip)
context.Abort()
}
}
}
复制代码
// main.go
func main() {
router := gin.New()
router.Use(ipauth.Auth())
router.GET("/test", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"success": true,
})
})
router.Run(":3000")
}
复制代码
测试实例:
// 如果你用localhost访问ip会显示为::1。
// 导致your ip is not trusted。这是因为你的电脑开启了ipv6支持,这是ipv6下的本地回环地址的表示。
$ curl http://127.0.0.1:3000/test
{"success":true}
复制代码
// 把whiteList中的127.0.0.1改成127.0.0.2之后,我们再试一下
$ curl http://127.0.0.1:3000/test
your ip is not trusted: 127.0.0.1
复制代码
Group中使用中间件
此外,我们的中间件可以不全局使用,而只针对部分的group:
func main() {
router := gin.Default()
// 定义了group
authorized := router.Group("/auth", ipauth.Auth())
// 对上面这个group进行路由绑定
authorized.GET("/write", handle)
router.GET("/read", handle)
router.Run(":3000")
}
func handle(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"success": true,
})
}
复制代码
测试实例
$ curl http://127.0.0.1:3000/auth/write
your ip is not trusted: 127.0.0.1
$ curl http://127.0.0.1:3000/read
{"success":true}
复制代码
单个路由使用中间件
也可以只针对单个路由:
func main() {
router := gin.Default()
// 注册一个路由,使用了 middleware1,middleware2 两个中间件
router.GET("/someGet", middleware1, middleware2, handler)
// 默认绑定 :8080
router.Run()
}
func handler(c *gin.Context) {
log.Println("exec handler")
}
func middleware1(c *gin.Context) {
log.Println("exec middleware1")
//你可以写一些逻辑代码
// 执行该中间件之后的逻辑
c.Next()
}
func middleware2(c *gin.Context) {
log.Println("arrive at middleware2")
// 执行该中间件之前,先跳到流程的下一个方法
c.Next()
// 流程中的其他逻辑已经执行完了
log.Println("exec middleware2")
//你可以写一些逻辑代码
}
复制代码
可以看出,中间件的写法和路由的 Handler 几乎是一样的,只是多调用c.Next()
。正是有个c.Next()
,我们可以在中间件中控制调用逻辑的变化,看下面的 middleware2 代码。在 middleware2中,执行到 c.Next()
时,Gin 会直接跳到流程的下一个方法中,等到这个方法执行完后,才会回来接着执行 middleware2 剩下的代码。
所以请求上面注册的路由 url /someGet ,请求先到达middleware1,然后到达 middleware2,但此时 middleware2调用了 c.Next()
,所以 middleware2的代码并没有执行,而是跳到了 handler ,等 handler执行完成后,跳回到 middleware2,执行 middleware2剩下的代码。
所以我们可以在控制台上看到以下日志输出:
exec middleware1
arrive at middleware2
exec handler
exec middleware2
复制代码
在中间件中使用goroutines
在中间件或处理程序中启动新的goroutine时,你不应该使用其中的原始上下文,你必须使用只读副本(c.Copy()
)
func main() {
r := gin.Default()
r.GET("/long_async", func(c *gin.Context) {
// 创建要在goroutine中使用的副本
cCp := c.Copy()
go func() {
// simulate a long task with time.Sleep(). 5 seconds
time.Sleep(5 * time.Second)
// 这里使用你创建的副本
log.Println("Done! in path " + cCp.Request.URL.Path)
}()
})
r.Run(":3000")
}
复制代码
有疑问加站长微信联系(非本文作者)