是,必须手动调用 resp.Body.Close();仅当 err == nil 且 res != nil 时才安全关闭,否则可能 panic,且不关闭会导致文件描述符泄漏和连接耗尽。

用 http.Get 发送简单 GET 请求,但别忽略响应体关闭
Go 标准库的 http.Get 看起来最省事,但实际用错会导致连接泄漏。它本质是 http.DefaultClient.Get 的封装,不自动关闭响应体(resp.Body),高并发下会耗尽文件描述符。
- 必须手动调用
resp.Body.Close(),哪怕只读状态码 - 如果需要设置超时、Header 或重试,
http.Get就不够用了,得换http.Client - 错误检查不能只看
err != nil,还要确认resp.StatusCode是否为 2xx
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() // 必须加
if resp.StatusCode != http.StatusOK {
log.Fatalf("HTTP %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
POST 表单数据用 http.PostForm,JSON 数据用 json.Marshal + http.NewRequest
http.PostForm 只适合发送 application/x-www-form-urlencoded,比如登录表单;发 JSON 必须自己构造请求体和 Header,否则服务端收不到或解析失败。
-
http.PostForm自动设Content-Type: application/x-www-form-urlencoded,参数是url.Values - 发 JSON 时,先
json.Marshal结构体,再用bytes.NewReader包装,最后显式设Content-Type: application/json - 不要用
http.Post直接传 JSON 字符串——它默认设的是text/plain,后端常拒收
// 表单提交
resp, _ := http.PostForm("https://www.php.cn/link/dc076eb055ef5f8a60a41b6195e9f329", url.Values{"name": {"go"}, "age": {"10"}})
<p>// JSON 提交
data := map[string]string{"name": "go", "version": "1.22"}
payload, <em> := json.Marshal(data)
req, </em> := http.NewRequest("POST", "<a href="https://www.php.cn/link/dc076eb055ef5f8a60a41b6195e9f329">https://www.php.cn/link/dc076eb055ef5f8a60a41b6195e9f329</a>", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
</p>
自定义超时和重试必须用 http.Client,别依赖全局 DefaultClient
http.DefaultClient 的超时是无限的,且无法单独配置。生产环境必须构造自己的 http.Client 实例,否则一次卡死就拖垮整个服务。
-
Timeout控制整个请求生命周期(DNS + 连接 + 写入 + 读取),不是仅网络超时 - 需要更细粒度控制(如只限制连接时间),得用
Transport配DialContext - 重试逻辑不能靠
http.Client自带——它不重试,得自己在Do外包一层循环
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
IdleConnTimeout: 30 * time.Second,
},
}
resp, err := client.Do(req) // 注意:err 是底层错误,resp 可能非 nil 但 StatusCode 异常
中文参数或路径要 url.PathEscape 和 url.QueryEscape,别直接拼字符串
URL 中出现中文、空格、斜杠等字符,不转义会导致请求 400 或路由匹配失败。Go 不像 Python 的 requests 那样自动处理,必须手动转义。
- 路径段(path segment)用
url.PathEscape,比如/user/张三→/user/%E5%BC%A0%E4%B8%89 - 查询参数(query string)用
url.QueryEscape,比如q=hello world→q=hello+world - 千万别用
fmt.Sprintf拼接 URL,尤其是带变量时,容易漏转义或双编码
base := "https://api.example.com/search"
q := url.QueryEscape("Go语言入门")
path := url.PathEscape("用户中心")
fullURL := fmt.Sprintf("%s?keyword=%s§ion=%s", base, q, path)
// 更安全的做法是用 url.URL 结构体 + url.Values 构建
真正麻烦的不是写几行请求代码,而是错误处理粒度、超时边界、编码一致性这些细节。一个没关的 Body,一次没转义的中文路径,或者一个没设超时的 DefaultClient,上线后都可能变成深夜告警。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











