gin 默认不处理 options 请求,因其不自动响应预检请求,需手动注册 options 路由或使用中间件返回 204 并设置 cors 头,否则跨域请求会因 404 或缺少响应头而失败。

为什么 Gin 默认不处理 OPTIONS 请求
Gin 本身不会自动响应 OPTIONS 预检请求,它只把请求交给注册的路由 handler;如果没匹配到任何 OPTIONS 路由,就会返回 404。这和 Nginx 不同——Gin 没有内置的“方法兜底”逻辑,也不会像 Web 服务器那样自动生成 405。
- 常见现象:前端发跨域
OPTIONS请求,Gin 返回404 Not Found或直接超时(后端根本没收到) - 本质原因:你没显式注册
OPTIONS路由,Gin 就当它不存在 - 不能依赖
gin.Recovery()或gin.Logger()拦截并修复——它们只在路由命中后才执行
如何手动注册 OPTIONS 路由并返回 204
最直接、最可控的做法是为每个需要跨域的 API 路径(或路径前缀)单独加一条 OPTIONS 路由,立即返回 204 No Content 并带上 CORS 头。
- 不要用
c.Next()让它继续走后续中间件或 handler——OPTIONS必须短路响应 - 必须设置
Access-Control-Allow-Origin,否则浏览器仍会报 CORS 错误 -
Content-Length: 0和Content-Type: text/plain是安全起见的显式声明,避免某些客户端解析异常
示例:
router.OPTIONS("/api/users", func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "https://your-frontend.com")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Max-Age", "1728000")
c.Status(204)
})
用中间件统一处理 OPTIONS(推荐)
如果你有大量路由,逐个写 OPTIONS 太重复。可以写一个中间件,在请求进入时判断方法,提前响应。
- 必须放在所有其他中间件之前(比如放在
router.Use()第一位),否则可能被鉴权、参数校验等中间件拦截 - 只对
OPTIONS做处理,其他方法调用c.Next()继续流程 - 注意:不要在中间件里调用
c.JSON()或c.String(),它们会设置默认Content-Type,而预检要求空响应体
示例:
func OptionsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Method == "OPTIONS" {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
c.Header("Access-Control-Allow-Credentials", "true")
c.AbortWithStatus(204)
return
}
c.Next()
}
}
router.Use(OptionsMiddleware())
和 CORS 中间件共存时的坑
如果你同时用了第三方 CORS 中间件(比如 gin-contrib/cors),它通常会自动处理 OPTIONS。但要注意:
- 它可能只对已注册的路由生效——如果某条
POST /api/foo路由存在,它才会响应OPTIONS /api/foo;但如果该路由根本没定义,中间件也无能为力 - 某些版本默认不放行
Credentials,需显式设AllowCredentials: true - 若你手动写了
OPTIONS路由,又启用了 CORS 中间件,可能导致重复设置 header(一般无害,但冗余)
真正容易被忽略的是:CORS 头必须出现在 204 响应里,而不是靠后端业务 handler 补充——因为 OPTIONS 根本不进业务逻辑。一旦漏掉,浏览器就判定预检失败,后续请求连发都发不出去。











