必须自定义 http.client 并设置超时、连接复用等参数,禁用 http.get;get 参数需用 url.values 编码;响应 body 要及时关闭并避免全量读入内存。

用 http.Get 发最简 GET 请求,但别直接用它处理生产流量
它确实能发请求,但默认不设超时、不复用连接、不检查状态码,线上一压就出问题。比如 http.Get("https://api.example.com") 看似干净,实际可能卡死在 DNS 解析或 TCP 握手,且永远等不到响应。
- 必须自己构造
http.Client并设Timeout,至少覆盖Timeout、KeepAlive和MaxIdleConns -
http.Get返回的*http.Response必须调用resp.Body.Close(),否则连接不会释放,很快耗尽文件描述符 - 它不自动重定向(
302),也不校验2xx状态码——404或500都会照常返回Body,得手动判断resp.StatusCode
自己配 http.Client 时,超时字段不能只设一个
Go 的 http.Client.Timeout 是“整个请求生命周期”超时,但掩盖了底层三个阶段:DNS 解析、TCP 连接、TLS 握手、服务器响应。只设它,遇到慢 DNS 或卡住的 TLS 就没法单独干预。
- 推荐用
http.Transport分开控制:DialContext控制 DNS+TCP(如3s),TLSHandshakeTimeout单独设(如5s),再用Client.Timeout做兜底(如10s) - 忽略
IdleConnTimeout和KeepAlive,长连接池会快速退化成短连接,QPS 上不去 - 若目标服务启用了 HTTP/2,确保 Go 版本 ≥ 1.8,且服务端证书有效——无效证书会导致静默失败(不是报错,而是
Do永远阻塞)
GET 带参数?别拼字符串,用 url.Values 编码
手动拼 "?q=hello world&tag=golang" 看似快,但空格、中文、斜杠全会乱码,还可能被注入恶意字符。
- 正确做法:
v := url.Values{}; v.Set("q", "hello 世界"); v.Set("tag", "go/lang"); req, _ := http.NewRequest("GET", "https://api.example.com/search?"+v.Encode(), nil) -
v.Encode()生成的是标准application/x-www-form-urlencoded格式,服务端ParseForm()才能正确读取 - 如果参数值本身含
=或&(比如 JWT 片段),手动拼接几乎必然出错;url.Values会自动转义
处理响应 Body 时,ioutil.ReadAll 已弃用,但 io.ReadAll 仍要小心
它把整个响应体读进内存,对大文件或流式接口(如 SSE、大图下载)就是内存炸弹。
- 小响应(io.ReadAll(resp.Body),但务必先检查
Content-Length头或预估大小 - 不确定大小时,用
io.Copy流式写入文件或bytes.Buffer,并设最大限制(如io.LimitReader(resp.Body, 10) - 忘记
defer resp.Body.Close()是高频崩溃点——哪怕你用io.ReadAll,也得在读完后立刻关,不能等函数退出才关
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











