DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Go Body.Close 报 EOF

Go 1.27.0 的服务端 Request.Body.Close 在部分正文排空后可能返回正常 EOF,导致清理辅助函数把请求改成失败。1.27.1 已修复,需要重新构建应用。

Go 1.27.0 的 HTTP handler 读到需要的请求内容后,调用 r.Body.Close() 却得到 EOF。如果 deferred 清理函数把 Close 错误写回处理结果,原本有效的请求也可能变成 500,客户端随后反复重试。

这条回归发生在服务端请求体的有界排空路径。Go 1.27.1 已包含修复,已核对的 1.27.2 实现也会清除这里正常结束产生的 io.EOF。它与客户端读取响应体时遇到 EOF、上传截断产生 unexpected EOF 不同。

读取停止,不一定意味着请求失败

有些解码器知道消息长度,读取需要的内容后就停止,没有继续读取到观察 io.EOF。HTTP 请求还可能带有后续数据或 trailer,handler 因而没有完全走到请求体末尾。

关闭服务端请求体时,net/http 会在受控边界内排空剩余数据,兼顾连接复用。1.27.0 的相关调整保留了排空上限,却漏掉了正常排空结束时把 io.EOF 清为 nil 的处理。

于是读取阶段没有错误,Close 却返回了 EOF。排查时要找到具体的关闭位置,尤其是 defer 中把关闭结果写回命名错误返回值的辅助函数,而不只是查看解码调用是否成功。

不要把这项行为理解成“请求体可以不校验”。正文、认证、长度与业务输入仍按原规则验证;真正的读取错误和不完整上传不能被忽略。

更新构建工具链,再检查实际程序

记录服务使用的实际构建版本,而不只看本机安装的 Go。标准库代码已经编进应用,采用包含修复的 Go 1.27.1 或后续维护版本后,还需重新构建并按原部署流程更新程序。

保留项目的工具链最低要求、目标平台、构建参数与依赖管理。当前已核对的 1.27.2 保留相同修正,可以按项目的维护版本约定采用。

更新后,原来有效的请求应完成处理,服务端请求体的正常排空结束不再被当成 Close 错误。继续检查响应状态、客户端重试与连接复用,不只验证进程能够启动。

不要通过无限排空或忽略全部错误绕过

强制读取任意剩余正文直到结束,可能让服务继续等待慢上传,或处理超出原边界的数据。保留既有的请求体大小、读取时间和访问控制限制,不应为避开这条 EOF 一次性放宽它们。

同样不要把所有 Body.Close() 错误统一忽略。上传被截断、底层连接出错或自定义请求体返回异常,都可能需要不同的处理;这项修复只清除特定排空分支中的正常 EOF。

需要对照时,使用不写业务数据的受控 handler 或隔离样例,比较正文读取结果和 Close 结果。不要靠重放真实分片上传、订单或其他写入请求来观察 500。

此前发生过重试时,还需核对请求是否已经产生业务效果,再决定恢复方式。响应报错不保证业务写入不存在,不能直接重试整批操作。

保留失败堆栈、关闭辅助函数与原构建配置。若维护版本更新后仍报错,继续查实际运行产物和具体错误来源,不把所有 EOF 都归到已经修复的服务端排空路径。