最简GET写法是http.Get,但必须defer resp.Body.Close();POST需用http.NewRequest+Client.Do;超时须用带Timeout的http.Client;JSON操作要检查marshal/unmarshal错误并正确处理Body。

用 net/http 发起 GET 请求最简写法
Go 标准库不通过 net 包发 HTTP 请求,而是用 net/http。直接调用 http.Get 是最快上手方式,它内部封装了连接、请求头、状态码处理等细节。
注意:http.Get 返回的 *http.Response 必须手动关闭 Body,否则会持续占用连接和内存。
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close() // 关键:必须调用
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
POST 请求要手动构造 *http.Request
http.Post 虽然存在,但只支持固定 Content-Type(如 application/x-www-form-urlencoded),灵活性差。真正可控的方式是用 http.NewRequest + http.DefaultClient.Do。
-
http.Post无法设置自定义 Header(比如Authorization) -
http.NewRequest允许你精确控制 Method、URL、Body、Header、Timeout - 务必检查
resp.StatusCode,http.Do不会因 4xx/5xx 自动报错
req, _ := http.NewRequest("POST", "https://httpbin.org/post", strings.NewReader(`{"name":"go"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer abc123")
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
log.Printf("unexpected status: %d", resp.StatusCode)
}
超时控制必须用 http.Client,不能靠 time.After
仅对 http.Get 或 http.Post 包一层 time.AfterFunc 无法中断底层 TCP 连接,可能造成 goroutine 泄漏。正确做法是创建带 Timeout 的 *http.Client,或更细粒度地设置 Transport 的 DialContext 和 ResponseHeaderTimeout。
-
Client.Timeout控制整个请求生命周期(DNS + 连接 + 写请求 + 读响应头 + 读响应体) - 若需分别控制连接超时和读响应超时,需自定义
http.Transport - 零值
http.Client{}没有超时,可能无限 hang 住
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 3 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
ResponseHeaderTimeout: 2 * time.Second,
},
}JSON 请求和响应的典型错误点
用 json.Marshal 构造请求体时,容易忽略错误;用 json.Unmarshal 解析响应时,常忘记先读完 resp.Body。这两步出错都不会触发 HTTP 状态码异常,但会导致静默失败。
-
json.Marshal失败返回nil字节切片,传给bytes.NewReader后发空体 -
resp.Body是流式读取,多次调用json.NewDecoder(resp.Body).Decode()会报io.EOF - 结构体字段没加
json:tag,导致序列化/反序列化字段为空
data := struct {
Name string `json:"name"`
Age int `json:"age"`
}{"go", 15}
body, err := json.Marshal(data)
if err != nil {
log.Fatal(err) // 必须检查
}
req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
log.Fatal(err) // 必须检查
}
HTTP 请求看似简单,但超时、Body 关闭、JSON 序列化错误、状态码忽略这四点,在真实项目里几乎必踩。尤其 Body 不关,压测时连接池迅速耗尽。










