go http客户端需注意url编码、json请求体封装、重定向header透传及超时设置:url参数须用url.values.encode;post json需bytes.newreader包装并设content-type;重定向需自定义checkredirect透传关键header;client必须设timeout或transport级超时。

Go HTTP客户端发起GET请求时,URL参数没编码导致400错误
Go的http.Get不会自动对URL中的查询参数做编码,传入含空格、中文或特殊符号的url.Values拼接字符串,服务端很可能直接返回400 Bad Request。
正确做法是用url.Parse + url.Values.Encode生成安全查询串:
u, _ := url.Parse("https://api.example.com/search")
q := url.Values{}
q.Set("q", "hello world") // 包含空格
q.Set("tag", "golang+net/http")
u.RawQuery = q.Encode()
resp, err := http.Get(u.String())- 别手拼
"?q=" + query,也不要用fmt.Sprintf硬塞 -
url.Values.Encode()会把空格转成+,中文转成%E4%BD%A0等标准格式 - 如果后端要求空格必须为
%20(而非+),得手动替换:strings.ReplaceAll(q.Encode(), "+", "%20")
Post JSON数据时Content-Type和Body处理不一致,服务端收不到body
常见错误是只设了Content-Type: application/json,但http.Post第二个参数写错,或json.Marshal后没用bytes.NewReader包装。
推荐统一用http.NewRequest构造,可控性更强:
立即学习“go语言免费学习笔记(深入)”;
data := map[string]string{"name": "alice", "role": "dev"}
payload, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", "https://api.example.com/users", bytes.NewReader(payload))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)-
http.Post(url, contentType, body)里body必须是io.Reader,json.Marshal返回的是[]byte,得包一层bytes.NewReader() - 如果漏掉
req.Header.Set("Content-Type", ...),某些API会拒绝解析JSON - 不要用
strings.NewReader(string(payload)),UTF-8字节序列转string再转回[]byte可能破坏二进制数据
自定义Header在重定向后丢失,登录态失效
默认http.Client遇到302跳转时,会丢弃原始请求的Authorization、X-API-Key等自定义Header,只保留User-Agent等少数几个。
必须显式配置CheckRedirect函数来透传关键Header:
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// 把上一个请求的Authorization带入重定向后的请求
if len(via) > 0 {
req.Header.Set("Authorization", via[0].Header.Get("Authorization"))
req.Header.Set("X-Request-ID", via[0].Header.Get("X-Request-ID"))
}
return nil
},
}- Go标准库默认重定向策略(
DefaultRedirectPolicy)会清空所有非安全Header - 只透传必要Header,避免泄露敏感字段(比如
Cookie一般不该跨域透传) - 如果服务端跳转到不同域名,还需确认CORS或服务端是否允许该Header跨域携带
超时没设或设错,HTTP请求卡死几十秒才失败
Go的http.DefaultClient没有默认超时,DNS失败、连接挂起、服务端不响应都会让Do()阻塞直到系统级TCP timeout(常是数分钟),线上服务极易雪崩。
必须为每个http.Client实例设置Timeout或更细粒度的Transport超时:
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second,
}
}-
Timeout是总超时,覆盖连接、TLS握手、写请求、读响应全过程 - 单独设
DialContext.Timeout可更快发现网络层问题,避免总超时被单个慢DNS拖长 - 别在每次请求前临时改
client.Timeout,它不是goroutine-safe的;应为不同场景创建不同client实例
Header设置、重定向控制、超时策略这三块,漏掉任何一环都可能让请求在生产环境表现异常——不是报错,而是“看起来正常却偶尔卡住”或“明明发了Header但对方收不到”,这种问题最难定位。










