cors.Default()在生产环境拖慢响应,因其强制开启AllowAllOrigins+AllowCredentials触发预检、重复写头、无白名单短路、未跳过静态资源且无法缓存。

为什么 cors.Default() 在生产环境会拖慢响应
它默认开启 AllowAllOrigins + AllowCredentials 组合,触发浏览器预检请求(OPTIONS)强制发送,且每次请求都重复生成并写入全部 CORS 头(如 Access-Control-Allow-Origin、Access-Control-Allow-Methods),不复用、不缓存、不跳过静态资源路径。
更关键的是:cors.Default() 内部未做 origin 白名单匹配短路,哪怕你只允许 https://a.example.com,它仍会对所有请求执行完整头写入逻辑——包括来自 https://evil.com 的非法请求,徒增 CPU 和内存分配开销。
- 避免在生产环境直接用
cors.Default(),尤其当 API 有明确可信前端域名时 - 预检请求(
OPTIONS)若未被中间件提前拦截并快速返回,会穿透到路由层,浪费一次路由匹配和Context初始化成本 - 所有 CORS 头应尽量静态化,避免运行时字符串拼接(比如动态拼
Origin值)
用 cors.New() 显式控制头生成与预检响应
替换 cors.Default() 为 cors.New() 配置,能精准控制哪些头必须写、哪些请求可跳过、哪些 origin 被放行。核心是把「动态决策」转为「静态配置 + 条件分支」,减少每请求的判断和分配。
例如,只允许特定域名且禁用凭证的典型配置:
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://a.example.com", "https://b.example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "HEAD"},
AllowHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: false,
MaxAge: 12 * time.Hour,
}))
-
AllowCredentials: false可让浏览器跳过预检(只要方法和头在安全列表内),大幅降低OPTIONS请求量 -
AllowOrigins是切片而非通配符,Gin-contrib/cors 内部会用slice.Contains快速比对,比正则或字符串前缀匹配更轻量 - 如果所有合法 origin 都带固定前缀(如
https://*.example.com),可自行加一层strings.HasPrefix判断,避免遍历整个切片
静态资源路由绕过 CORS 中间件
CORS 是浏览器机制,对直接访问的静态文件(如 /static/js/app.js)无需设置响应头。但若你把 router.Use(cors.New(...)) 放在全局,所有路由(包括 GET /favicon.ico、GET /robots.txt)都会执行完整 CORS 逻辑,纯属浪费。
正确做法是:只对 API 路由组启用 CORS,静态资源走独立路由且不挂中间件:
// ✅ 正确:CORS 仅作用于 /api
api := router.Group("/api")
api.Use(cors.New(cors.Config{...}))
{
api.GET("/users", listUsers)
api.POST("/login", login)
}
<p>// ✅ 静态资源不走 CORS
router.Static("/static", "./assets")
router.GET("/favicon.ico", func(c *gin.Context) {
c.Status(http.StatusOK)
})
- 不要把
router.Use(...)放在router.Static()之前,否则每个静态文件请求都进中间件链 - 若使用
router.StaticFS()或自定义文件服务,确保其 handler 不依赖Context中被 CORS 修改的字段(如c.Writer状态) - 某些 CDN 或反向代理(如 Nginx)更适合统一处理 CORS 头,此时 Gin 层应完全移除 CORS 中间件
高频请求路径做 origin 白名单快速匹配
当 AllowOrigins 列表较长(>10 项)或需支持子域通配时,原生 cors.New() 的线性查找会成为瓶颈。这时可提前构建 map 或 trie 结构,在中间件入口做 O(1) 判断。
一个轻量级优化示例(不引入额外依赖):
var allowedOrigins = map[string]bool{
"https://a.example.com": true,
"https://b.example.com": true,
"https://shop.example.com": true,
}
<p>func corsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
if origin == "" || !allowedOrigins[origin] {
c.Next()
return
}
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,HEAD")
c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization")
c.Header("Access-Control-Expose-Headers", "Content-Length")</p><pre class="brush:php;toolbar:false;"> if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusOK)
return
}
c.Next()
}}
router.Use(corsMiddleware())
- map 查找比切片遍历快一个数量级,尤其在 QPS > 1k 的服务上差异明显
- 手动写中间件时务必检查
Origin是否为空,否则可能被空 origin 绕过校验 - 若需支持
https://*.example.com,可用strings.HasSuffix(origin, ".example.com") && strings.HasPrefix(origin, "https://")替代正则,避免regexp包初始化开销
真正影响性能的从来不是 CORS 本身,而是你让它做了多少本不必做的判断和分配。白名单越具体、路径越隔离、预检越少触发,中间件就越接近零开销。别让一个本该 5 行完成的头设置,变成每次请求都 malloc 三段字符串的累赘操作。



















