不能每次新建http.Client,因其绕过连接池导致TCP连接和TLS握手无法复用、超时重试失效;应全局复用并配置Transport与Timeout,配合context.WithTimeout控制请求生命周期,且必须关闭resp.Body以释放连接。

Go 写 REST 客户端,关键不是选库,而是控制好 http.Client 生命周期和请求上下文。
为什么不能每次 new http.Client()
新建 http.Client 看似简单,但会绕过连接池,导致:
- 每请求都新建 TCP 连接,耗尽文件描述符(常见错误:
too many open files) - 无法复用 TLS 会话,握手开销翻倍
- 超时、重试、Keep-Alive 全部失效
正确做法是全局复用一个 http.Client 实例,并显式配置 Timeout 和 Transport:
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 30 * time.Second,
},
}如何用 context 控制请求生命周期
不带 context 的请求无法主动取消,超时后 goroutine 可能卡在读响应体上。必须用 context.WithTimeout 或 context.WithCancel 包裹:
立即学习“go语言免费学习笔记(深入)”;
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)- 调用
req.WithContext(ctx),再传给client.Do(req) - 记得
defer cancel(),避免 context 泄漏
错误示例:client.Get("https://api.example.com") —— 没有上下文,没超时,没取消能力。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
第三方 API 密钥和敏感配置怎么管理
硬编码密钥或写死在代码里,等于把钥匙贴在门上。必须:
- 从环境变量读取,比如
os.Getenv("API_KEY") - 不提交到 Git:确保
.gitignore包含.env和配置文件 - 生产环境用 secret manager(如 HashiCorp Vault、AWS Secrets Manager),而非本地文件
尤其注意:HTML 模板中渲染第三方返回的数据,必须用 template.HTMLEscapeString 或自动转义机制,否则可能触发 XSS。
测试时如何避免真实 HTTP 调用
单元测试跑真实网络请求,慢、不稳定、难断言。应该把 HTTP 传输逻辑抽象成接口:
type HTTPTransport interface {
Do(req *http.Request) (*http.Response, error)
}然后注入 mock 实现,比如用 httptest.Server 或 gock 拦截请求。不要测 http.Client 本身,只测你封装的业务逻辑是否正确构造了请求、解析了响应。
最容易被忽略的一点:哪怕用了 http.Client 复用和 context,如果没关闭响应体(resp.Body.Close()),连接就永远不会归还给连接池 —— 这个 bug 不报错,但压测时连接数会缓慢爬升直至耗尽。

















