go微服务本身不提供cors支持,需手动注入响应头或使用中间件;常见失败原因包括options未响应、allow-origin与credentials冲突、header设置过晚;推荐用gorilla/handlers.cors或gin-contrib/cors,并注意nginx透传配置。

Go 微服务本身不提供任何 CORS 支持,net/http 包完全不处理跨域逻辑,所有响应头都得你手动注入或用中间件兜底——否则浏览器直接拦截,连请求都发不到你的服务里。
为什么手写中间件容易失败
常见现象:前端报 No 'Access-Control-Allow-Origin' header,或 OPTIONS 请求返回 404/405;本质是漏了三件事:
-
OPTIONS请求没被响应,而是交给了业务 handler,结果 404 或 panic -
Access-Control-Allow-Origin设为"*",但同时设了Access-Control-Allow-Credentials: true,浏览器静默拒绝 - header 在
w.WriteHeader()或w.Write()之后才调用,Go 的底层 writer 已冻结 header,设置无效
gorilla/handlers.CORS 是最稳的通用解法
它适配原生 http.ServeMux、gorilla/mux、chi 等路由,自动处理预检、origin 白名单校验、expose headers 注入,且参数语义清晰:
- 必须显式传
handlers.AllowedOrigins([]string{"https://myapp.com"}),不能只靠默认值 - 带凭证时,
handlers.AllowedOrigins([]string{"https://myapp.com"})和handlers.AllowCredentials()必须同时存在,且 origin 不能是"*" -
handlers.MaxAge(3600)控制预检缓存时间,避免每次请求都触发 OPTIONS -
handlers.ExposedHeaders([]string{"X-Total-Count"})才能让前端读取自定义响应头
示例:handlers.CORS(handlers.AllowedOrigins([]string{"https://myapp.com"}), handlers.AllowCredentials(), handlers.MaxAge(3600))(r)
GIN 用户必须用 gin-contrib/cors
gin 的 response writer 封装了状态码和 header 写入逻辑,手写 c.Header().Set() 极易覆盖预检响应或漏掉关键头:
- 注册必须在
r.Use()中,且要在所有r.GET()等路由定义之前 - 禁用
cors.Default():它隐式关闭AllowCredentials,且MaxAge为 0,导致每次请求都走预检 - 带 cookie 场景下,要用
cors.New(cors.Config{AllowCredentials: true, AllowOrigins: []string{"https://myapp.com"}}) - 动态 origin?用
AllowOriginFunc回调,别硬编码"*"
Nginx 后面跑 Go 服务时 header 会丢
即使 Go 层返回了正确的 Access-Control-Allow-Origin,Nginx 默认不透传自定义响应头,浏览器仍收不到:
- Nginx 配置里必须加
add_header Access-Control-Allow-Origin $cors_origin;(配合 map 模块做白名单) - 或更稳妥地,在 Nginx 层统一处理 CORS,Go 层完全不设相关 header,避免双层逻辑冲突
- 确保 Nginx 的
proxy_pass后端配置包含proxy_hide_header不屏蔽关键头
微服务场景下,CORS 不是单个服务的事——网关层统一处理比每个服务自己配更可控,也更容易审计和灰度。但前提是,你得清楚每个环节(Go 中间件、反向代理、前端 fetch 配置)在哪设、设什么、不能设什么。











