Go的net/http包默认不处理CORS,必须手动添加响应头或使用中间件(如gorilla/handlers、gin-contrib/cors);否则浏览器因缺失Access-Control-Allow-Origin等头而拦截请求,常见错误包括OPTIONS 404、credentials与通配符冲突、header设置过晚或Nginx未透传。

Go 的 net/http 包默认不处理 CORS,所有跨域响应头都得你手动加,或者用中间件补上;否则浏览器直接报 Access-Control-Allow-Origin 缺失、预检 404 或静默丢响应——这不是框架“没配好”,是 Go 原生就不管这事。
手写 header 为什么容易失败
看似只加几行 w.Header().Set() 就能跑通,但实际踩坑率极高:
-
OPTIONS请求返回 404:因为没注册该方法的 handler,DefaultServeMux不会 fallback 处理预检 -
Access-Control-Allow-Credentials: true和Access-Control-Allow-Origin: "*"同时出现:浏览器强制拒绝,连 Network 面板里都看不到完整响应体 - header 设置太晚:在
w.WriteHeader()或首次w.Write()之后调用Set(),完全无效 - 漏掉
Access-Control-Allow-Headers或Access-Control-Expose-Headers:带Authorization或读取X-Total-Count时直接失败
用 gorilla/handlers 是最稳的通用方案
它不依赖框架,轻量无副作用,覆盖所有预检逻辑和 header 时机控制,适合原生 net/http、gorilla/mux 等场景:
- 安装:
go get github.com/gorilla/handlers - 基础用法:
http.ListenAndServe(":8080", handlers.CORS(handlers.AllowedOrigins([]string{"https://myapp.com"}))(r)) -
AllowedOrigins传["*"]时,handlers.Credentials(true)会被自动禁用——这是浏览器规范强制的,中间件已做校验 - 需要暴露自定义 header(如
X-Request-ID)?加handlers.ExposedHeaders([]string{"X-Request-ID"}) - 动态白名单?改用
handlers.AllowedOriginsFunc(func(origin string) bool { return inWhitelist(origin) })
GIN 用户必须用 gin-contrib/cors,别手写 c.Header()
GIN 的 c.Writer 是封装过的,手写 header 极易绕过中间件链,导致 OPTIONS 响应不全、credentials 冲突或 header 覆盖失效:
- 安装:
go get github.com/gin-contrib/cors - 注册位置:必须在
r := gin.Default()之后、任何r.GET()之前调用r.Use(cors.New(...)) - 生产环境禁用
cors.Default():它隐式禁用AllowCredentials,且每次请求都触发预检(MaxAge为 0) - 带 cookie 的请求必须显式列域名:
AllowOrigins: []string{"https://myapp.com", "http://localhost:3000"},不能含"*" - Nginx 后面跑 Gin?检查是否透传了 header:
add_header Access-Control-Allow-Origin $sent_http_access_control_allow_origin always;,否则上游设的 header 会被清空
nginx 透传失败是上线后最常被忽略的一环
Gin 或 gorilla/handlers 返回了正确的 CORS header,但浏览器仍报错——大概率是 nginx 拦截或覆盖了响应头:
- 用
add_header ... always,否则子 location 块会丢掉父块 header -
OPTIONS请求不能由 nginx 直接返回 204,必须透传给后端处理,否则预检失败 - 检查 nginx error log,常见错误是
upstream sent no valid HTTP/1.0 header,说明 Go 服务提前写了 body,导致 header 无法再设置 - 如果用了
proxy_buffering off或流式响应,确保 Go 侧没有在 header 发送前调用w.Write()


















