c.Header() 设置自定义 Header 最直接,需在响应方法前调用,会自动标准化 key 大小写,避免中间件修改 c.Request.Header,建议封装复用逻辑。

用 c.Header() 设置自定义 Header 最直接
绝大多数场景下,直接调用 c.Header("X-Request-ID", "abc123") 就能生效。它底层调用的是 http.ResponseWriter.Header().Set(),在写响应前都有效。
注意两点:
- 必须在
c.JSON()、c.String()等响应方法调用之前设置,否则 header 已被写出,再设无效 - 如果用了
c.Data()或c.Stream()这类底层响应方式,header 也得提前设好 - 别在中间件里反复调
c.Header()同一个 key——后设的会覆盖前设的,但没必要多此一举
c.Header() 会自动标准化 key 大小写
你写 c.Header("new-token", "xxx"),实际发出去的是 New-Token。这是 Go 标准库 net/textproto.CanonicalMIMEHeaderKey 的行为,Gin 没绕过它。
前端 JS 里取 header 必须按标准化后的名字来:
- ❌
response.headers.get('new-token')—— 返回null - ✅
response.headers.get('New-Token')—— 才能取到值 - 调试时可用
console.log([...response.headers.keys()])看真实 key 列表
需要动态生成或复用逻辑?封装个函数更稳
比如统一加 trace ID、版本号、灰度标记等,硬编码在每个 handler 里容易漏、难维护。建议封装成函数:
func SetCommonHeaders(c *gin.Context, traceID string) {
c.Header("X-Trace-ID", traceID)
c.Header("X-Server-Version", "v2.3.0")
c.Header("X-Env", os.Getenv("ENV"))
}
然后在 handler 里调用:SetCommonHeaders(c, getTraceID(c))。比一堆 c.Header() 更易读、易测、易改。
别在中间件里改 c.Request.Header
有人想“给请求头补个字段”,比如把 X-Forwarded-For 解析后塞进 X-Real-IP,然后在 handler 里读——这很危险。
c.Request.Header 是引用类型,中间件里调 c.Request.Header.Set("X-Real-IP", ip),后续所有 handler 都会看到这个改动,且可能干扰框架自身逻辑(如日志中间件、认证中间件)。
正确做法是:
- 只读:用
c.Request.Header.Get("X-Forwarded-For")取值 - 存上下文:用
c.Set("real_ip", ip)存到 gin.Context,后续 handler 用c.GetString("real_ip")读 - 真要透传/注入,交给反向代理(Nginx)做,不是业务层该干的事


















