
本文详解 Go 程序批量请求 Hacker News API 时出现 `EOF json` 错误的根本原因——HTTP 连接复用与服务端异常关闭的冲突,并提供两种可靠解决方案:手动设置 `Request.Close = true` 或配置 `Transport.DisableKeepAlives`,确保 JSON 解析稳定。
Go 默认使用 HTTP/1.1 的持久连接(Keep-Alive),客户端会复用 TCP 连接以提升性能。然而,Hacker News 的 API 服务器在返回响应后静默关闭连接,却未发送标准的 Connection: close 响应头。这导致 Go 的 http.Client 在后续请求中尝试复用已被对端关闭的连接,读取时触发 EOF,进而使 json.Unmarshal 失败(表现为 invalid character '\x00' looking for beginning of value 或类似 EOF 相关错误)。
✅ 推荐解决方案一:显式关闭单次请求连接
修改 get() 函数,不依赖默认客户端行为,而是构造自定义 *http.Request 并设置 req.Close = true:
func get(url string) ([]byte, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Close = true // 强制本次请求后关闭连接
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close() // 注意:务必关闭响应体
return io.ReadAll(res.Body) // 替换已弃用的 ioutil.ReadAll
}? 提示:io/ioutil.ReadAll 自 Go 1.16 起已弃用,请使用 io.ReadAll(需导入 "io" 包)。
✅ 推荐解决方案二:全局禁用 Keep-Alive(适合高频批量请求)
若程序中大量调用外部 API,可创建专用 http.Client,禁用连接复用:
var httpClient = &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true,
},
}
func get(url string) ([]byte, error) {
res, err := httpClient.Get(url)
if err != nil {
return nil, err
}
defer res.Body.Close()
return io.ReadAll(res.Body)
}该方式避免了每次构造 Request 的开销,更适合高并发场景(如配合 goroutine 启动多请求)。
⚠️ 注意事项与最佳实践
- 永远 defer res.Body.Close():防止文件描述符泄漏,这是 Go HTTP 客户端的强制要求;
- 避免共享 http.DefaultClient 于关键路径:其默认 Transport 可能被其他库修改,建议使用私有 *http.Client 实例;
- 考虑超时控制:生产环境应为请求添加上下文超时,例如 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second);
- 错误处理需健壮:原示例中循环内仅 fmt.Println(err),实际应用应记录日志或降级处理,避免单个失败阻断整体流程。
通过以上任一方案,即可彻底规避因服务端非标准行为引发的 EOF 解析错误,保障批量 HTTP 请求的稳定性与可靠性。










