go 的 net/http 默认不支持 cors,需手动处理 options 预检、设置响应头并避免凭据与通配符冲突;推荐用 gorilla/handlers 或 gin-contrib/cors,nginx 需透传头。

Go 的 net/http 包默认不发任何 CORS 头,浏览器看到缺失 Access-Control-Allow-Origin 就直接拦截请求——不是后端没收到,是根本没走到你写的 handler 里。
为什么 OPTIONS 请求总是 404 或被拦截
浏览器发起带自定义头(如 Authorization)或 credentials: 'include' 的请求前,会先发一个 OPTIONS 预检。Go 默认路由不匹配 OPTIONS 方法,也不会自动响应,所以返回 404 或被中间件跳过。
- 用
http.HandleFunc时,必须显式注册OPTIONS路由,或在中间件里拦截并短路 - 用
gorilla/mux时,Methods("GET", "POST", "OPTIONS")必须包含OPTIONS,否则CORSMethodMiddleware不生效 - 用
gin时,gin-contrib/cors会自动注册预检路由;但若手写c.Header(),OPTIONS请求可能压根没进 handler -
OPTIONS响应必须返回204 No Content或200 OK,且不能有响应体,否则某些浏览器拒绝后续请求
Access-Control-Allow-Origin 不能是 * 但又想支持多个前端域名
当启用凭证(credentials: 'include' 或发送 cookie)时,Access-Control-Allow-Origin 值禁止为 *,浏览器会直接报错:“The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'...”。
- 静态白名单:用
gorilla/handlers.AllowedOrigins([]string{"https://a.com", "http://localhost:3000"}) - 动态校验:自己解析
r.Header.Get("Origin"),查数据库或配置表,匹配后再设头;注意空 Origin 或非法协议要拒绝 - 别在 handler 里用
w.Header().Set("Access-Control-Allow-Origin", "*")然后又设Allow-Credentials: true——这组头一并发出去,浏览器就静默失败 - 如果用
gin-contrib/cors,必须传cors.Config{AllowOrigins: []string{...}, AllowCredentials: true},不能混用cors.Default()
gorilla/handlers.CORS 中间件为什么有时不生效
这个包轻量可靠,但配置顺序和参数组合极易出错,尤其在嵌套中间件或配合 gorilla/mux 时。
- 必须包裹最终的
http.Handler,例如handlers.CORS(...)(r),而不是handlers.CORS(...)(http.DefaultServeMux) -
AllowedOrigins([]string{"*"})会自动禁用AllowCredentials,即使你额外传了handlers.AllowCredentials(true)也无效 -
ExposedHeaders必须显式列出前端 JS 要读的响应头,比如X-Total-Count,否则response.headers.get("X-Total-Count")返回null - 如果用了 Nginx 反向代理,它默认不透传自定义响应头;需加
add_header 'Access-Control-Allow-Origin' '$sent_http_access_control_allow_origin' always;
手写中间件最简可用模板
不引入第三方库也能跑通,关键逻辑就三行:判断 Origin、补全头、短路 OPTIONS。但要注意 header 设置时机——必须在 w.WriteHeader() 或首次 w.Write() 之前。
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
if origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization,X-Requested-With")
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
- 这段代码适用于开发调试或固定域名场景,生产环境建议换
gorilla/handlers或gin-contrib/cors - 不要在业务 handler 里再调
w.Header().Set(),会覆盖中间件写的头(Header 是 map,后设覆盖前设) - 如果前端用 fetch 发送
Content-Type: application/json,Access-Control-Allow-Headers必须包含Content-Type,否则预检失败
真正容易被忽略的是:CORS 是浏览器单方面执行的策略,服务端永远“不知道”自己被跨域了——所有问题都表现为前端控制台报错、网络面板卡在预检、或响应体明明返回了但 JS 拿不到。排查时优先确认 OPTIONS 是否被正确响应、Access-Control-Allow-Origin 是否与前端 origin 完全一致(协议、域名、端口)、以及 Nginx 是否透传了头。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











