登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go http.Request Body 请求体关闭应该由谁负责

来源:17golang原创

时间:2026-09-11 15:44:12 387浏览 收藏

在 Go 的 HTTP 服务端代码里,r.Body.Close() 经常被当成“必须写的清理动作”。结论其实更具体:对于由 net/http Server 交给 Handler 的服务端请求,Handler 不需要负责关闭 http.Request.Body;Server 会在请求处理结束时关闭它。如果代码是在客户端读取 resp.Body,则责任完全相反,调用方必须关闭响应体。

官方文档:https://pkg.go.dev/net/http

要点速览
  • 服务端 Handler 重点是读取、限制大小和处理错误,不必对入站 r.Body 重复关闭。
  • 客户端 http.Client.Do 返回的 resp.Body 必须由调用方关闭,最好在确认错误为空后立即 defer
  • 只有在自定义接管、包装或提前终止读取时,才需要重新确认 Close 的所有权和连接复用影响。

服务端请求体为什么不用 Handler 关闭

http.Request.Body 的语义会随请求方向变化。服务端请求的 Body 由 HTTP Server 管理,官方字段说明直接指出 Server 会关闭请求体,ServeHTTP Handler 不需要关闭它。Handler 的正常职责是把需要的字节读出来,然后返回响应。

Go net/http 服务端请求体由 HTTP Server 管理并在 Handler 生命周期结束后收尾的静态关系图
图1:服务端入站请求中,Handler 读取请求体,关闭责任仍由 HTTP Server 持有。

这不代表 Body 永远不会被关闭,而是关闭动作不应被当作 Handler 的业务模板。Server 在处理完成后会根据是否读完、是否提前关闭以及剩余数据量决定收尾方式。提前关闭一个尚未读完的请求体,可能让当前连接不能继续复用;因此“我已经读够了”与“我应该主动 Close”不是同一个判断。

Handler 里应该写什么:读取、限流和错误处理

一个更稳妥的服务端写法是限制可读大小,再把读取错误转换成业务响应。下面的 io.LimitReader 只控制业务读取范围,不把请求体的关闭责任转移给 Handler。

func decodeCreate(w http.ResponseWriter, r *http.Request) {
	// 限制业务最多读取 1 MiB,避免无界读取占用内存。
	limited := io.LimitReader(r.Body, 1

如果需要拒绝超大请求,可以用 http.MaxBytesReader 包住 r.Body,让读取阶段更早得到大小错误。这个包装是为了表达读取策略,不等于把普通服务端 Body 的最终生命周期改成由业务代码管理。

最容易写反的地方:客户端必须关闭 Response.Body

当 Go 代码作为客户端发请求时,req.Body 由 Transport 负责关闭;但收到响应后,resp.Body 由调用方负责。关闭前尽量读取到 EOF,Transport 才更有机会复用 HTTP/1.x 的连接。

Go HTTP 客户端请求体和响应体分别由 Transport 与调用方负责关闭的静态关系图
图2:客户端方向上,Transport 处理请求体,调用方读取并关闭响应体。
resp, err := client.Do(req)
if err != nil {
	// 请求未得到可用响应时,直接处理错误。
	return err
}
// 客户端拿到响应后立即登记清理责任,避免连接资源长期占用。
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
	// 读取失败仍会执行上面的 Close。
	return err
}
return handleResponse(body)
场景谁负责 Close代码重点
服务端 Handler 的 r.BodyHTTP Server读取、限流、处理解析错误
客户端的 req.BodyTransport由客户端 Transport 发送并收尾
客户端的 resp.Body调用方检查 err 后立即 defer Close
自定义包装 Reader看包装协议与所有权确认谁持有底层 Close

提前结束读取时,先判断是否真的要接管

中间件可能把 Body 替换成一个自定义的 io.ReadCloser,测试代码也可能传入自己创建的 Reader。这时不能只看变量名是 r.Body 就套用默认结论,应先确认这个 Reader 的创建者、底层资源和 Close 约定。若只是标准 Server 交给 Handler 的请求体,通常让 Server 统一收尾更清楚;若业务明确接管了文件、管道或连接,则应在接管点记录并执行对应清理。

还有一个常见误区:客户端代码中的 defer req.Body.Close() 并不能代替 defer resp.Body.Close()。前者最多覆盖自定义请求体的本地资源,后者才是释放响应读取资源的关键。

相关问题

服务端 Handler 写 defer r.Body.Close() 会出错吗?

通常不会立刻出错,但它是重复且容易误导的清理动作。更重要的是不要在尚未完成必要读取前随意提前关闭。

服务端不关闭 Body 会导致连接泄漏吗?

按标准 net/http Server 的生命周期不会因为 Handler 不写 Close 就泄漏;应把注意力放在读取边界、超大请求和自定义 Body 所有权上。

为什么客户端关闭响应体还建议先读完?

读取到 EOF 能让 Transport 更明确地完成本次响应消费,从而提高持久连接复用的机会;关闭动作本身仍然不能省略。

复核这类代码时,可以先问一句“这是服务端入站 Body,还是客户端出站响应 Body”,再看 Reader 是否被自定义包装。方向和所有权一旦分清,Close 应该写在哪里就不再靠记忆猜测。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>