gorilla/handlers.cors是最稳的通用方案,因其不依赖框架、覆盖预检逻辑与header设置时机、自动校验origin/credentials互斥、支持动态白名单且无副作用。

Go 原生 net/http 和主流框架(如 Gin、Echo)都不自动处理 CORS,所有响应头都得你显式控制;配错一个字段(比如 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials: true 同时用 "*"),浏览器就静默丢响应——Network 面板里连完整请求都看不到。
gorilla/handlers.CORS 为什么是最稳的通用方案
它不依赖框架,覆盖预检逻辑、header 设置时机、动态 origin 校验等全部边界情况,且无运行时副作用。
-
handlers.CORS()返回的是http.Handler,必须包裹最终 handler(如http.DefaultServeMux或gorilla/mux.Router),不能传给http.HandleFunc -
handlers.AllowedOrigins([]string{"*"})会自动禁用AllowCredentials,这是对浏览器规范的主动校验,不是 bug - 要支持带 cookie 的请求,必须用具体域名列表:
[]string{"https://myapp.com", "http://localhost:3000"},不能含通配符或路径 - 需要暴露前端 JS 可读的响应头(如
X-Total-Count),必须显式加handlers.ExposedHeaders([]string{"X-Total-Count"}),否则response.headers.get("X-Total-Count")返回null
GIN 中用 gin-contrib/cors 必须避开的配置陷阱
GIN 的 c.Writer 是封装过的,手写 c.Header().Set() 极易绕过中间件链,导致 OPTIONS 响应不全或 header 覆盖失效。
- 注册位置必须在
r := gin.Default()之后、任何r.GET()之前调用r.Use(cors.New()) - 生产环境禁用
cors.Default():它隐式设MaxAge = 0,每次请求都触发预检;且默认禁用AllowCredentials -
config.AllowCredentials = true时,config.AllowOrigins数组里绝不能出现"*",否则整个响应被浏览器丢弃 - 若需动态 origin(如从 DB 查白名单),必须用
config.AllowOriginFunc,而不是硬编码数组
手写中间件时 OPTIONS 404 的根本原因和解法
Go 的 http.DefaultServeMux 对 OPTIONS 方法无默认路由逻辑,也不会 fallback 到你的 handler;没显式拦截,就直接 404。
- 必须在中间件里判断
r.Method == "OPTIONS",并立即调用w.WriteHeader(http.StatusNoContent)返回,不能继续调用next.ServeHTTP() -
w.Header().Set()必须在w.WriteHeader()或首次w.Write()之前调用,否则完全无效——Header 是 map,但响应头一旦写出,再设也进不了网络包 - 常见错误:在 handler 末尾才写 header,或在
if r.Method == "OPTIONS"分支外统一设,导致 OPTIONS 响应缺Access-Control-Allow-Methods等关键头 - 如果用了
gorilla/mux,必须在HandleFunc().Methods()中显式包含http.MethodOptions,否则中间件拿不到该请求
最易被忽略的是 header 设置时机和 origin/credentials 的互斥关系:哪怕其他都对,只要 w.Header().Set() 放在 w.Write() 之后,或者 AllowCredentials = true 时 AllowOrigins 里混进了 "*",整个跨域流程就彻底失效,而且不会报错——浏览器静默丢包,你只能靠抓包或换浏览器反复验证。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











