Gin中间件工作流程

GinGo语言中一个很流行的Web框架,其性能比较高,比较简洁,中间件与Koa类似,采用了洋葱模型,我们来看一下它的大概执行原理。

中间件使用方式

中间件使用方式示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 自定义中间件Logger
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
// 请求处理前做一些事

c.Next() // 继续执行后边的中间件

// 请求处理后做一些事
}
}

func main() {
r := gin.New()
r.Use(Logger())

// 监听并在 0.0.0.0:8080 上启动服务
r.Run(":8080")
}

洋葱模型实现

执行流程示例

先给出一个示例,然后我们对照该实例去实现它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func Middleware1(c *gin.Context) {
fmt.Println("middleware1 before")
c.Next()
fmt.Println("middleware1 after")
}

func Middleware2(c *gin.Context) {
fmt.Println("middleware2 before")
}

func Middleware3(c *gin.Context) {
fmt.Println("middleware3 before")
c.Next()
fmt.Println("middleware3 after")
}

func HandlerFunc(c *gin.Context) {
fmt.Println("handler()")
}

r.Use(Middleware1, Middleware2, Middleware3)
r.Get("/", HandlerFunc)

可见我们定义了三个中间件和一个处理函数,在请求/时触发的流程为:

1
2
3
4
5
6
middleware1 before
middleware2 before
middleware3 before
handler()
middleware3 after
middleware1 after

符合洋葱模型的执行流程

代码实现

我们只会实现其大致流程,不关注细节。

首先是Context的定义:

1
2
3
4
5
6
7
// Context的基本定义,忽略无关属性
type Context struct {
// 该请求流程应用的所有中间件,处理函数也会收集进来
handlers []gin.HandlerFunc
// 执行到第几个中间件,注意初始化为-1
index int
}

收集流程涉及太多其他内容,不在这里赘述,我们只需要清除是先收集中间件,后将处理函数加入到handlers的结尾并手动调用Next方法。

如上示例中,handlers该有以下内容:

1
[Middleware1, Middleware2, Middleware3, HandlerFunc]

实现洋葱模型的核心就是Next()方法,我们看一下它的实现:

1
2
3
4
5
6
7
8
9
10
func (c *Context) Next() {
// 先执行一次自增,开始执行中间件
// 初始化为-1,正好自增后可以执行第一个
c.index++
// 循环调用
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}

实现就是这么简单,与Koa一样,实现方式很简洁,根据上边的示例我们可以想一下它的执行流程:

  • 执行Middleware1中的before部分
  • 执行Middleware1中的Next()
  • index++后执行Middleware2中的before部分
  • for循环继续运行执行Middleware3中的before部分
  • 执行Middleware3中的Next()
  • index++后执行HandlerFunc
  • for循环已经跳出,执行调用栈中Middleware3after部分
  • 执行调用栈中Middleware1after部分

执行流程一致。

主要有两个细节需要注意:

  1. 为什么要执行for循环?

因为不是所有的中间件都需要在请求处理函数后做一些操作,那么也就不是所有的中间件内部都会执行Next()方法。

所以需要加一个循环,即便不调用Next()也会依次执行所有中间件

  1. 执行第一个中间件调用Next开始for循环,执行后续中间件中的Next又会开始for循环,会不会导致后续的中间件重复执行?

并不会。

因为我们修改的是c.index,而c*Context,所以在所有中间件中使用的是同一个Context,其index值的修改会影响到所有中间件,并不会重复执行。

作者

胡兆磊

发布于

2023-09-03

更新于

2024-04-03

许可协议