Gin框架学习

本文最后更新于:2022年2月8日 晚上

前言:之前在QQ音乐实习的时候,一直用的是TEG那边写的going框架+TME在此基础上的封装,一直没能目睹Gin框架的真容,目前IEG所在组用的是Gin,正好学习一下优秀开源框架的设计思想,故记录此文

https://gin-gonic.com/

Gin框架学习

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "github.com/gin-gonic/gin"

func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}

1. Gin Middleware

在学习项目源码的过程中,看到大量使用了中间件,比如统计某接口的耗时,进行登陆态校验,开启跨域函数等等,那么这个中间件是个什么东西?

Middleware support

An incoming HTTP request can be handled by a chain of middlewares and the final action. For example: Logger, Authorization, GZIP and finally post a message in the DB.

从Gin的官网介绍大概能得知中间件是一个可以处理HTTP请求的函数调用链,这类似与Java中的Filter过滤器或者是Spring中的Interceptor拦截器,可以统一在请求接口之前或之后执行我们定义的中间件。

1
2
3
4
5
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc

用例Demo

了解了基本的概念之后,如何使用?开发文档提供了如下Demo

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
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// Set example variable
c.Set("example", "12345")
// before request

c.Next()

// after request
latency := time.Since(t)
log.Print(latency)
// access the status we are sending
status := c.Writer.Status()
log.Println(status)
}
}

func main() {
r := gin.New()
r.Use(Logger()) // 注册中间件
r.GET("/test", func(c *gin.Context) { // 注册/test接口对应的执行方法
example := c.MustGet("example").(string)
// it would print: "12345"
log.Println(example)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
1
2
3
4
curl --location --request GET 'http://localhost:8080/test'
2022/01/26 17:38:34 12345
2022/01/26 17:38:34 140.807µs
2022/01/26 17:38:34 200

可以看到,在注册了Logger()中间件后,依次是打印了examplelatencystatus三个变量的值。

实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Use attaches a global middleware to the router. ie. the middleware attached though Use() will be
// included in the handlers chain for every single request. Even 404, 405, static files...
// For example, this is the right place for a logger or error management middleware.
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...) // 将多个中间件添加到group.Handlers也就是slice中
return group.returnObj()
}

// RouterGroup is used internally to configure router, a RouterGroup is associated with
// a prefix and an array of handlers (middleware).
type RouterGroup struct {
Handlers HandlersChain
basePath string
engine *Engine
root bool
}

可以看到,Use()将多个中间件函数注册到RouterGroup这个结构体中的HandlersChain slice中

再来看Get()是如何执行并访问到注册的接口的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath) // 绝对地址
handlers = group.combineHandlers(handlers) // 将注册的func合并到HandlersChain中
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}

const abortIndex int8 = math.MaxInt8 / 2 // =63

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers) // HandlersChain已有的HandlerFunc + 当前要添加的HandlerFunc的长度大于最大长度则panic
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers) // copy原有的HandlerFunc到新的slice mergedHandlers
copy(mergedHandlers[len(group.Handlers):], handlers) // copy新增的HandlerFunc
return mergedHandlers
}

因此,在像Demo中调用的那样之后,HandlersChain 会形成这样一个Slice,里面的HandlerFunc是依次顺序执行的。

再来看Next(),实现原理很简单,首先是index指针自增1,随后进入一个死循环,依次执行HandlersChain Slice中的下一个HandlerFunc直到最后

1
2
3
4
5
6
7
8
9
10
// Next should be used only inside middleware.
// It executes the pending handlers in the chain inside the calling handler.
// See example in GitHub.
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) { // 死循环
c.handlers[c.index](c) // 执行HandlersChain Slice中下一个HandlerFunc
c.index++
}
}

所以Demo中代码首先执行Logger(),在设置完example中的值之后调用了Next()从而执行main.main.func1,最后再回到main.Logger.func1执行3和4

image-20220126195826399

踩坑点

需要注意的是,Use()需要在Get() Post()...之类的方法之前调用,否则HandlersChain中只会有Use()添加的中间件而不会有Get() Post()...添加的接口执行函数,因为只有在handle()方法中才会进行HandlersChain的copy和append

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
30
31
32
33
34
35
36
func main() {
r := gin.New()
r.Use(TimeExpend)
r.GET("/ping", f1, process, f2, f3)
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

func TimeExpend(c *gin.Context) {
t := time.Now()
c.Next()
fmt.Println(time.Since(t))
}

func process(c *gin.Context) {
fmt.Println("process")
c.JSON(200, gin.H{
"message": "pong",
})
}

func f1(c *gin.Context) {
fmt.Println("f1")
}
func f2(c *gin.Context) {
fmt.Println("f2")
}
func f3(c *gin.Context) {
fmt.Println("f3")
}
// 执行链条 TimeExpend记录开始时间->f1->process->f2->f3->TimeExpend打印耗时
// 输出结果:
// f1
// process
// f2
// f3
// 106.441µs

image-20220126224500983

2. Gin 路由树

TODO


Gin框架学习
https://yangshuai-uestc.github.io/2022/02/08/Gin框架学习/
作者
Catsyang
发布于
2022年2月8日
许可协议