背景
关于Gin
中间件Context.Next()
的用途我之前一直认为就是用在当前中间件的最后,用来把控制权还给Context
让其它的中间件能执行,反过来说就是如果没有这一句其它的中间件就不能执行,翻阅这篇文章发现不止我有同样的想法,直到今天遇到跨域问题需要为此写一个中间件,于是找到了cors这个项目,在翻阅源码的过程中意外发现代码中竟然没有c.Next()
环节,顿时有了疑虑,其它中间件和handler
怎么执行?经过仔细确认发现确实没有后,感觉事情并不简单可能超出认知了于是就有了这篇文章。
还是一样先实践验证后分析原理,既然cors可以不用c.Next()
,那我们就来写个小demo验证下如果没有调用Next()
后续中间件到底能不能执行?
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
|
package main
import (
"github.com/gin-gonic/gin"
"log"
)
func midOne() gin.HandlerFunc {
return func(c *gin.Context) {
log.Println("midOne")
}
}
func midTwo() gin.HandlerFunc {
return func(c *gin.Context) {
log.Println("midTwo")
}
}
func main() {
r := gin.New()
r.Use(midOne())
r.Use(midTwo())
r.GET("/ping", func(c *gin.Context) {
c.JSON(200,"ok")
})
r.Run(":8080")
}
|
通过上边demo可以看到中间件中最后虽然没有调用Next()
但其它中间件及handlers
也都执行了。接下来我们来结合源码分析下原理。
原理
Gin
中最终处理请求的逻辑是在engine.handleHTTPRequest()
这个函数
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 (engine *Engine) handleHTTPRequest(c *Context) {
// ...
// Find root of the tree for the given HTTP method
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next() //执行handlers
c.writermem.WriteHeaderNow()
return
}
// ...
}
break
}
// ...
}
|
其中c.Next()
是关键
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) //执行handler
c.index++
}
}
|
从Next()
方法我们可以看到它会遍历执行全部handlers
(中间件也是handler
),所以中间件中调不调用Next()
方法并不会影响后续中间件的执行。
既然中间件中没有Next()
不影响后续中间件的执行,那么在当前中间件中调用c.Next()
的作用又是什么呢?
通过Next()
函数的逻辑也能很清晰的得出结论:在当前中间件中调用c.Next()
时会中断当前中间件中后续的逻辑,转而执行后续的中间件和handlers,等他们全部执行完以后再回来执行当前中间件的后续代码。
结论
- 中间件代码最后即使没有调用
Next()
方法,后续中间件及handlers
也会执行;
- 如果在中间件函数的非结尾调用
Next()
方法当前中间件剩余代码会被暂停执行,会先去执行后续中间件及handlers
,等这些handlers
全部执行完以后程序控制权会回到当前中间件继续执行剩余代码;
- 如果想提前中止当前中间件的执行应该使用
return
退出而不是Next()
方法;
- 如果想中断剩余中间件及handlers应该使用
Abort
方法,但需要注意当前中间件的剩余代码会继续执行。
参考
https://segmentfault.com/q/1010000020256918
https://www.cnblogs.com/yjf512/p/9670990.html