Go HTTP服务默认拒绝跨域请求,因其net/http包不自动设置Access-Control-Allow-Origin等CORS响应头,需手动添加中间件或使用gorilla/handlers.CORS并正确配置。

Go HTTP 服务为什么默认拒绝跨域请求
浏览器在发起跨域 XMLHttpRequest 或 fetch 时,会先发一个 OPTIONS 预检请求;如果后端没返回正确的 Access-Control-Allow-Origin 等响应头,浏览器直接拦截响应——你看到的 net::ERR_FAILED 或 “CORS policy blocked” 就是这个原因。Go 的 net/http 默认不设任何 CORS 头,不是它“不支持”,而是它压根不管这事。
常见错误现象:
- 前端报错 Response to preflight request doesn't pass access control check
- 后端日志里根本没收到 OPTIONS 请求(其实是被浏览器拦截前就卡住了)
- 手动用 curl 调用接口正常,但浏览器里失败
关键点:
- CORS 是浏览器强制的同源策略,和服务端语言无关
- Go 中间件必须在路由处理链最前面注册,否则可能被其他中间件提前返回、跳过 CORS 头写入
- 不要只处理 GET 和 POST,预检请求一定是 OPTIONS,且需返回 204 或 200 + 正确头
手写一个最小可用的 CORS 中间件
不用第三方库也能搞定,核心就是三件事:判断是否预检请求、设置必要响应头、放行或短路。
实操建议:
- 检查 r.Method == "OPTIONS",并立即 w.WriteHeader(204) 返回空响应
- 对所有请求(包括预检后的真实请求)统一加 Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers
- 如果前端带了 Credentials(比如 fetch(..., { credentials: 'include' })),Access-Control-Allow-Origin 不能是 *,得写具体域名,且必须加 Access-Control-Allow-Credentials: true
示例片段:
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "https://your-frontend.com")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
<pre class='brush:php;toolbar:false;'> if r.Method == "OPTIONS" {
w.WriteHeader(204)
return
}
next.ServeHTTP(w, r)
})}
立即学习“go语言免费学习笔记(深入)”;
gorilla/handlers.CORS 为什么有时不生效
这个包很常用,但容易因配置顺序或参数误用失效。
常见坑:
- 把 handlers.CORS() 放在路由注册之后(比如先 r.HandleFunc(...) 再套 handlers.CORS),实际中间件没包裹到 handler 上
- 用 handlers.AllowedOrigins([]string{"*"}) 但前端又发了带 credentials 的请求,导致浏览器拒绝响应(* 和 credentials 不兼容)
- 忘了传 handlers.ExposedHeaders,导致前端拿不到自定义响应头(如 X-Total-Count)
推荐写法(显式指定 origin,兼容 credentials):
origins := handlers.AllowedOrigins([]string{"https://your-frontend.com"})
methods := handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"})
headers := handlers.AllowedHeaders([]string{"Content-Type", "Authorization"})
creds := handlers.ExposedHeaders([]string{"X-Total-Count"})
c := handlers.CORS(origins, methods, headers, creds, handlers.AllowCredentials())
<p>http.ListenAndServe(":8080", c(r))生产环境要注意的细节
本地开发用 * 很方便,但上线后必须收敛。
关键限制:
- 不要硬编码 Access-Control-Allow-Origin 为 *,尤其当 API 同时供多个前端调用时,应根据 Origin 请求头做白名单校验
- 避免在中间件里重复设置同一 header(比如既用 gorilla/handlers 又手动 w.Header().Set),Go 的 Header 是 map,后设会覆盖前设,但某些 header(如 Set-Cookie)会被浏览器忽略
- 如果用了反向代理(如 Nginx),确认它没过滤掉 Access-Control- 开头的响应头(Nginx 默认会 strip 掉非标准头)
真正难的不是加几个 header,而是把 origin 校验逻辑和你的 auth、tenant 隔离机制对齐——比如 SaaS 多租户场景下,每个子域名对应不同租户,CORS 白名单就得动态查库或缓存,这时候静态中间件就不够用了。


















