go 的 net/http 默认不发 cors 头,请求被浏览器拦截;必须手动或用中间件设置响应头,且 options 预检、credentials 冲突、header 设置时机任一出错均导致静默失败。

Go 的 net/http 默认不发任何 CORS 头,浏览器直接拦截请求——不是后端没收到,是请求根本没发出去。必须手动或用中间件补全响应头,且 OPTIONS 预检、Credentials 冲突、Header 设置时机三者任一出错,都会导致静默失败。
为什么 Access-Control-Allow-Origin: "*" 有时报错
只要前端 fetch 带了 { credentials: 'include' },或后端返回了 Set-Cookie、Authorization 等敏感头,浏览器就强制拒绝 Access-Control-Allow-Origin: "*"。错误信息明确提示:“The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'”。
- 正确做法是读取请求头
Origin,白名单校验后原样回写:w.Header().Set("Access-Control-Allow-Origin", origin) - 白名单不能写
https://*.example.com—— 浏览器不认子域名通配符,只能列全[]string{"https://a.example.com", "https://b.example.com"} - 用
gorilla/handlers.CORS时,handlers.AllowedOrigins([]string{"*"})会自动禁用handlers.AllowCredentials(),这是库对规范的主动校验,不是 bug
OPTIONS 预检请求为什么总 404 或没响应
Go 默认的 http.ServeMux 对 OPTIONS 方法无路由逻辑,既不匹配也不 fallback,请求直接被拒,根本进不到你的 handler。
- 手写中间件必须显式判断:
if r.Method == "OPTIONS" { w.WriteHeader(http.StatusNoContent); return },不能继续调用next.ServeHTTP() - 预检响应必须带全 CORS 头:
Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers,缺一个浏览器就判定预检失败 - 如果前端发了自定义头如
X-Auth-Token,Access-Control-Allow-Headers必须显式包含它,否则预检返回 403
w.Header().Set() 为什么写了却没生效
Go 的 Header 只能在 w.WriteHeader() 或首次 w.Write() 之前设置,之后调用 Set() 完全无效——Header 已写出,网络包里不会出现。
- 常见错误:在 handler 末尾才设 header,或把 header 设置放在
if r.Method == "OPTIONS"分支外统一写,导致 OPTIONS 响应漏掉关键头 - 用
gorilla/handlers.CORS或gin-contrib/cors能规避此问题,它们在 middleware 入口就完成所有 header 注入 - 若用 Nginx 反代,必须配置
add_header 'Access-Control-Allow-Origin' '$sent_http_access_control_allow_origin' always;,否则 Nginx 会丢掉或覆盖 Go 返回的 header
该用中间件还是第三方库
手写轻量中间件适合开发调试或固定源场景;但涉及凭证、动态 Origin、ExposeHeaders、缓存控制等需求时,gorilla/handlers.CORS 或 gin-contrib/cors 更稳——它们把预检响应、header 注入时机、Credentials 与 Origin 的互斥校验都封装好了。
- 手写示例关键点:先取
origin := r.Header.Get("Origin")→ 白名单检查 →w.Header().Set("Access-Control-Allow-Origin", origin)→if r.Method == "OPTIONS" { w.WriteHeader(204); return } - GIN 用户绝不要手写
c.Header(),gin-contrib/cors才能保证中间件链完整、OPTIONS 响应不漏头、Credentials 冲突被提前拦截 - 若用 Nginx,还要确认它没把 OPTIONS 请求拦掉或直接返回 204——必须透传给 Go 处理,否则预检失败
最常被忽略的其实是 header 设置顺序和 Nginx 透传逻辑:Go 写对了,Nginx 拦一下,前端照样报错;OPTIONS 响应少一个 Access-Control-Allow-Headers,浏览器连后续请求都不会发。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











