Go的http.Header大小写不敏感,键名自动规范化为首字母大写格式(如content-type→Content-Type),Get()方法内部自动转换更安全,而直接map访问需用规范键名;多值头如Set-Cookie必须用Header["Key"]切片遍历获取全部值。

Header字段大小写不敏感,但底层键名是规范化的
Go 的 http.Header 内部会把所有键名转成首字母大写、其余小写的格式(如 content-type → Content-Type),所以用 resp.Header.Get("content-type") 和 resp.Header.Get("CONTENT-TYPE") 都能取到值。这不是字符串匹配,而是 map key 的规范化映射。
但注意:直接用 resp.Header["Content-Type"] 这种切片访问时,必须用规范化后的 key;而 Get() 方法内部做了转换,更安全。
- 别在代码里硬写
"content-type"当 key 去查切片,容易漏掉 - 调试时打印
for k := range resp.Header看到的全是规范形式,不是原始请求里的写法 - 自定义 Header 也建议用
req.Header.Set("X-Request-ID", ...)而非req.Header.Set("x-request-id", ...),避免后续逻辑误判
多值 Header(如 Set-Cookie)必须用切片访问,不能只靠 Get()
resp.Header.Get("Set-Cookie") 只返回第一个值,而实际响应中可能有多个 Set-Cookie 字段——比如同时设 session 和 csrf token。如果只取第一个,会丢掉后续 Cookie。
正确做法是直接读切片:resp.Header["Set-Cookie"],它返回 []string,遍历即可:
立即学习“go语言免费学习笔记(深入)”;
cookies := resp.Header["Set-Cookie"]
for _, cookie := range cookies {
// 解析 Domain/Path/Expires 等属性
}
-
Get()适合Content-Type、Location这类标准单值头 -
Header.Values("Cache-Control")并不存在——http.Header没这个方法,得自己遍历或封装 - 某些代理或 CDN 会合并重复 Header(如多个
Cache-Control),但 Go 不做合并,原样保留所有值
缓存 Header 解析结果需考虑请求上下文唯一性
Header 解析本身开销极小,但如果你在中间件里反复调用 r.Header.Get("Authorization") 或解析 X-Forwarded-For,没必要缓存字符串结果——除非你做了复杂计算(如 JWT 解析、IP 段校验)。
真正要小心的是“缓存整个 *http.Request 对象”或复用 r.Header 引用:
-
r.Header是指针,多个 goroutine 同时读写会 panic,别把它塞进全局 map - 如果用
context.WithValue(r.Context(), key, r.Header.Get("X-Trace-ID")),没问题;但存r.Header本身就不安全 - HTTP/2 多路复用下,同一个
*http.Request实例可能被复用,Header 内容也可能被修改(如某些中间件重写)
Cache-Control 生效的前提是响应里没 Set-Cookie
只要响应头里存在 Set-Cookie,无论你是否设置了 Cache-Control: public, max-age=3600,CDN 和浏览器都会忽略缓存指令,强制视为 private 响应。
这不是 Go 的 bug,是 HTTP 规范要求。常见翻车点:
- 登录后首页渲染 HTML,顺手调了
http.SetCookie(w, ...),结果整页都不缓存 - 用
http.FileServer返回静态资源,但 handler 里提前写了Set-Cookie,导致缓存失效 - 想缓存 API 响应,却忘了中间件(如 auth)悄悄加了
Set-Cookie
真正可控的缓存,得从响应头组合开始判断:有没有 Set-Cookie、Vary 是否包含敏感字段、Cache-Control 是否带 private 或 no-store ——这些都得在写 header 前就定好策略,而不是事后补。


















