七叶笔记 » golang编程 » 抄github上的golang代码被坑后,弄懂了gin的原理

抄github上的golang代码被坑后,弄懂了gin的原理

搬运代码, 高高兴兴

gin 是 Golang 中很火的 Web 框架. 最近我有一个拦截 gin response 返回值并记录日志的需求.

显然使用 gin 的 middleware 来实现最合适. 我搜索后发现 github 上 gin issue 中有人给出了相关实现,还有好几个赞, 于是乎我就高高兴兴的把代码抄下来了.

当时我也测了一下, 发现没毛病, 便稍加改动上线了 …

不好, 有 bug 了!

结果上线不到一天, 我就发现问题了, 咦, 怎么有的 response 返回值没有输出呢?

经过一番探索, 这个 bug 在我本地复现了. 上面代码中函数 sayHello 调用 c.JSON 来响应, 如果改为调用 c.String 也就是response 返回值为一个字符串, 那么 logResponseBody 这个 middleware 函数就 hook 不到 response 返回值了.

 // sayHello 这样改动后, 
// logResponseBody 这个 middleware 函数
// 就 hook 不到 response 返回值了
func sayHello(c *gin.Context) {
//c.JSON(200, gin.H{
//"hello": "privat io nel",
//})
c.String(200, "hello world")
}
  

解析 github 上的代码

我搬运的代码:

代码中 responseBodyWriter 结构体 实现的是 gin.ResponseWriter 接口.

注意一下其中的 Write 方法, 这个方法把 response 返回值缓存到 responseBodyWriter 结构体的 body 属性中, 后面会用来输出 response 返回值.

我对相关代码, 加了注释:

 func (r responseBodyWriter) Write(b [] byte ) (int, error) {
  // b 就是 response
  // Write 方法把 response 缓存到 responseBodyWriter 结构体的 body 属性中
r.body.Write(b)
return r.ResponseWriter.Write(b)
}
  

最终 logResponseBody 函数负责打印 response 返回值, 结合相关注释理解下:

 func logResponseBody(c *gin.Context) {
w := &responseBodyWriter{body: & bytes .Buffer{}, ResponseWriter: c.Writer}
  // 使用 responseBodyWriter 替换 gin 中的 responseWriter,
  // 替换的目的是把 response 返回值缓存起来
c.Writer = w
c.Next()
  // 打印 response 的返回值
fmt.Println("Response body: " + w.body.String())
}
  

搞懂 gin response 返回值的原理, 揪出 bug

如果你看懂了上文, 那么这个 bug 也就不难找了.

c.JSON , c.String 等 response 方法都实现的 Render 包中的 Render 接口. 不同的是, 在实现 Render 接口的 Render 方法, c.JSON 调用了 gin.ResponseWriter.Write 方法输出返回值; 而 c.String 调用的是 gin.ResponseWriter.WriteString 输出返回值. 函数调用 关系图如下:

我把相关的源码贴上, 再来验证一下.

c.JSON 的 Render 方法中调用了 WriteJSON, 在 WriteJSON 中调用的是 ResponseWriter.Write 方法. 源码如下图所示.

c.String 的 Render 方法中调用了 WriteString, 在 WriteString 中调用的是 io.WriteString.

io.WriteString 的实现, 如下图所示, 调用的是 sw.WriteString, 也就是 gin.ResponseWriter 接口中的 WriteString 方法, 而高票答案中没有实现这个方法.

修复bug

理解了以上内容, 修复 bug 也非常简单, 只需要重写一下 WriteString 方法就可以了:

 func (r responseBodyWriter) WriteString(s string) (n int, err error)  {
r.body.WriteString(s)
return r.ResponseWriter.WriteString(s)
}
  

补充

查看扩展链接, 可以看到 github 上的那个 issue.

相关文章