Files
go-omni/cors

可选 CORS

CORS 控制浏览器能否跨源读取 HTTP 响应。默认关闭;同源反向代理可以保持关闭。模块无 Gin 或具体业务依赖,标准库/native/HTTP2 与自定义 Runner 使用相同 App 接入策略。

生成项目只改配置

在既有 etc/config.yaml 的 server 中加入,无需修改 main、svc 或每条 .api 路由:

server:
  cors:
    enabled: true
    allowedOrigins:
      - https://admin.example.com
      - https://merchant.example.com
    allowedMethods: [GET, HEAD, POST, PUT, PATCH, DELETE]
    allowedHeaders: [Authorization, Content-Type]
    exposedHeaders: [X-Request-ID, Content-Disposition]
    allowCredentials: false
    maxAge: 10m

文件也可显式引用环境变量:allowedOrigins: ["${WEB_ORIGIN}"]。一个数组元素代表一个来源,不把逗号分隔环境文本隐式拆成多个来源。JSON/YAML/XML 走同一加载和校验;非法配置定位 server.cors.allowedOrigins[0] 等字段、文件及可用环境名/行列,不回显值。

go run . -config etc/config.yaml -check-config
go run ./examples/cors # 在框架源码根目录运行独立示例

手写项目与独立 HTTP 服务

app := omni.New(omni.WithCORS(omni.CORSConfig{
    Enabled: true,
    AllowedOrigins: []string{"https://web.example.com"},
    AllowedMethods: []string{"GET", "POST"},
    AllowedHeaders: []string{"Authorization", "Content-Type"},
}))

已有 net/http 服务也可单独使用,不需要创建 App:

policy, err := cors.New(cors.Config{
    Enabled: true,
    AllowedOrigins: []string{"https://web.example.com"},
})
if err != nil { return err }
handler := policy.Wrap(existingHandler)

Config/New/WithCORS 在初始化时校验并快照配置;同一 Policy 可并发复用。Config.Validate() 返回 error,直接向 omni.New 传非法选项沿用框架初始化 panic 约定;生成项目在创建 App 前已校验。

配置规则

字段 规则
enabled 默认 false;关闭时直接使用原 Handler,不添加头、不拦截 OPTIONS
allowedOrigins 启用时必填。完整 HTTP(S) origin(协议+主机+可选端口),无路径、尾斜杠、userinfo、查询或片段。配置中的协议/主机转小写、默认 80/443 端口省略;请求匹配浏览器序列化后的来源
allowedMethods 空列表默认 GET、HEAD、POST;填写后使用指定集合。大写方法名,拒绝浏览器禁止的 CONNECT/TRACE/TRACK,不提供方法通配符
allowedHeaders 空列表不批准额外预检请求头。Authorization、JSON Content-Type 等按需要列出;名称忽略大小写,不提供通配符
exposedHeaders 默认空;额外允许前端 JS 读取的响应头,例如下载文件名用 Content-Disposition。浏览器本来就可读取的安全列表不需重复填写
allowCredentials 默认 false;跨源 Cookie 等凭据模式需要显式 true 和具体来源。单纯手动设置 Authorization 头只需列入 allowedHeaders,不必因此开启 Cookie 凭据模式
maxAge 浏览器缓存预检结果的时长,必须非负且为整秒;0 不发送该头。浏览器可按自身上限截短

公开无凭据 API 可使用唯一来源 "*";与 allowCredentials: true 或其他来源并存会报错。此版本只支持 HTTP(S) 来源,拒绝 null、子域通配符、正则、file:// 和不透明来源。国际化域名填写浏览器发送的 ASCII/Punycode 形式;IP 使用完整标准形式。不会为匹配来源查询 DNS。

预检、错误响应与安全边界

  • CORS 位于路由/业务中间件外层。只有 OPTIONS 且同时带 Origin 和 Access-Control-Request-Method 才视为预检;普通 OPTIONS 继续执行项目逻辑。
  • 合法预检返回 204 空响应,不进入业务中间件、鉴权、Logic 或业务响应包装。拒绝预检返回 403 空响应,不暴露内部原因;两者是协议响应,不受业务“失败也返回 HTTP 200”的设置影响。
  • 预检验证全局来源、方法、请求头策略,不探测路由存在性;通过不代表实际接口一定存在。实际请求仍由路由处理,可返回 404/405。
  • 实际请求继续正常鉴权;来源/方法获准时,正常响应、401/403、404/405、框架恢复的 500 都带 CORS 头,响应体、状态和错误目录不变。协议解析、TLS、网络层过载等在进入 App 前发生的拒绝不由本模块改写。
  • 未获准或没有 Origin 的实际请求仍正常进入业务,只是不添加 CORS 许可头。CORS 不等于身份认证、服务端访问控制或 CSRF 防护;服务端回调仍需项目自己的验签/鉴权。
  • 始终追加并去重 Vary: Origin;预检再追加请求方法/请求头的 Vary,保留原有 Vary 与 *。不要在下游代码覆盖这些头。代理与应用选择一处负责 CORS,避免重复头或冲突策略。
  • 不包装 ResponseWriter、不读取/缓存 body、不克隆 Request。下载、上传、Flush、Hijack 和请求上下文保留;不增加响应体大小限制或改写统一返回格式。
  • 使用 App、Engine、NewServer 或 Run/ServeContext/RunWith 接入。Router() 暴露底层路由供高级注册,单独拿 Router().ServeHTTP 服务会绕过应用的外层策略。
  • App 内业务日志/指标不统计被提前结束的预检;如需记录全部流量,在 App 外层或代理配置访问日志。不要让全局登录校验挡在 CORS 预检之前。

配置最多 1024 个来源、每个最多 4096 字节;方法/请求头/暴露头各最多 128 个且名称最多 256 字节;预检请求头列表最多 128 项、8192 字节。拒绝 CRLF、歧义的重复 Origin/请求方法及非法 token。WebSocket Origin 校验、Private Network Access 扩展和云桶 CORS 不包含在本模块中,按对应服务配置。

规则依据:Fetch CORS 协议、凭据规则、缓存规则。可运行示例:examples/cors。