2020年1月

需求场景

我需要对http请求的body体做flter处理,以及拿到request的body做验签,之后http的request body又会参与二次或者多次的使用,例如透明传递给backend做转发,流量拷贝等.

名词解释

backend: 这个backend是我需要将流量(http 请求)复制给后端server,然后拿到后端server的结果返回给前端,整个过程你可以理解为是gateway或者类似nginx的porxy
所以这里的backend指的是: 后端server或者后端server的一些特征属性组成的一个实例.

bug复现

前端同学反馈给我,说backend提供了一个post请求的接口,直接访问backend的接口是200的status,且没有respose body,但是在网关层访问返回500.

查找bug思路第一步:

第一步,赶紧找log,所以log内容如下:

net/http: HTTP/1.x transport connection broken: http: ContentLength=30 with Body length 0

从日志看来是body的内容指定了content-length,但是是空body对应一个非0值的content-length

查找bug思路第二步->误入歧途:

这里我的第一脑补是backend的结果是空导致的,是不是backend返回给我了一个空body的情况下content-length是非0?
所以我就开始直接构造请求到backend
结果是:backend 返回没有问题, 到此我开始了阅读roundtrip源代码的漫长之路,可是整个过程很复杂,涉及到异步,涉及到http 协议的底层.

查找bug思路的第三步->回归现象:

无奈,源码阅读之后并无头绪,所以重新回归现象,仔细分析

其实log中可以看出来,说明以及很明确了,一定是body内容和content-length头不一致导致的,所以这里反思之后觉得是不是请求body的问题?

一旦想到这一点,我便去构造不同的body来验证length长度,果然length长度随着请求的body不同而不同

查找bug思路第四步->初见端倪:

一定是body被某一层的代码读走了,而roundtrip依然要使用,所以开始了各个flter层的查看,最后找到罪魁祸首:

bodyString, _ := ctx.GetRawData()

果然body被拿走,而没有回写,导致roundtrip拿不到body了

解决bug:

找到原因后,解bug不足一分钟
思路:回写request body到ctx中

bodyString, _ := ctx.GetRawData()
ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer([]byte(bodyString)))

总结:

当你读io的时候。读取时,它会把它读取完。一旦你阅读它,内容就消失了。你不能再读一遍。
io.reader 接近与水龙头:你可以得到水,但一旦它出来了,它就出来了。