Beego中间件默认不处理OPTIONS预检请求,因其在过滤器链前被短路;唯一拦截方式是显式注册OPTIONS路由并由Controller的Options()方法处理,同时需统一设置CORS头。

Beego 中间件默认不处理 OPTIONS 请求
Beego 的 InsertFilter 注册的中间件(如 beego.BeforeRouter)**完全不会执行**在 OPTIONS 预检请求上——这不是漏配,是框架明确跳过。你写再多日志、ctx.Next() 或 header 设置,在预检请求里都看不到效果。
原因很直接:Beego 把 OPTIONS 当作“路由元操作”,在进入过滤器链之前就做了短路处理,只留给控制器层响应。所以别试图在中间件里 if r.Method == "OPTIONS" 做判断,它根本进不来。
- 所有
beego.InsertFilter("/*", beego.BeforeRouter, ...)对OPTIONS无效 -
beego.BeforeExec和AfterExec同样不触发 - 唯一能拦截
OPTIONS的方式,是把它当成普通路由交给 controller 处理
必须用 NSRouter 或 NSRouter 显式注册 OPTIONS 路由
你需要在命名空间(NSNamespace)或主路由中,**显式声明 OPTIONS 方法对应的 handler**,让 Beego 把它当正常请求走 controller 流程。
例如:
ns := beego.NewNamespace("/api",
beego.NSRouter("/users", &controllers.UserController{}, "get:Get;post:Post;options:Options"),
beego.NSRouter("/products", &controllers.ProductController{}, "get:Get;options:Options"),
)
beego.AddNamespace(ns)
注意两点:
-
options:Options必须写在路由方法列表里,不能省略 - 如果想全局覆盖所有路径的预检(比如
/api/*),用通配符:beego.NSRouter("/*", &controllers.BaseController{}, "options:Options")
BaseController.Options() 必须手动设置 CORS 头
OPTIONS 请求到达 Options() 方法后,它不会自动继承你在其他方法里写的 AllowCross() 逻辑——除非你显式调用。
典型写法:
func (c *BaseController) Options() {
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "http://localhost:8080")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Credentials", "true")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Max-Age", "1728000")
// 注意:这里不能调用 c.ServeJSON(),否则会多写一次 Content-Type 和 200 状态码
// 直接返回空响应即可,浏览器只看状态码和 header
c.Ctx.ResponseWriter.WriteHeader(204)
}
常见错误:
- 忘记设
Access-Control-Allow-Origin→ 预检失败,控制台报 “No 'Access-Control-Allow-Origin' header” - 用了
c.ServeJSON()→ 返回 200 + JSON body,但浏览器期望的是 204 或 200 空体,某些客户端会卡住 -
Allow-Credentials: true时仍设Access-Control-Allow-Origin: "*"→ 浏览器直接忽略整个响应
跨域头要和业务接口保持一致,不能只在 Options 里设
很多人以为只要 Options() 返回了正确 header 就万事大吉,其实不然。真实业务请求(比如 POST /api/users)也必须带同样的 CORS 头,否则浏览器通过预检后,正式请求仍会被拦。
推荐做法是抽成复用函数:
func (c *BaseController) SetCORSHeaders() {
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "http://localhost:8080")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Credentials", "true")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
}
然后在每个 controller 方法开头调用:
func (c *UserController) Post() {
c.SetCORSHeaders()
// ... 业务逻辑
}
以及在 Options() 里也调用它——这样能保证 header 完全对齐,避免因拼写、大小写或遗漏导致的隐性失败。
最易被忽略的一点:Beego 不会帮你合并 header。如果你在中间件里设了一次 Access-Control-Allow-Origin,又在 controller 里设了另一次,后者会覆盖前者。所以统一入口管理比分散设置更可靠。











