首先,gin的中间件是有执行顺序的,就是按照添加的顺序进行的。之前没在意,我把timeout中间件放在了最后面,导致业务一直不正常,后面debug源码总算看明白了:
源码入口:
go
func(c *gin.Context) {
finish := make(chan struct{}, 1)
panicChan := make(chan interface{}, 1)
w := c.Writer
buffer := bufPool.Get()
tw := NewWriter(w, buffer)
c.Writer = tw
buffer.Reset()
.....
}
然后这个NewWriter(w, buffer)
的实现如下:
go
func NewWriter(w gin.ResponseWriter, buf *bytes.Buffer) *Writer {
return &Writer{ResponseWriter: w, body: buf, headers: make(http.Header)}
}
他这里所做的就是把原始的writer下层一级,然后new了新的body和headers,也就是进行一层封装
这里也就意味着,假如你原来已经在body和header中写入了一些内容,在后续的代码中你就无法获取到原来的内容改了,获取到的是新的header 和body
当然,gin框架自带的中间件,肯定还是考虑得很细的,所以再最后他有把新旧header和body合并的操作:
go
dst := tw.ResponseWriter.Header()
for k, vv := range tw.Header() {
dst[k] = vv
}
if _, err := tw.ResponseWriter.Write(buffer.Bytes()); err != nil {
panic(err)
}
tw.Header()
就是新header,dst
就是旧header,用一个for循环新header中的值合并到旧header中,也就是原始的c *gin.Context
中。这里一切看起来都那么的合理是吧!
但是,这里有个bug,假设在dst
中,已经包含了keyabc
,tw.Header()
也有keyabc
,那么旧的key就会被覆盖掉。导致原header信息丢失,这就是我项目中遇到的bug!
同理,body
也是一样存在丢失的风险。
这里他就没考虑到这个问题,所以,该中间件需要放在有业务逻辑的中间件之前执行,这样才能避免出现这个bug。