Go HTTP服务器需显式处理OPTIONS预检请求并返回204/200,设置完整CORS头(含Vary: Origin),禁用通配符*与凭证共用,推荐rs/cors实现动态Origin校验。

Go 的 HTTP 服务器默认不处理跨域,OPTIONS 预检请求直接 404,前端 fetch 会卡在预检阶段,根本不会走到你的业务逻辑——这不是后端“没收到”,是请求压根没发出去。
为什么必须拦截 OPTIONS 请求
浏览器对非简单请求(如带 Authorization 头、Content-Type: application/json、PUT/DELETE 方法)会先发 OPTIONS 预检。如果中间件没捕获并响应它,net/http 默认返回 405 或 404,整个请求链就断了。
-
OPTIONS响应必须返回http.StatusNoContent(204)或http.StatusOK(200),且不能有响应体 - 所有 CORS 头(
Access-Control-Allow-Methods、Access-Control-Allow-Headers等)必须出现在OPTIONS响应里,否则预检失败 - 中间件要包裹最终 handler,比如
http.HandlerFunc或gin.Engine的Use(),不能只套在http.ServeMux外层——否则子路由可能绕过
gorilla/handlers.CORS 的两个参数必须配对
用 gorilla/handlers.CORS 时,handlers.AllowedCredentials(true) 和 handlers.AllowedOrigins([]string{"*"}) 是非法组合,会触发浏览器静默拒绝,并在控制台报错:The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'。
- 若启用凭证(
credentials: 'include'),AllowedOrigins必须是具体域名列表,例如[]string{"https://app.example.com", "http://localhost:3000"} -
https://*.example.com这类子域名通配不被标准支持,gorilla/handlers也不支持回调校验,需自己解析Origin请求头做白名单匹配 - 带凭证时,还必须手动确保响应中含
Vary: Origin头(rs/cors和gin-contrib/cors会自动加,手写中间件得自己补)
手写中间件时最容易漏的三件事
自己写 func(next http.Handler) http.Handler 看似简单,但以下三点漏掉任一,线上就会出问题:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 没判断
r.Method == "OPTIONS"就直接next.ServeHTTP()—— 导致预检被转发给业务 handler,返回 404 或错误内容 - 对非
OPTIONS请求才设Access-Control-Allow-Origin,但忘了检查Origin是否在白名单里;直接写死"*"在带凭证场景下等于关掉登录态 - 没显式设置
Access-Control-Expose-Headers—— 前端调用response.headers.get("X-Request-ID")返回null,即使后端确实写了这个 header
生产环境推荐用 rs/cors 而不是 gorilla/handlers
rs/cors(即 github.com/rs/cors)比 gorilla/handlers.CORS 更贴近实际需求:支持 AllowedOriginsFunc 回调,能动态校验 Origin;自动加 Vary: Origin;对 MaxAge、ExposedHeaders 等字段封装更合理;且维护更活跃。
- 示例配置:
cors.New(cors.Options{AllowedOriginsFunc: func(origin string) bool { return strings.HasSuffix(origin, ".example.com") }, AllowCredentials: true}) -
gorilla/handlers.CORS不支持 Origin 动态判断,遇到 SaaS 多租户场景基本没法用 - 别图省事用
gin-contrib/cors的默认配置——它默认允许"*",上线前务必检查Config.AllowAllOrigins是否为false
最常被忽略的是:CORS 不是“加几个 header 就完事”,它是浏览器、代理、CDN、后端四层协作的结果。Vary: Origin 漏了,CDN 可能缓存一个 Access-Control-Allow-Origin: https://a.com 的响应,再给 https://b.com 用,导致跨域失败;OPTIONS 没拦截,前端永远卡在 loading 状态,日志里却查不到任何业务请求记录。

















