最简get请求用http.get,但需手动关闭resp.body且无超时;生产环境应使用自定义http.client配置timeout和transport以避免连接泄漏与超时问题。

怎么用 http.Get 发起最简 GET 请求
直接调用 http.Get 是最快的方式,但它不支持自定义 header、超时或重定向控制,适合调试或内部轻量调用。
常见错误是忽略响应体关闭,导致连接泄漏;或者没检查 err 就直接读 resp.Body,遇到 4xx/5xx 时程序 panic。
-
http.Get默认不限制超时,网络卡住会一直挂起,生产环境必须避免 - 必须手动调用
resp.Body.Close(),否则底层 TCP 连接不会复用 - 返回的
*http.Response中resp.StatusCode不等于 200 时,resp.Body仍可能有内容(比如错误详情),别直接跳过
示例:
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() // 关键:必须 defer
<p>body, _ := io.ReadAll(resp.Body)
fmt.Printf("status: %d, body: %s", resp.StatusCode, string(body))
</p>
怎么用 http.Client 控制超时和 header
真正可控的 HTTP 调用必须自己构造 *http.Client,尤其是设置 Timeout——Go 的 net/http 默认不设超时,这是线上服务最常踩的坑。
注意 http.Client 是可复用的,不该每次请求都 new 一个;它本身是并发安全的,适合全局复用。
-
Client.Timeout是整个请求生命周期上限(DNS + 连接 + 写请求 + 读响应),不是单个阶段的超时 - 如果需要更细粒度控制(比如只限制连接时间),得用
Transport配置DialContext和ResponseHeaderTimeout - 设置 header 要在
http.NewRequest之后、Client.Do之前,用req.Header.Set();http.Get没法设 header
示例:
client := &http.Client{
Timeout: 5 * time.Second,
}
req, _ := http.NewRequest("GET", "https://httpbin.org/get", nil)
req.Header.Set("User-Agent", "my-app/1.0")
<p>resp, err := client.Do(req)
if err != nil {
log.Fatal(err) // 可能是 timeout、connection refused、TLS handshake failed...
}
defer resp.Body.Close()
</p>
POST 表单数据该用 url.Values 还是 json.Marshal
取决于后端期望的 Content-Type。表单提交(application/x-www-form-urlencoded)用 url.Values;API 接口(application/json)用 json.Marshal。混用会导致后端收不到字段或解析失败。
另一个关键点:POST 请求体是 io.Reader,不能重复读。如果要 log 请求体,得先 bytes.Buffer 缓存一份,否则 Client.Do 会读空。
-
url.Values{"key": {"value"}}.Encode()生成的是key=value字符串,自动做 URL 编码 - JSON POST 必须显式设置
req.Header.Set("Content-Type", "application/json"),否则后端可能当纯文本处理 - 不要用
strings.NewReader(jsonStr)拼接 JSON,容易漏转义或编码错乱,始终走json.Marshal
表单示例:
data := url.Values{"email": {"user@example.com"}, "token": {"abc123"}}
resp, err := http.Post("https://httpbin.org/post", "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
JSON 示例:
payload := map[string]string{"name": "Alice", "role": "dev"}
jsonBytes, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://httpbin.org/post", bytes.NewReader(jsonBytes))
req.Header.Set("Content-Type", "application/json")
<p>resp, err := http.DefaultClient.Do(req)
</p>
为什么 http.DefaultClient 在长连接场景下容易出问题
因为 http.DefaultClient 底层用的是 http.DefaultTransport,它的 MaxIdleConns 和 MaxIdleConnsPerHost 默认都是 100,但很多服务端(尤其 Nginx、云函数)会主动断开空闲连接,而 Go 客户端默认不校验连接是否还活着,下次复用时直接报 read: connection reset by peer 或 i/o timeout。
这不是代码写错了,是连接池配置和对端行为不匹配。高频调用或跨公网调用时,这个问题几乎必现。
- 解决方法是自定义
http.Transport,设IdleConnTimeout = 30 * time.Second(比服务端 keepalive 时间短) - 加上
ForceAttemptHTTP2 = true和TLSClientConfig: &tls.Config{InsecureSkipVerify: true}(仅测试环境) - 如果调用目标是云厂商 API(如 AWS、阿里云),它们文档里写的 “建议复用 client” 就是指复用带合理 transport 的 client,不是指用
DefaultClient
精简配置示例:
transport := &http.Transport{
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := &http.Client{Transport: transport}
实际写的时候,最容易被忽略的是连接池参数和响应体关闭时机——这两处不出问题则已,一出就是线上抖动或内存缓慢上涨,查起来特别费时间。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











