需要写一个中间件来投递请求日志,为了不影响响应效率,使用了协程方式,逻辑代码如下:
// 日志投递
func AddLogToOpdbLog() gin.HandlerFunc {
return func(c *gin.Context) {
go func() {
token := c.DefaultQuery("token", "")// 投递日志操作}()c.Next()}
}
首先,此中间放在最后的位置,确保最后被执行。
我使用的是postman发起的请求,使用的是form-data
,参数是一样的,就是连续多点几次。
然而在测试的过程中,发现,偶尔无法从c
中获取到请求参数。
将请求方式改成x-www-form-urlencoded
,也是一样的。
从逻辑上来讲,中间件中的c
已经是内核对参数解析完之后了,不存在还没来得及解析的情况,那么,可能的情况是,此goroutine是在此请求已经被处理完,且上下文已经被释放了之后才被调起的。
要测试也很简单,修改代码:
func AddLogToOpdbLog() gin.HandlerFunc {
return func(c *gin.Context) {
go func() {
time.Sleep(time.Second*3)token := c.DefaultQuery("token", "")// 其他参数// 投递日志操作}()c.Next()}
}
发现,每次都能获取到参数,纳尼,是我理解错了?,难道c
在响应完之后不会被框架主动释放掉吗?的确是这样的,它依赖于gc来回收。
使用副本来测试。
func AddLogToOpdbLog() gin.HandlerFunc {
return func(c *gin.Context) {
cp := c.Copy()go func() {
token := cp.DefaultQuery("token", "")// 其他参数// 投递日志操作}()c.Next()}
}
偶尔会报如下错误。
[GIN-debug] error on parse multipart form array: multipart: NextPart: EOF
这个错误是context.go
中,显然是解析数据时出错了。
func (c *Context) getFormCache() {
if c.formCache == nil {
c.formCache = make(url.Values)req := c.Requestif err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
if err != http.ErrNotMultipart {
debugPrint("error on parse multipart form array: %v", err)}}c.formCache = req.PostForm}
}
将请求方式改成x-www-form-urlencoded
,就不会报这种错了。
但是,即便使用c.Copy()
也没有解决获取不到参数的问题。
扯了这么多,本质问题,其实就是多协程的情况下,并发的读写map,因为c.Request.Form
和c.Request.PostForm
都是map结构,因此,你或许还能看到map争抢的panic,因为gin内部没有给他们加锁。
综上所述,gin 的 context 应该是在协程环境下共享数据出现了点问题。
解决方式:
将获取来自c
的参数,放在协程外面,这样就是同步了,就不会出现上面的问题了。
func AddLogToOpdbLog() gin.HandlerFunc {
return func(c *gin.Context) {
token := cp.DefaultQuery("token", "")// 其他参数go func() {
// 投递日志操作}()c.Next()}
}