HTTP请求失败时resp可能为nil而err非网络错误;需先判err再查StatusCode,及时Close Body并配置超时,封装错误类型,避免盲目defer关闭Body。
resp 为 nil,但 err 不一定代表网络错误Go 的 http.DefaultClient.Do() 返回两个值:resp *http.Response 和 err error。很多人误以为只要 err != nil 就说明请求没发出去,其实不然——比如服务端返回 500 或 404,err 仍为 nil,resp 存在但状态码异常。
这时候如果直接 resp.Body.Close() 前不做 resp != nil 判断,会 panic。
err:非 nil 通常表示连接失败、超时、DNS 解析失败等底层问题resp.StatusCode:即使 err == nil,也要判断是否在 200–299 范围内resp.Body 后调用 resp.Body.Close(),否则连接不会复用,容易耗尽文件描述符http.Client 配置超时,避免请求无限挂起默认的 http.DefaultClient 没有设置超时,遇到网络卡顿或服务无响应时,goroutine 会长时间阻塞。必须显式配置 Timeout 或更细粒度的 Transport 参数。
Timeout 是总超时(从发起请求到读完响应体),适合大多数场景http.Transport,例如设置 DialContext、ResponseHeaderTimeout
client := &http.Client{
Timeout: 10 * time.Second,
}原始的 error 接口太泛,无法快速判断错误性质。建议定义一个结构体,携带错误分类、原始 err、HTTP 状态码、响应体片段等信息,便于日志记录和重试策略。
net.OpError、url.Error)可归为 NetworkErr
HTTPStatusErr,应附带 resp.StatusCode 和可能的 Content-Type
DecodeErr,和 HTTP 层解耦type HTTPError struct {
Kind string
Status int
RawBody []byte
Cause error
}defer resp.Body.Close()
常见反

defer resp.Body.Close()。这会导致 body 提前关闭,后续无法读取内容,尤其是需要多次读取或流式处理时。
resp.Body 只能被读取一次,且必须全部读完(或显式 io.Copy(ioutil.Discard, ...))才能释放连接io.ReadAll 之后bytes.NewReader 包装已读内容,但要注意内存开销最易被忽略的是:当 err != nil 且 resp == nil 时,resp.Body 根本不存在,此时调用 Close() 会 panic。