背景

关于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")
}

image-20201016111403262

通过上边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,等他们全部执行完以后再回来执行当前中间件的后续代码。

结论

  1. 中间件代码最后即使没有调用Next()方法,后续中间件及handlers也会执行;
  2. 如果在中间件函数的非结尾调用Next()方法当前中间件剩余代码会被暂停执行,会先去执行后续中间件及handlers,等这些handlers全部执行完以后程序控制权会回到当前中间件继续执行剩余代码;
  3. 如果想提前中止当前中间件的执行应该使用return退出而不是Next()方法;
  4. 如果想中断剩余中间件及handlers应该使用Abort方法,但需要注意当前中间件的剩余代码会继续执行。

参考

https://segmentfault.com/q/1010000020256918

https://www.cnblogs.com/yjf512/p/9670990.html