必须手动关闭http.Response.Body,否则导致连接复用失败和文件描述符泄漏;应defer resp.Body.Close(),且置于检查StatusCode之后;小响应用ioutil.ReadAll,大响应用io.Copy。

Go中http.Response.Body必须手动关闭
不关闭会导致连接复用失败、文件描述符泄漏,尤其在高频请求或长连接场景下容易触发 too many open files 错误。Go 的 http.Client 默认启用连接池,但不会替你关 Body。
-
Body是一个io.ReadCloser,用完必须调用Close() - 即使只读取部分数据,也得关;哪怕遇到
io.EOF或解析失败,也不能跳过Close() - 推荐用
defer resp.Body.Close(),但注意:要放在检查resp.StatusCode之后,否则可能提前关闭导致错误处理逻辑拿不到响应体
用ioutil.ReadAll还是io.Copy?看数据规模和用途
小响应(如 JSON API 返回,通常
- 读小响应:用
io.ReadAll(resp.Body)(Go 1.16+ 推荐,ioutil.ReadAll已弃用) - 读大响应:用
io.Copy(dst, resp.Body),比如写入文件或管道 - 若需解析 JSON 又怕内存爆,可用
json.NewDecoder(resp.Body)直接解码,它内部按需读取,不全载入内存
JSON响应解析常见坑:resp.Body只能读一次
一旦调用 io.ReadAll 或 json.NewDecoder.Decode,Body 就被消费完毕。重复读会得到空数据或 io.EOF。
- 调试时想打印原始响应?先
bytes.Buffer中转一下:
buf := new(bytes.Buffer) buf.ReadFrom(resp.Body) bodyBytes := buf.Bytes() // 现在可以多次使用 bodyBytes,也可以 json.Unmarshal(bodyBytes, &v) // 别忘了:resp.Body 已被读完,不能再传给 json.NewDecoder
- 想同时记录日志又解析?要么缓存字节切片,要么用
io.TeeReader把读取过程“镜像”到io.Writer - 注意结构体字段首字母必须大写(导出),否则
json.Unmarshal不会赋值
处理非UTF-8编码的响应(如 GBK、ISO-8859-1)
Go 标准库默认把 Body 当作 UTF-8 处理,如果响应头声明了 Content-Type: text/html; charset=gbk,但没做转换,中文会乱码。
- 先从
resp.Header.Get("Content-Type")提取 charset,再用golang.org/x/text/encoding包解码 - 简单场景可硬编码判断:
strings.Contains(ct, "gbk")→ 用simplifiedchinese.GBK.NewDecoder().Bytes(data) - 别依赖
http.Response自动解码 —— 它不做字符集转换,只负责传输字节
最易忽略的是:很多老接口返回 charset=gb2312 或无声明但实际是 GBK,不显式转码就直接 string(bodyBytes),结果全是问号或方块。










