OPTIONS预检失败后接口无响应,根本原因是预检响应不符合RFC规范:必须返回204 No Content而非200,且Access-Control-Allow-Origin不能为*(当AllowCredentials为true时),中间件需在OPTIONS时调用c.Abort()终止链式调用。

OPTIONS预检失败后接口无响应
这是最典型的“看似成功实则卡死”现象:浏览器发完OPTIONS请求,返回200,但后续GET/POST请求完全不发出。根本原因不是没配CORS,而是OPTIONS响应不符合规范。
-
OPTIONS必须返回204 No Content,不能用c.AbortWithStatus(200)或c.JSON(200, ...)——200带响应体,违反RFC 7231,Chrome/Safari会静默丢弃 - 中间件链必须在
OPTIONS时调用c.Abort(),否则后续handler仍会执行,可能写入重复header或干扰状态码 - 如果用了
gin-contrib/cors,确保没混用自定义中间件——两个中间件都设Access-Control-Allow-Origin可能导致header冲突
AllowCredentials为true时Origin不能用通配符
只要前端请求带credentials: true(比如含Cookie或Authorization头),后端Access-Control-Allow-Origin就不能是*,否则浏览器直接拒绝整个请求,控制台只显示“CORS header ‘Access-Control-Allow-Origin’ cannot be ‘*’ when credentials flag is true”。
- 开发时写死
AllowOrigins: []string{"http://localhost:3000"}最稳妥 - 生产环境需动态校验:从
c.Request.Header.Get("Origin")取值,白名单匹配后再写入c.Header("Access-Control-Allow-Origin", origin) -
AllowOriginFunc优先级高于AllowOrigins,适合做域名前缀匹配(如strings.HasPrefix(origin, "https://app."))
gin-contrib/cors配置项的实际影响
gin-contrib/cors的New函数参数看似自由,但几个关键字段组合不当会直接失效:
-
AllowAllOrigins: true等价于AllowOrigins: []string{"*"},但和AllowCredentials: true互斥——启用前者就别设后者 -
MaxAge单位是time.Duration,写86400没用,必须写24 * time.Hour,否则预检结果不缓存,每请求都发OPTIONS -
ExposeHeaders只控制前端JS能读取哪些响应头(如X-Total-Count),不影响跨域本身;漏配会导致response.headers.get("X-Total-Count")返回null
本地开发 vs 生产环境的配置差异
开发用cors.Default()省事,但上线必须收口——它默认允许所有源、所有方法、所有头,且AllowCredentials为false,和真实业务场景脱节。
- 开发阶段可接受
AllowOrigins: []string{"http://localhost:3000", "http://127.0.0.1:3000"},但别留"*" - 生产环境必须关闭
AllowAllOrigins,且AllowOriginFunc里要校验协议+域名+端口,避免被恶意站点利用 - 如果API要被多个子域名调用(如
shop.example.com、admin.example.com),AllowOriginFunc比硬编码列表更易维护
真正容易被忽略的是:CORS是浏览器强制执行的策略,服务端代码是否“执行成功”根本不重要——只要响应头缺失或冲突,前端就收不到数据。调试时盯着curl -I看响应头,比看Go日志更直接。


















