go语言net/http包不处理cors,必须手动或用第三方库(如gorilla/handlers.cors)显式设置响应头;options请求易404因servemux无默认路由,须中间件拦截并返回204;带credentials时access-control-allow-origin不可为*,需白名单校验后回写具体origin;access-control-allow-headers须包含前端所有自定义头,否则预检403。

Go 语言本身不处理 CORS,net/http 包完全不会自动加任何跨域头,浏览器看到响应里没 Access-Control-Allow-Origin 就直接拦截——不是后端没收到请求,是前端压根没把请求发出去。
为什么 OPTIONS 请求总返回 405 或 404
Go 默认的 http.ServeMux 对 OPTIONS 方法没有路由匹配逻辑,它既不转发也不忽略,而是直接拒掉。你写的 http.HandleFunc("/api", handler) 只注册了 GET/POST,OPTIONS 根本不进你的 handler。
- 必须在中间件里显式拦截
r.Method == "OPTIONS",提前w.WriteHeader(http.StatusNoContent)并return - 不能依赖
http.ServeMux自动 fallback,它不会帮你兜底 - 如果用了
gorilla/mux,确保CORS中间件是最外层包装器,而不是只包某个子路由
带 credentials 时 Access-Control-Allow-Origin: "*" 一定失败
只要前端 fetch 用了 { credentials: 'include' }(或 jQuery 的 xhrFields: { withCredentials: true }),后端就不能设 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"} - 同时必须设
Access-Control-Allow-Credentials: "true",且二者必须严格成对出现
预检失败常见原因:Access-Control-Allow-Headers 漏字段
浏览器发预检时,会把实际请求中带的自定义头(比如 X-Auth-Token、X-Request-ID)全列在 Access-Control-Request-Headers 里。如果你的 Access-Control-Allow-Headers 没包含它们,预检就 403。
- 检查前端实际发送了哪些头:
fetch("/api", { headers: { "X-Auth-Token": "abc", "Content-Type": "application/json" } }) - 后端
Access-Control-Allow-Headers必须包含"X-Auth-Token, Content-Type",不能只写"Content-Type" - 简单请求(如仅
Content-Type: text/plain)不触发预检;但application/json一定会触发,别被“没写错”骗了
手写中间件 vs 第三方库怎么选
手写轻量中间件适合规则固定、源少、无复杂策略的场景;第三方库(如 rs/cors 或 gorilla/handlers.CORS)更适合生产环境,它自动处理 Vary: Origin、缓存头、边缘 case(比如多个 Origin 同时存在时的 header 冲突)。
- 手写要注意:
w.Header().Set()是覆盖写,别在业务 handler 里再调用它去改中间件已设的头 - 用
rs/cors时,cors.Default()不开 credentials,要显式传cors.AllowCredentials() - 无论哪种方式,中间件必须包裹最终 handler,例如:
corsMiddleware(http.StripPrefix("/api", apiHandler)),而不是包裹http.ServeMux实例本身
最容易被忽略的是:CORS 头必须在所有其他响应头之前设置,且不能被后续逻辑覆盖;OPTIONS 响应体必须为空,哪怕多一个换行也会让某些浏览器拒绝后续请求。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











