Echo框架必须用echo/middleware.CORS,因其中间件链与响应时机特殊,其他CORS中间件会绕过c.Writer导致OPTIONS预检失败、header被覆盖或凭空消失。

直接用 gin-contrib/cors 或 gorilla/handlers.CORS 是错的 —— Echo 框架必须用 echo/middleware.CORS,否则 OPTIONS 预检会静默失败或凭空消失。
为什么 echo.Use(cors.New(...)) 是唯一可靠方式
Echo 的中间件链和响应写入时机与 net/http 或 Gin 完全不同。手写 w.Header().Set() 或套用其他框架的 CORS 中间件,会在 c.Writer 封装层外绕过关键逻辑,导致:预检请求不返回 204/200、Access-Control-Allow-Origin 被覆盖、带 Credentials 时 header 冲突被浏览器直接丢弃。
正确做法是使用官方维护的 echo/middleware 包:
go
import "github.com/labstack/echo/v4/middleware"
e := echo.New()
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://myapp.com", "http://localhost:3000"},
AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.DELETE, echo.OPTIONS},
AllowHeaders: []string{echo.HeaderContentType, echo.HeaderAuthorization, "X-Request-ID"},
AllowCredentials: true,
}))
-
AllowOrigins不能填"*"同时设AllowCredentials: true,否则浏览器拒绝解析响应 -
AllowMethods必须显式包含echo.OPTIONS,Echo 不会自动补全 -
AllowHeaders要列全前端实际发的头(如X-Auth-Token),漏一项预检就 403
OPTIONS 请求返回 404 或空白响应的真因
这不是路由没配对,而是中间件注册顺序或配置触发了 Echo 的“短路”行为:一旦某个中间件调用了 c.NoContent() 或提前 w.WriteHeader(),后续中间件(包括 CORS)就再没机会执行。
立即学习“go语言免费学习笔记(深入)”;
常见诱因:
- 在自定义认证中间件里写了
return c.String(http.StatusUnauthorized, "..."),但没加return提前退出,导致流程继续走到 CORS 层并试图写 header —— 此时状态码已发,header 设置无效 -
middleware.CORSWithConfig放在了e.Use(...)链的末尾,而前面的日志或 auth 中间件已触发响应 - 用了
middleware.CORS()默认配置,但前端 origin 不在白名单里,Echo 默认返回空响应而非 403,Network 面板里只看到 status 200 + 空 body
动态 Origin 和 Credentials 共存的实操要点
生产环境常需根据请求头中的 Origin 做白名单校验,又得支持 Cookie 登录。这时不能靠字符串匹配,得用函数回调:
go
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOriginsFunc: func(origin string) bool {
for _, allowed := range []string{"https://a.com", "https://b.com"} {
if origin == allowed {
return true
}
}
return false
},
AllowCredentials: true,
}))
注意两点:
-
AllowOriginsFunc返回true时,Access-Control-Allow-Origin响应头会原样回写该origin字符串,不是"*" - 只要用了
AllowCredentials: true,Access-Control-Allow-Origin就绝不能是"*",哪怕只在AllowOriginsFunc里漏判一个 origin,整个响应都会被浏览器丢弃
最易被忽略的是:Echo 的 CORS 中间件默认不暴露自定义响应头(如 X-Rate-Limit)。如果前端要读这些字段,必须显式加 ExposeHeaders: []string{"X-Rate-Limit", "X-Request-ID"} —— 这个配置项不在文档首页,藏在源码注释里,漏掉就只能在 Network 面板看到 header,JS 却读不到。


















