必须在http.defaultclient.do(req)前通过req.header.set()或add()设置,否则请求已冻结失效;set覆盖同名头,add追加多值;host、content-length等由go自动管理,手动设置无效。

Go 里设请求头,不是往 client 上配,也不是传参时塞进去,而是创建 http.Request 后,立刻调用 req.Header.Set() 或 req.Header.Add() —— 晚了就发不出去,错了也不报错,只默默失效。
为什么 req.Header.Set() 看似执行了却没发出去?
最常见原因是时机不对:在 http.DefaultClient.Do(req) 之后才调用 Set,或者用了 http.Post 这类快捷函数后反向操作响应里的 req(它已是只读副本)。http.NewRequest 返回的请求对象 Header 是可写的,但一旦交给 Do() 开始发送,内部会冻结结构,后续修改无效。
- 必须在
Do()调用前完成所有 Header 设置 - 别用
http.Get()/http.Post()后试图改resp.Request.Header—— 那是历史快照,改了白改 - 调试时加一句
fmt.Printf("header: %+v\n", req.Header),比猜半天快得多
Set() 和 Add() 到底该用哪个?
Set() 覆盖同名字段,Add() 追加。对单值头(如 Authorization、Content-Type)一律用 Set();只有明确需要多个同名头时(比如多个 Cookie),才用 Add()。
-
req.Header.Set("Authorization", "Bearer abc123")—— 安全,避免残留旧 token -
req.Header.Add("Cookie", "a=1")和req.Header.Add("Cookie", "b=2")—— 生成两个 Cookie 行 - 误用
Add()设Content-Type可能导致重复头,部分服务端拒收
哪些 Header 绝对不能手动设?
Host、Content-Length、Connection、Transfer-Encoding 这些由 Go 自动管理。你设了,运行时不报错,但发出去的请求里还是被 runtime 覆盖掉。
-
Host必须靠req.URL的 host 部分决定,或直接赋值req.Host = "example.com" -
Content-Length由 body 的io.Reader实现自动计算(如bytes.Reader可读长度,strings.Reader也可推),手动设错会触发http: invalid Content-Length - 自定义头尽量用短横线风格(
X-Request-ID),别用下划线(X_Request_ID),某些代理会静默丢弃
Content-Type 和 body 类型不匹配会怎样?
后端按这个头决定怎么解包 body。设错等于主动误导服务端,结果通常是 400、空数据或降级为 raw string。
- 发 JSON:
jsonBytes, _ := json.Marshal(data)→req.Header.Set("Content-Type", "application/json")→bytes.NewReader(jsonBytes)当 body - 发表单:
bodyStr := url.Values{"key": {"val"}}.Encode()→req.Header.Set("Content-Type", "application/x-www-form-urlencoded")→strings.NewReader(bodyStr) - 漏设
Content-Type却发 JSON,很多 REST API 直接返回 415 Unsupported Media Type
Header 设置本身很简单,难的是它不报错、不警告、不提示——你得自己盯住时机、键名、值格式和 body 配套。哪怕只漏一个空格(比如 "Bearer"+token 少了中间空格),服务端就收不到凭证。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











