
本文详解如何在 Go 中安全、完整地读取 POST 请求的 HTTP 响应体(包括 4xx/5xx 错误状态下的 JSON 错误信息),避免因忽略 resp.Body 导致响应内容丢失或连接复用异常。
本文详解如何在 go 中安全、完整地读取 post 请求的 http 响应体(包括 4xx/5xx 错误状态下的 json 错误信息),避免因忽略 resp.body 导致响应内容丢失或连接复用异常。
在调用 RESTful API 的 POST 方法时,许多开发者误以为只有 2xx 成功状态才需要读取响应体,而对 401 Unauthorized 等错误响应直接忽略 resp.Body。这不仅会导致关键错误信息(如示例中的 "Login Required")无法解析,还可能引发 HTTP 连接池阻塞、内存泄漏或后续请求超时等问题——因为 Go 的 http.Client 要求无论状态码如何,都必须显式关闭并读取响应体,否则底层连接无法被复用。
以下是修正后的完整实现,包含错误处理、资源清理和 JSON 解析示例:
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
func postUrl(url string, byt []byte) (map[string]interface{}, error) {
tr := &http.Transport{
DisableCompression: true,
}
client := &http.Client{
Transport: tr,
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("POST", url, bytes.NewBuffer(byt))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("X-Custom-Header", "myvalue")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Basic "+basicAuth("username", "password"))
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close() // ✅ 关键:始终关闭 Body,无论状态码
// ✅ 关键:始终读取响应体(即使是错误状态)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
// 可选:根据状态码区分处理逻辑
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal success response: %w", err)
}
return result, nil
} else {
// 错误响应同样解析为 map,便于提取 errorMessages 等字段
var errResp struct {
ErrorMessages []string `json:"errorMessages"`
Errors map[string]string `json:"errors"`
}
if err := json.Unmarshal(body, &errResp); err != nil {
return nil, fmt.Errorf("failed to unmarshal error response: %w", err)
}
return map[string]interface{}{
"status_code": resp.StatusCode,
"error_messages": errResp.ErrorMessages,
"errors": errResp.Errors,
}, nil
}
}
func basicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}⚠️ 重要注意事项:
-
永远不要省略
defer resp.Body.Close():即使请求失败或状态码异常,也必须关闭Body,否则连接将滞留,最终耗尽连接池; -
必须调用
io.ReadAll(resp.Body)或等效读取操作:仅关闭而不读取,仍可能导致连接无法复用(尤其在Keep-Alive启用时); -
错误响应也含有效业务信息:如
401返回的errorMessages是调试和用户提示的关键依据,不应被丢弃; -
推荐使用
io.ReadAll替代已弃用的ioutil.ReadAll(Go 1.16+); - 若响应体较大,可考虑流式解析(如
json.Decoder),但需确保完整消费Body。
通过以上实践,你不仅能稳定获取任意状态码下的 JSON 内容,还能构建健壮、可维护的 API 客户端逻辑。


















