Gin框架学习(五)- 路由&&中间件
Gin路由
普通路由
1 2 3
| r.GET("/index", func(c *gin.Context) {...}) r.GET("/login", func(c *gin.Context) {...}) r.POST("/login", func(c *gin.Context) {...})
|
此外,还有一个可以匹配所有请求方法的Any
方法如下:
1
| r.Any("/test", func(c *gin.Context) {...})
|
为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html
页面。
1 2 3
| r.NoRoute(func(c *gin.Context) { c.HTML(http.StatusNotFound, "views/404.html", nil) })
|
路由组
我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}
包裹同组的路由,这只是为了看着清晰,你用不用{}
包裹功能上没什么区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func main() { r := gin.Default() userGroup := r.Group("/user") { userGroup.GET("/index", func(c *gin.Context) {...}) userGroup.GET("/login", func(c *gin.Context) {...}) userGroup.POST("/login", func(c *gin.Context) {...})
} shopGroup := r.Group("/shop") { shopGroup.GET("/index", func(c *gin.Context) {...}) shopGroup.GET("/cart", func(c *gin.Context) {...}) shopGroup.POST("/checkout", func(c *gin.Context) {...}) } r.Run() }
|
路由组也是支持嵌套的,例如:
1 2 3 4 5 6 7 8 9
| shopGroup := r.Group("/shop") { shopGroup.GET("/index", func(c *gin.Context) {...}) shopGroup.GET("/cart", func(c *gin.Context) {...}) shopGroup.POST("/checkout", func(c *gin.Context) {...}) xx := shopGroup.Group("xx") xx.GET("/oo", func(c *gin.Context) {...}) }
|
通常我们将路由分组用在划分业务逻辑或划分API版本时。
路由原理
Gin框架中的路由使用的是httprouter
这个库。
其基本原理就是构造一个路由地址的前缀树。
Gin中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
定义中间件
Gin中的中间件必须是一个gin.HandlerFunc
类型。
记录接口耗时的中间件
例如我们像下面的代码一样定义一个统计请求耗时的中间件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func StatCost() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Set("name", "小王子") c.Next() cost := time.Since(start) log.Println(cost) } }
|
记录响应体的中间件
我们有时候可能会想要记录下某些情况下返回给客户端的响应数据,这个时候就可以编写一个中间件来搞定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| type bodyLogWriter struct { gin.ResponseWriter body *bytes.Buffer }
func (w bodyLogWriter) Write(b []byte) (int, error) { w.body.Write(b) return w.ResponseWriter.Write(b) }
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
库,一行代码解决前后端分离架构下的跨域问题。 该中间件需要注册在业务处理函数前面。
这个库支持各种常用的配置项,具体使用方法如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package main
import ( "time"
"github.com/gin-contrib/cors" "github.com/gin-gonic/gin" )
func main() { router := gin.Default() 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() }
|
当然你可以简单的像下面的示例代码那样使用默认配置,允许所有的跨域请求。
1 2 3 4 5 6 7 8 9
| func main() { router := gin.Default() router.Use(cors.Default()) router.Run() }
|
注册中间件
在gin框架中,我们可以为每个路由添加任意数量的中间件。
为全局路由注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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() }
|
为某个路由单独注册
1 2 3 4 5 6 7 8
| 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:
1 2 3 4 5
| shopGroup := r.Group("/shop", StatCost()) { shopGroup.GET("/index", func(c *gin.Context) {...}) ... }
|
写法2:
1 2 3 4 5 6
| 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()
)。