controller层没有加@responsebody_为什么这段代码没有拦截到 gin String 类型的 response 返回值?...

news/2024/7/4 19:15:59

e7020a803ffaa42262945f800fe34dcb.png

1. 搬运代码, 高高兴兴

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

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

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

2. 不好, 有 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")
}

3. 解析 github 上的代码

我搬运的代码:

bbc8521522efc991579f86f4cbb7c3d6.png

代码中 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())
}

4 揪出 bug

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

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

68257c8fa7884b37cf2b767b7d96229d.png

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

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

54d65c002aa69fca99215cd9d52217c9.png

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

598cf9347555032e96f51f10073af7d3.png

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

f1a8abedc4ebafbce9c3486081e20116.png

5. 修复bug

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

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

6. github 上的那个 issue

https://github.com/gin-gonic/gin/issues/1363​github.com

http://www.niftyadmin.cn/n/1998472.html

相关文章

window 编程指南(二)

51、如何创建一个具有特定点大小的字体 可以指定字体逻辑单位的大小,但有时指定字体的点的大小可能会更方便一些。可以如下将字体的点转换为字体的高度: int nHeigthmulDiv (nPointSize, -dc.GetDeviceCaps (LOGPIXELSY), 72); 下例创建了一个8点…

Java基础技术细节总结

2019独角兽企业重金招聘Python工程师标准>>> 开发莫忘基础,写业务写多了很多基础内容容易忘。这里将寻根溯源,总结Java语言规范和基础类中的一些细节问题。所有关于Java语言规范的细节问题,都可以参考 The Java Language Specific…

django ajax 更新表格_Python随身听技术精选django精简版与python造火箭

>>>> python随身听,编程任我行 >>>>你好,欢迎回到python随身听,我是主播DE8UG,今天是周一,给你带来的是全球技术精选。我开发的编程入门神器PythonX正在测试中,之前的两期节目和公…

10分钟让你懂得基金是什么

10分钟让你懂得基金是什么 近期见到有很多网友常常提问基金是怎么回事,好象基金是个很复杂难懂的东西,又说推荐去读的基金知识文章看不懂。因此我常想如何让这些朋友在最短的时间内理解基金是什么,为大家展示一下这些并不神秘的基金&#x…

最初的感动:各种应用和代码在BCH这里不断复活

BCH就像是一个孩子,不断的捡起被core扔掉的玩具,并将其仔细擦拭,使玩具变得光彩如新。从零确认技术的重新测试使用到5月份再次分叉将op-return字节数据增加,BCH乐此不疲的恢复一些过去在BTC那里没有获得支持的想法,让这…

celery 停止任务_django - 在Django-Celery中停止/清除定期任务 - SO中文参考 - www.soinside.com...

任务是一条消息,“周期性任务”以周期性间隔发送任务消息。发送的每个任务都将分配有唯一的ID。revoke仅取消单个任务消息。要获取任务的ID,您必须保留跟踪发送的ID,但您也可以在发送任务时指定自定义ID。我不确定您是否要取消单个任务消息&a…

“马俊和黄佳婚姻”所引发的思考

江苏电视台播出的那个有关黄佳和马俊婚姻的节目,大家看了吗 首先声明:我不是针对所有叫黄佳的人。 可我看了,确实觉得那里面的那个黄佳很恶心,下面附上黄佳的博客: 我第一次看到下面的这个帖子&#xff0…

一个程序员的自白(旁观者清)

《旁观者》的文笔如此精彩,写得好像真的一样,无论对环境亦或事物的描述,都能感受得到德鲁克的专注和透析。看完这本书后,我真心觉得要真正做到“旁观者清”实属不易。没有一定的功力,凭什么根据自己所见所闻即可推断别…