http客户端需自定义http.client:设timeout防挂起,复用client提升性能,手动设content-type发json,必须defer resp.body.close()防连接池耗尽,transport参数慎调。

用 http.Get 发最简单的 GET 请求,但别直接用在生产环境
它确实一行就能发请求,比如 http.Get("https://api.example.com"),但默认不设超时、不复用连接、不处理重定向边界——线上服务一卡就是几十秒挂起。真正用的时候得自己包一层。
- 必须显式设置
http.Client,控制Timeout、Transport和CheckRedirect -
http.Get内部创建的默认 client 没有超时,遇到 DNS 卡住或后端失联,goroutine 就永远等下去 - 如果要发多次请求,复用同一个
http.Client实例,否则底层 TCP 连接不会复用,性能差还容易打满文件描述符
POST JSON 数据时,json.Marshal 后记得设 Content-Type: application/json
Go 的 http.Post 不会自动设头,光传个字节流进去,服务端大概率返回 415 Unsupported Media Type。常见错误是只写 http.Post(url, "application/json", body),但 body 是 *bytes.Reader 或 strings.NewReader,没设 header。
- 正确做法:用
http.NewRequest构造,手动调req.Header.Set("Content-Type", "application/json") - 别用
http.Post传 JSON,它没法设 header,除非你额外再调一次http.DefaultClient.Do(req) - 注意
json.Marshal可能返回 error,比如结构体字段没加json:tag 或含不可序列化类型(如func、chan),不检查就 panic
处理响应 Body 必须 defer resp.Body.Close(),且要在读完之后
漏关 Body 是 Go HTTP 客户端最常踩的坑,不是内存泄漏,而是连接池被占满、后续请求卡死。而且不能在 if resp.StatusCode != 200 就提前 return,否则 Body 永远没机会关。
- 不管状态码多少、是否要读 body,都得先
defer resp.Body.Close()(放在resp, err := client.Do(req)之后立刻写) - 如果只关心状态码,也要调一次
io.Copy(io.Discard, resp.Body)或ioutil.ReadAll(Go 1.19+ 用io.ReadAll),否则连接无法归还给连接池 - 用
json.NewDecoder(resp.Body).Decode(&v)时,解码失败不会自动关 body,仍需确保 defer 已生效
自定义 http.Transport 时,别乱改 MaxIdleConns 和 MaxIdleConnsPerHost
这两个值影响连接复用效率,但设太高反而压垮服务端或本地 fd 限制;设太低又失去复用意义。默认值(100/100)对多数场景够用,除非你明确知道瓶颈在哪。
- 修改前先看
netstat -an | grep :443 | wc -l,确认是不是真有大量CLOSE_WAIT或TIME_WAIT -
IdleConnTimeout建议设为 30–90 秒,太短导致频繁建连,太长让空闲连接占着资源 - 如果服务端要求 keep-alive 关闭,或者你发的是单次脚本任务,干脆把
Transport的DisableKeepAlives设为true,省得管复用逻辑
HTTP 客户端看着简单,但超时控制、连接复用、body 关闭、header 设置这四点,任一漏掉都会在流量上来时突然出问题。尤其 Body.Close,它不报错、不 panic,只悄悄拖慢整个服务。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











