不能直接用http.defaultclient,因为它在go 1.19前timeout无效,transport各阶段超时(dialcontext、tlshandshaketimeout等)默认为0,导致dns卡住、tls协商失败或服务端不发header时goroutine永久阻塞;且共用transport引发域名解析失败连锁阻塞、maxidleconns=0致连接不复用、文件句柄打满;重试必须在调用层实现,每次需新建*http.request并正确处理body、context和连接池参数。

为什么不能直接用 http.DefaultClient
它不是“能用”,而是“一上线就出问题”。http.DefaultClient 的 Timeout 字段在 Go 1.19 之前根本不起作用,底层 http.Transport 的 DialContext、TLSHandshakeTimeout、ResponseHeaderTimeout 全是 0,意味着 DNS 卡住、TLS 握手失败、服务端不发 header,你的 goroutine 就永远挂在那里。
更麻烦的是所有请求共用同一个 Transport,一个域名解析失败,后续所有请求都会被阻塞;连接池默认关闭(MaxIdleConns=0),HTTP/1.1 根本不复用连接,短时间高频调用直接打满文件句柄。
http.Transport 必须显式配置的四个参数
封装的核心不在 http.Client,而在它底下的 http.Transport。漏掉任何一个,都可能在线上突然超时或泄漏连接:
-
MaxIdleConns和MaxIdleConnsPerHost都得设成非零值(比如100),否则连接永不复用 -
IdleConnTimeout设为30 * time.Second,防止服务端主动断连后 client 还傻等 -
TLSHandshakeTimeout必须设(如10 * time.Second),否则 TLS 协商失败会无限卡住 -
Proxy推荐用http.ProxyFromEnvironment,内网环境没配代理会导致所有请求超时
示例片段:
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
Proxy: http.ProxyFromEnvironment,
}
重试不能写在 http.Client 里,必须在调用层做
http.Client 本身不支持重试,而且 req.Body 是单次读取的——重试时如果复用同一个 *http.Request,第二次 client.Do() 会 panic。
- 每次重试必须调用
http.NewRequest新建请求 - 只对幂等方法(
GET、HEAD)或明确可重试的状态码(502、503、504)重试,POST默认不重试 - 用
context.WithTimeout控制单次请求,不是给整个 client 设 timeout - 重试间隔建议指数退避(
100ms → 200ms → 400ms),避免下游雪崩
别忽略 resp.Body.Close() 和上下文传递
漏关 resp.Body 不只是内存泄漏,还会导致连接无法归还到连接池,MaxIdleConns 形同虚设。同时,所有对外请求必须带 context.Context,否则无法在超时或取消时及时中断底层 TCP 连接。
- 必须在
defer resp.Body.Close()前确认resp非 nil - 用
http.NewRequestWithContext(ctx, ...)替代http.NewRequest - 不要把
context.Background()直接传给下游,应从上游继承或用context.WithTimeout显式控制
真正容易翻车的点,往往不在“怎么发请求”,而在于“请求没发出去时,程序还在等什么”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











