Gin 默认不处理 OPTIONS 请求,导致预检失败返回 404;正确做法是在 CORS 中间件中拦截 OPTIONS 方法并立即返回 204 No Content,同时设置 Access-Control-Allow-Origin、Methods、Headers 等头,且当 Credentials 为 true 时 Origin 不能为 *。

为什么 OPTIONS 请求总是 404 或被忽略
因为 Gin 默认不注册任何 OPTIONS 路由,也不自动响应预检请求。浏览器发起的预检是真实 HTTP 请求,若没对应 handler,Gin 就按 404 处理——哪怕你已经写了 CORS 中间件,只要没显式处理 OPTIONS 方法,它就过不了第一关。
常见现象:前端发 PUT 或带 Authorization 头的 POST,控制台报 Response to preflight request doesn't pass access control check,但 GET 正常;抓包发现 OPTIONS 返回 404 或空响应体。
- 中间件里没判断
method == "OPTIONS",直接c.Next()后走不到路由,最终 fallback 到 404 - 用了
gin-contrib/cors却没启用Config.AllowAllOrigins或白名单匹配失败,导致中间件跳过设头 - 自定义中间件写在
r.GET(...)之后,OPTIONS请求根本没经过它
中间件里怎么正确响应 OPTIONS
必须在中间件中拦截 OPTIONS 并立即返回 204 No Content,不能调用 c.Next(),否则会继续往下匹配路由(大概率 404)或写重复响应头。
关键点不是“允许什么”,而是“让预检快速通过”:状态码必须是 204,响应体必须为空,且所有 CORS 头必须存在。
- 先取
c.Request.Method,如果是"OPTIONS",立刻c.AbortWithStatus(http.StatusNoContent) - 在
if method == "OPTIONS"分支里设头,不要放在c.Next()后面——否则OPTIONS响应里没头 - 必须设置
Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers,缺一不可 - 如果前端带凭证(
withCredentials: true),Access-Control-Allow-Origin不能为"*",得动态匹配白名单
Allow-Origin 用 * 还是具体域名
取决于是否需要前端发送 Cookie 或 Authorization 头。只要开了 Access-Control-Allow-Credentials: "true",Access-Control-Allow-Origin 就不能是 "*",否则浏览器直接拒绝。
生产环境必须校验 Origin 请求头,只对可信域名回写对应值;开发阶段可临时用 http://localhost:3000 硬编码,但别留 "*" 上线。
-
Origin来自c.Request.Header.Get("Origin"),为空时说明不是跨域请求,无需设 CORS 头 - 白名单建议用 map 或 slice 遍历比对,避免正则或模糊匹配引入安全风险
- 若用
gin-contrib/cors,确保Config.AllowOrigins是具体字符串切片,如[]string{"https://a.com", "https://b.com"},不是["*"]
为什么 Nginx 层处理 OPTIONS 更稳妥
当 Gin 应用部署在 Nginx 后,把预检请求在网关层截住并返回 204,能省掉一次 Go runtime 调度、中间件链执行和路由匹配开销,尤其适合高并发场景。
但要注意:Nginx 的 add_header 默认只对 2xx 响应生效,而 return 204 是 204 响应,需加 always 参数才能注入 CORS 头。
- Nginx 配置里必须写
add_header Access-Control-Allow-Origin "*" always;,否则头不会出现在 204 响应中 -
if ($request_method = 'OPTIONS')块内要显式设Content-Length: 0和Content-Type: text/plain,避免某些客户端解析异常 - 若后端 Gin 也开了 CORS 中间件,Nginx 和 Go 层的头可能冲突,建议关掉 Gin 的 CORS,只留 Nginx 处理
实际中最容易被忽略的,是 OPTIONS 响应必须严格满足:状态码 204、无响应体、所有必需 CORS 头齐全、且 Access-Control-Allow-Origin 与凭证设置不冲突——少一个,浏览器就卡在预检这步,后续请求根本发不出去。


















