Beego中间件中必须用ctx.ResponseWriter.Header().Set()设置响应头,因ctx.Output.Header()仅在Controller层生效,Filter阶段未初始化;Header需在AbortWithStatusJSON()前设置,否则无效。

Beego中间件里怎么设置响应头
必须用 ctx.ResponseWriter.Header().Set(),不能用 ctx.Output.Header() —— 后者只在 Controller 层生效,中间件阶段调用无效。
原因在于 ctx.Output 是 Beego 封装的输出工具,其内部状态(如 Header 缓存)在 Filter 阶段尚未初始化或已被跳过;而 ctx.ResponseWriter 是底层 http.ResponseWriter 实例,Filter 中可直接操作。
常见错误是复制 Controller 里的写法,比如:
ctx.Output.Header("X-Trace-ID", traceID) // ❌ 无效果
正确写法是:
ctx.ResponseWriter.Header().Set("X-Trace-ID", traceID)
ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
- Header 键名大小写不敏感,但建议按 RFC 规范首字母大写(如
"Content-Type") - 若需覆盖已有 Header(如多次 Set),Go 的
Header().Set()会自动覆盖,无需先Del() - 设置 Header 必须在
ctx.Next()之前或之后均可,但若后续 Controller 或其他 Filter 也设同名 Header,以最后一次为准
为什么 AbortWithStatusJSON 之后再设 Header 会失效
ctx.AbortWithStatusJSON() 内部已调用 WriteHeader() 并写入响应体,此时 HTTP 响应头已发送,再调 Header().Set() 不会生效。
所以自定义 Header 必须放在 AbortWithStatusJSON() 调用之前:
ctx.ResponseWriter.Header().Set("X-Error-Code", "AUTH_FAILED")
ctx.AbortWithStatusJSON(401, map[string]string{"error": "token expired"})
- 如果需要统一错误响应头,建议封装一个辅助函数,在调
AbortWithStatusJSON()前集中设置 - 不要依赖
defer设置 Header,Filter 执行流可能提前中断,defer不一定触发 - 注意:某些 Header(如
Set-Cookie)对空格和分号敏感,建议用标准库http.SetCookie()而非手动拼接
静态资源请求(/static/)中如何加 Header
静态资源走的是 beego.BeforeStatic 过滤点,该阶段 ctx.Input 和 ctx.Data 不可用,但 ctx.ResponseWriter 和 ctx.Request 仍有效 —— 所以 Header 设置方式一致,只是注册位置不同。
例如给所有静态文件加 CORS 头:
func AddStaticHeaders(ctx *context.Context) {
ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "*")
ctx.ResponseWriter.Header().Set("Cache-Control", "public, max-age=31536000")
}
// 注册时必须用 beego.BeforeStatic
beego.InsertFilter("/static/", beego.BeforeStatic, AddStaticHeaders)
-
/static/路径匹配必须带结尾斜杠,否则可能匹配到/staticfile.js这类无路径分隔符的 URL -
BeforeStatic中不能调ctx.Next(),它没有后续执行链,直接返回即可 - 若同时需对 API 和静态资源设相同 Header,得分别注册到
BeforeRouter和BeforeStatic,无法复用同一个 Filter 函数注册两次
Content-Type 和 charset 设置的坑
Beego 默认不自动设置 Content-Type 的 charset,尤其导出 CSV 或返回 JSON 时容易漏掉,导致浏览器解析乱码。
正确做法是显式声明:
ctx.ResponseWriter.Header().Set("Content-Type", "text/csv; charset=utf-8")
ctx.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
- 不要写成
"text/csv;charset=utf-8"(缺空格),部分客户端会忽略 - Beego 的
ctx.Output.JSON()内部已设好application/json; charset=utf-8,但仅限 Controller 层;Filter 中自己写 JSON 响应时必须手动设 - 如果用了
AbortWithStatusJSON(),它内部设的是application/json,不含charset—— 若需明确指定,只能放弃该函数,改用手动WriteHeader()+json.Marshal()
BeforeStatic 和 BeforeRouter 两个阶段行为一致,但上下文能力完全不同;很多人试图在 BeforeStatic 里读取 ctx.Input.Param(),结果 panic —— 因为那压根没被解析。


















