抄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": "privationel", //}) c.String(200, "hello world")}解析 github 上的代码我搬运的代码:
抄github上的golang代码被坑后,弄懂了gin的原理文章插图
代码中 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 :=而 c.String 调用的是 gin.ResponseWriter.WriteString 输出返回值. 函数调用关系图如下:
抄github上的golang代码被坑后,弄懂了gin的原理文章插图
我把相关的源码贴上, 再来验证一下.
c.JSON 的 Render 方法中调用了 WriteJSON, 在 WriteJSON 中调用的是 ResponseWriter.Write 方法. 源码如下图所示.
抄github上的golang代码被坑后,弄懂了gin的原理文章插图
c.String 的 Render 方法中调用了 WriteString, 在 WriteString 中调用的是 io.WriteString.
抄github上的golang代码被坑后,弄懂了gin的原理文章插图
io.WriteString 的实现, 如下图所示, 调用的是 sw.WriteString, 也就是 gin.ResponseWriter 接口中的 WriteString 方法, 而高票答案中没有实现这个方法.
抄github上的golang代码被坑后,弄懂了gin的原理文章插图
修复bug理解了以上内容, 修复 bug 也非常简单, 只需要重写一下 WriteString 方法就可以了:
func (r responseBodyWriter) WriteString(s string) (n int, err error){ r.body.WriteString(s) return r.ResponseWriter.WriteString(s)}补充【抄github上的golang代码被坑后,弄懂了gin的原理】查看扩展链接, 可以看到 github 上的那个 issue.