Beego中设置自定义响应头必须在w.WriteHeader()或任何w.Write()调用之前执行,否则头将被静默丢弃;常见失效原因是Header().Set()置于ServeJSON()等输出方法之后,导致响应头已锁定。

Beego 里设置自定义响应头,关键不是“怎么写”,而是“什么时候写”——w.Header().Set() 必须在 w.WriteHeader() 或任何 w.Write() 调用之前完成,否则头会被静默丢弃。
为什么 w.Header().Set() 有时不生效
常见现象是:代码里明明调用了 c.Ctx.ResponseWriter.Header().Set("X-Trace-ID", "abc123"),但抓包看不到这个头。根本原因是 Beego 的 ResponseWriter 行为和标准库一致:一旦响应体开始写入(比如调用了 c.ServeJSON()、c.TplName = "xxx"、甚至 c.Ctx.WriteString("ok")),底层就会自动触发 WriteHeader(http.StatusOK),此时 Header 已锁定。
- Beego v2+ 中
c.ServeJSON()内部会先调用WriteHeader(200),再写 JSON 字节流 - 即使你把
Header().Set()放在ServeJSON()前面,只要没显式调用WriteHeader(),它仍属于“未发送状态”,没问题;但顺序反过来就彻底失效 - App Engine 等托管平台对此更敏感,Header 锁定后连
http.Error()都无法补救
正确设置时机:在 ServeJSON / WriteString / TplName 之前
最稳妥的做法是把所有 Header 设置放在控制器方法开头,或至少确保在任何输出逻辑之前执行:
func (c *MainController) Get() {
// ✅ 正确:Header 设置优先
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "*")
c.Ctx.ResponseWriter.Header().Set("X-App-Version", "v1.2.3")
// ✅ 此时再输出内容
c.Data["json"] = map[string]string{"msg": "hello"}
c.ServeJSON()
}
- 不要依赖
c.Ctx.Output.Header()—— 它只是Header().Set()的封装,行为完全一样 - 若需动态状态码(如 401),必须显式调用
c.Ctx.ResponseWriter.WriteHeader(401),再设 Header,最后再输出内容 - 避免在
FinishRouter过滤器里设 Header:此时响应可能已发出,Header 修改无效
Beego 特有的 Header 相关陷阱
Beego 框架本身不拦截或重写 Header,但某些内置行为容易干扰你的设置:
-
c.ServeJSON()默认会设置Content-Type: application/json; charset=utf-8,如果你之前用Header().Set()设过同名头,会被覆盖(Set()是覆盖语义) - 使用
c.Data["json"]+ServeJSON()时,Beego 会自动加Content-Type,无需手动设;但自定义头必须你自己加 - 如果启用了
gzip中间件(beego.BConfig.WebConfig.EnableGzip = true),它只影响响应体压缩,不影响 Header 设置逻辑 - Beego v1 和 v2 的
context.Context结构略有差异,但Ctx.ResponseWriter.Header()接口保持兼容,不用额外适配
真正容易被忽略的点是:Header 设置没有错误反馈。设错名字、拼错大小写(如 "access-control-allow-origin" 小写)、或晚于第一次 Write(),都不会 panic,只会静默失败。建议在本地用 curl -I 或浏览器 DevTools 的 Network 标签页实时验证,别只信日志或代码顺序。


















