转自知乎:如何在golang http服务端程序中读取2次Request Body? - 知乎

提问:

在golang http服务端程序中,我想在真正处理Request Body之前将Body中的内容记录到日志中.

实际上这一需求就是要在Request Body中读取2次数据,由于Body为`ReadCloser` 类型,读取一次之后就无法再次进行读取,就需要读取完之后对Body重新赋值来支持后续的读取操作, 网上看了大家一般都是这样实现的.

bodyBytes, _ := ioutil.ReadAll(req.Body)
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

但是这样做实际上覆盖了原来的Body,原来的Body是实现了Close方法的,这里直接对Body赋值,是否需要对原来的Body调用 Close方法?

bodyBytes, _ := ioutil.ReadAll(req.Body)
req.Body.Close()  //  这里调用Close
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

官方的代码注释里虽然写了不需要在处理函数里调用Close:Request.Body:"The Server will close the request body. The ServeHTTP Handler does not need to."

但是在处理完成之后http框架会去调用Close做一些操作,finishRequest() ,如果在处理程序里对Body做了替换,这里调用的Close就是我替换后的 NopCloser.

How to read response body twice in Golang middleware?​stackoverflow.com/questions/46948050/how-to-read-response-body-twice-in-golang-middleware正在上传…重新上传取消

里有条评论: No, the original "close" is not lost. You merely assign a new value to the exportedRequest.Bodyfield. This is not the only reference to the original body reader that needs to be closed.– iczaOct 26 '17 at 9:40

想请问下各位熟悉golang http包的大神,这里直接对Body进行覆盖不显示调用Close 到底会不会造成不好的影响!

回答:

作者:波罗学
链接:https://www.zhihu.com/question/329045911/answer/714781838
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

的确从网上没有找到为什么?

网上找不到答案,没办法,只看自己看源码了。顺着服务的启动流程找到了原本执行 reqBody.Close 的地方。在 server.go 文件中的 finishRequest 函数:

// Close the body (regardless of w.closeAfterReply) so we can
// re-use its bufio.Reader later safely.
w.reqBody.Close()

就是这段代码,注释意思是,关闭 body 是为了之后可以安全的重用 bufio.Reader。这个 bufio.Reader 在哪里可以看到,,看下 body 的定义,如下:

type body struct {
	src          io.Reader
	hdr          interface{}   // non-nil (Response or Request) value means read trailer
	r            *bufio.Reader // underlying wire-format reader for the trailer
	closing      bool          // is the connection to be closed after reading body?
	doEarlyClose bool          // whether Close should stop early

	mu         sync.Mutex // guards following, and calls to Read and Close
	sawEOF     bool
	closed     bool
	earlyClose bool   // Close called and we didn't read to the end of src
	onHitEOF   func() // if non-nil, func to call when EOF is Read
}

body 中有个 r 字段,类型是 bufio.Reader,用于从底层读取请求数据。它是请求连接传递过来的。

现在的问题是,w.reqBody 已经不是最初的那个 body 了,已经被 middleware 重置了。所以,在 finishRequest 中关闭 body 是没有用的,现在的 Body 是 NopCloser 类型,即 Close 中是空操作。要想重用 bufio.Reader,只有在 middleware 重置repBody 的时候,关闭它。


补充:

发现了个问题,如题主评论区所言,middleware 中的 req.Body 和 response 中的 reqBody 是两个变量。初期,req.Body 和 reqBody 中存放了同一个地址。但是,当 req.body = io.NoCloser 时,只是改变了 req.Body 中的指针,而 reqBody 仍旧指向原始请求的 body,故不需要在 middleware 中执行关闭。

我突然在想,是 stackoverflow 错了,还是 net/http 的历史版本中关闭的是 req.body 呢?而后来通过引进 reqBody 修复这个不算 bug 的 bug呢?

最终找到了这个提交记录,在 Go 1.6 已经不用担心这个问题了。提交记录如下:

net/http: don't panic after request if Handler sets Request.Body to nil · golang/go@b105054​github.com/golang/go/commit/b1050542c1dcff9f5902ab2745aae3ccc8340c11#diff-bf538ad0b0c9263061db13ca6d8f324e正在上传…重新上传取消

提交信息:

net/http: don't panic after request if Handler sets Request.Body to nil。

大致的意思是,不用再担心把 req.Body 设置 nil,其实也就是不用再担心重置 req.Body 了。


关于怎么读这个源码?

了解几部分就行了,一个服务启动的 serve.go,两个函数,一个是 Serve,它启动服务,另一个是 Serve 函数的最后部分, go c.serve 启动一个协程处理请求。而 finishRequest 就在 c.serve 中。找 body 继续往下追吧!不说了。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐