gin框架本身不自动处理options请求,必须显式注册路由或使用cors中间件,否则返回404;非简单请求触发预检,需返回204并设置完整cors响应头,且allowcredentials为true时alloworigins不能为*。

Gin 框架本身不会自动处理 OPTIONS 请求——除非你显式注册或使用 CORS 中间件,否则它会返回 404 或被其他中间件拦截丢弃。这是跨域场景下最常踩的坑:前端发了预检请求,后端没响应,请求直接失败。
为什么 OPTIONS 请求经常 404?
Gin 默认路由不自动匹配未注册的 OPTIONS 方法。即使你写了 r.GET("/api/user", handler),浏览器发起的预检 OPTIONS /api/user 也不会命中这个路由,因为方法不匹配,且没有对应 r.OPTIONS() 注册。
- 不是所有客户端都发
OPTIONS:只有非简单请求(如带Content-Type: application/json、自定义 header、PUT/DELETE)才触发 -
gin.Default()里没有内置OPTIONS处理逻辑,它只注册了 Logger 和 Recovery 中间件 - 如果你用
gin.New()且没挂 CORS 中间件,OPTIONS请求根本不会进业务逻辑层
手动注册 OPTIONS 路由的适用场景
适合调试、极简服务或需精确控制预检响应头的场景,但不推荐用于生产环境全量路由。
- 只对特定路径开放:
r.OPTIONS("/login", func(c *gin.Context) { c.Status(204) }) - 必须返回
204 No Content,不能带 body,否则部分浏览器拒绝后续请求 - 响应头需包含
Access-Control-Allow-Methods、Access-Control-Allow-Headers等,否则预检失败 - 注意:手动注册无法覆盖所有动态路由(比如
/user/:id),得配合通配或中间件
用 cors 中间件接管 OPTIONS 的正确姿势
绝大多数情况应使用 github.com/gin-contrib/cors,它会在中间件链中拦截所有 OPTIONS 并统一响应,无需手写路由。
- 安装:
go get github.com/gin-contrib/cors - 启用时确保
cors.Config{AllowAllOrigins: true}不用于生产;应设AllowOrigins白名单 - 关键配置项:
AllowCredentials: true时,AllowOrigins不能为"*",必须指定具体域名 - 中间件必须在
r.Use(...)中尽早注册,否则可能被后续中间件提前终止 - 示例片段:
import "github.com/gin-contrib/cors"<br>r := gin.Default()<br>r.Use(cors.New(cors.Config{<br> AllowOrigins: []string{"http://localhost:3000"},<br> AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},<br> AllowHeaders: []string{"Authorization", "Content-Type"},<br> AllowCredentials: true,<br>}))
OPTIONS 响应体为空但 headers 必须完整
浏览器只看响应头是否合规,body 内容完全忽略。常见错误是返回 200 OK + JSON body,这会导致预检失败。
- 正确做法:调用
c.Status(204)或c.AbortWithStatus(204),然后立即 return - 不要在
OPTIONS处理中调用c.JSON()、c.String()等写 body 的方法 - 如果用自定义中间件,务必检查
if c.Request.Method == "OPTIONS" { c.Abort(); return }是否放在 header 设置之后、body 写入之前 - 某些代理(如 Nginx)可能重写
OPTIONS响应,需确认实际返回 headers 是否被透传
真正麻烦的不是怎么写 OPTIONS,而是它什么时候该出现、谁该负责响应、以及响应头是否和前端请求头严格匹配——漏掉一个 Access-Control-Allow-Headers 字段,整个预检就卡住。别指望“先跑起来再说”,CORS 配置必须和前端发起的请求特征对齐。











