gin默认不处理options预检请求,正确做法是用c.abortwithstatus(http.statusnocontent)返回204且无响应体;开启凭证时alloworigins不可设为["*"];cors头须在c.next()前用c.writer.header().set()设置;中间件必须在路由注册前调用r.use()。

直接上结论:Gin 默认不处理 OPTIONS 预检请求,手写中间件若没在正确时机 abort、或返回了非标准状态码,前端就会卡在预检阶段——现象是 Network 面板里 OPTIONS 显示 200,但后续请求根本不发,控制台报 “preflight request doesn't pass access control check”。
为什么 c.AbortWithStatus(200) 在 OPTIONS 处理里是错的
RFC 6365 和浏览器实现(Chrome/Safari/Firefox)明确要求:成功的预检响应必须是 204 No Content,不能带响应体,且语义上表示“纯确认”。用 200 虽然 HTTP 合法,但部分浏览器会静默拒绝,后续请求被拦住,连 debug 都找不到源头。
-
c.AbortWithStatus(http.StatusOK)→ 错误,即使 header 全对也大概率失败 -
c.AbortWithStatus(http.StatusNoContent)→ 正确,必须用这个 - 不能在
c.Next()之后再处理OPTIONS,否则路由逻辑可能已 panic 或写入 body,header 设置失效
gin-contrib/cors 的 AllowOrigins 为什么不能填 ["*"] 当开启凭证时
当你设置了 Access-Control-Allow-Credentials: "true"(比如前端带 withCredentials: true 或发 Cookie),浏览器强制要求 Access-Control-Allow-Origin 必须是具体域名,不能是通配符 "*"。否则整个响应会被丢弃——Network 面板里你看不到 response body,甚至 headers 都不显示你设的 CORS 头。
- 开发环境可写死:
[]string{"http://localhost:3000"} - 生产环境需动态校验:
origin从c.Request.Header.Get("Origin")取值,再比对白名单 -
gin-contrib/cors的Config{AllowOrigins: []string{"*"}, AllowCredentials: true}会直接 panic 或静默降级,别这么配
自定义中间件里 c.Header() 和 c.Writer.Header().Set() 的区别
二者看似等价,但在中间件执行链中行为不同:c.Header() 是 gin 封装方法,内部调用 c.Writer.Header().Set(),但前提是响应尚未 flush;一旦你调了 c.JSON()、c.String() 或 c.Next() 后业务 handler 已写响应,再调 c.Header() 就完全无效。
- 安全写法:所有 CORS header 必须在
c.Next()前设置,且统一用c.Writer.Header().Set()更可控 - 常见错误:在
c.Next()后补 header,比如if c.Writer.Size() == 0 { c.Header(...) }—— 这时 response 已 commit,设置被忽略 -
Access-Control-Allow-Headers必须包含前端实际发送的字段,例如X-Auth-Token或X-Request-ID,漏一个,预检就 403
注册顺序错会导致中间件完全不生效
中间件必须在任何路由注册前调用 r.Use(),否则它根本不会进入请求链。Gin 的执行顺序是:全局中间件 → 路由匹配 → 路由级中间件 → handler。如果你把 r.Use(Cors()) 放在 r.POST("/api/login", loginHandler) 后面,那这个路由就永远收不到 CORS 头。
- 正确位置:
r := gin.Default()之后、第一个r.GET()之前 - 错误写法:
r.GET(...); r.Use(...)→ 中间件只对后续路由生效,前面的全绕过 - 调试技巧:在中间件开头加
log.Println("CORS hit:", c.Request.Method, c.Request.URL.Path),确认是否被触发
最易被忽略的一点:预检请求本身不带 Cookie 或 Authorization,所以你在中间件里做 JWT 解析、session 校验,或者依赖 c.MustGet("user"),都会 panic 或 401——OPTIONS 必须是无状态、无鉴权、立即返回 204 的裸响应。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











