golang web框架——gin使用教程(二)
gin使用教程(二)
上篇教程主要讲了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") } 复制代码