Go HTTP Server 默认不支持跨域,需手动添加CORS响应头;必须处理预检OPTIONS请求并正确设置Access-Control-Allow-Origin、Credentials等头字段,推荐用中间件统一管理。

Go HTTP Server 默认不支持跨域,必须手动加响应头
Go 的 net/http 包完全不会自动处理 CORS,浏览器看到缺少 Access-Control-Allow-Origin 就直接拦截请求。不是“没生效”,而是压根没发过去——你得自己在每个响应里写进去。
常见错误是只加了 Access-Control-Allow-Origin,但漏掉预检(OPTIONS)响应,或没处理带凭证(withCredentials: true)的场景,结果 POST 请求卡在预检失败。
实操建议:
- 所有需要跨域的 handler 前,统一加一层中间件,别在每个路由里重复写
- 如果前端用
fetch({ credentials: 'include' }),后端必须设Access-Control-Allow-Credentials: true,且Access-Control-Allow-Origin不能为*,得指定具体域名 - 预检请求(OPTIONS)必须返回 204 状态码,且不能有响应体,否则某些浏览器会拒绝后续请求
用中间件实现可配置的 CORS 支持
手写一个轻量中间件比引入第三方库更可控,尤其当你只需要支持几个固定源时。核心就是拦截请求、判断是否 OPTIONS、补全必要响应头。
立即学习“go语言免费学习笔记(深入)”;
示例中间件:
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")
w.Header().Set("Access-Control-Expose-Headers", "Content-Length,Authorization")
}
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
使用方式:
http.Handle("/api/", corsMiddleware(http.StripPrefix("/api", apiHandler)))
http.ListenAndServe(":8080", nil)
注意点:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
Access-Control-Allow-Headers要包含前端实际发送的自定义头(比如X-Auth-Token),否则预检失败 - 如果 API 入口是
http.ServeMux,确保中间件包裹的是最终 handler,而不是 mux 本身 - 不要在 handler 内部再调用
w.Header().Set()覆盖中间件写的头——Header 是 map,后设覆盖前设
使用 gorilla/handlers 时如何避免“CORS 头重复”警告
用 gorilla/handlers.CORS() 很方便,但容易因多次包装或与自定义中间件叠加,导致同一响应头被设多次,Chrome 控制台报 Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values。
正确用法:
- 只在最外层套一次
handlers.CORS(),别和自定义 CORS 中间件混用 - 若需动态控制允许源(比如白名单校验),别用
handlers.AllowedOrigins([]string{"*"}),改用handlers.OriginValidator回调函数 - 启用
handlers.ExposedHeaders才能让前端 JS 读取自定义响应头(如X-Total-Count)
典型安全配置示例:
origins := []string{"https://example.com", "https://admin.example.com"}
handler := handlers.CORS(
handlers.AllowedOrigins(origins),
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
handlers.AllowedHeaders([]string{"Content-Type", "Authorization"}),
handlers.ExposedHeaders([]string{"X-Total-Count", "Link"}),
handlers.AllowCredentials(),
)(r)
本地开发时绕过 CORS 的临时方案(仅限调试)
浏览器插件或命令行启动 Chrome 禁用安全策略,只应在本地验证逻辑时用,绝不能用于测试环境或教别人这么干。
例如 macOS 启动无 CORS 检查的 Chrome:
open -n -a "Google Chrome" --args --user-data-dir="/tmp/chrome_dev_test" --disable-web-security
这招解决不了 OPTIONS 404 或后端漏头的问题,只是让浏览器闭嘴——真正上线前必须配好服务端 CORS。
最容易被忽略的一点:Nginx 或其他反向代理如果开了缓存,可能把预检响应(204)也缓存了,导致后续真实请求被返回 204。务必在 proxy 配置里禁用 OPTIONS 缓存:location = / { add_header Access-Control-Allow-Origin "*"; if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type"; add_header Access-Control-Max-Age 1728000; add_header Content-Length 0; add_header Content-Type text/plain; return 204; } }

















