API网关层统一处理OPTIONS预检请求是唯一兼顾性能、一致性与可维护性的方案,因Nginx、Cloudflare等网关常默认拦截OPTIONS,导致Go微服务根本收不到;微服务自建CORS不仅无效还引发配置漂移和响应头冲突。

API网关层统一处理 OPTIONS 预检请求,是唯一能兼顾性能、一致性与可维护性的方案。微服务各自加 CORS 中间件不仅无法解决预检被拦截的问题,还会放大配置漂移和链路污染风险。
为什么 OPTIONS 请求根本到不了你的 Go 微服务?
绝大多数“收不到 OPTIONS”的问题,根源不在 Go 代码本身,而在网关或反向代理层提前截断了它。Nginx 默认不转发 OPTIONS 到上游;Cloudflare 在代理模式下会直接响应 204;Kong 或 APISIX 若未显式开启 cors 插件或配置 preflight 路由,也会静默丢弃。
- Nginx 必须加
location ~ ^/.*$ { add_header Access-Control-Allow-Origin "*"; if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Headers "Content-Type, Authorization, X-Request-ID"; add_header Access-Control-Allow-Credentials "true"; add_header Access-Control-Expose-Headers "X-Total-Count"; return 204; } } - Cloudflare 的「CORS Header」规则只影响响应头,不放行 OPTIONS 请求;真正要解,得关掉代理模式(改为 DNS only)或用 Workers 拦截并透传
- Go 微服务自己注册
http.HandleFunc("/api/users", ...)对 OPTIONS 无效——因为路径匹配失败,DefaultServeMux根本不会调用你写的 handler
API 网关必须覆盖全路径的 OPTIONS 响应
网关的预检响应不能只针对已知 API 路径,而要能匹配任意前缀(如 /api/*、/v1/**),否则新增服务或路径时,前端一发非简单请求就卡死。
- 使用 Kong:启用
cors插件,并设config.preflight_continue = true,确保预检通过后仍透传给上游 - 使用 APISIX:配置
cors插件时,allow_origins建议填具体域名列表,allow_credential设为true时,allow_origins不能含* - 手写网关(如基于
net/http+httputil.NewSingleHostReverseProxy):需在Director前插入逻辑,对所有OPTIONS请求短路返回 204,并带齐Access-Control-Allow-Origin、Access-Control-Allow-Methods等头
Go 微服务内部该不该再处理 CORS?
不该。除非你确定该服务永远不走网关、永远直连前端(比如本地开发联调),否则重复加 CORS 头会导致响应头冲突,且 Access-Control-Allow-Origin 可能被覆盖成 *,而网关设的是具体域名,最终以后者为准——但中间多一次 Go 层解析和 header 合并,纯属浪费 CPU。
- 若必须保留微服务级 CORS(如部分服务绕过网关),禁用其 OPTIONS 处理逻辑:用
rs/cors时,传OptionsPassthrough: true,让网关全权负责预检 - 所有微服务的
Access-Control-Allow-Origin应设为空或注释掉,避免干扰网关策略 - 暴露给前端的响应头(如
X-Request-ID、X-Total-Count)必须在网关层统一通过Access-Control-Expose-Headers声明,Go 服务里设了也没用
真正的瓶颈从来不在 Go 的 http.ServeMux,而在于你是否把预检当成了“每个服务都要自己答的考题”。网关才是那个该交卷的人——而且必须答得又快又准,否则前端每发一个 PUT 或带 Authorization 的请求,都得多扛一次毫秒级延迟和一次额外网络跳转。


















