加了 Access-Control-Max-Age 却仍每次发 OPTIONS,是因为 Apache 的 Header set 不作用于 OPTIONS 预检响应,必须用 Header always set 并在 <Limit OPTIONS> 块中显式配置,且需确保 Origin、Method、Headers 完全一致才能缓存。

加了 Access-Control-Max-Age 却还是每次请求都发 OPTIONS?问题大概率出在:Apache 默认不给 OPTIONS 响应加这个头,哪怕你在 <Directory> 里写了也没用。
为什么 Header set Access-Control-Max-Age 对预检无效
Header set 只作用于主响应(比如 GET/POST 的 200),而预检是 OPTIONS 请求,由 Apache 内部模块(如 mod_proxy、静态文件处理器)直接生成响应,绕过常规响应头注入流程。所以你看到的 OPTIONS 响应里压根没有 Access-Control-Max-Age,浏览器自然无法缓存。
-
Header always set才能穿透内部模块,在所有响应(包括OPTIONS)上强制注入头 - 必须把
Header always set显式绑定到OPTIONS方法上,否则仍可能被忽略 - 如果用了反向代理(
mod_proxy),还要确认它没过滤或重写掉Access-Control-*头
正确配置 OPTIONS 预检响应头的写法
在 <VirtualHost> 或站点级配置中,用 <Limit OPTIONS> 包裹,并坚持用 always set:
Apache Superset 是一个广泛采用的开源 BI 平台,用于 SQL 探索、图表构建和仪表板交付。当代理需要查询仓库数据、组装仪表板或使用成熟的分析界面解释指标而不是临时笔记本代码时,此技能非常有用。
<IfModule mod_headers.c>
<Limit OPTIONS>
Header always set Access-Control-Allow-Origin "https://your-frontend.com"
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
Header always set Access-Control-Max-Age "86400"
Header always set Access-Control-Allow-Credentials "true"
</Limit>
</IfModule>
- 别用
<Location>或<Directory>块里的Header set—— 它对OPTIONS基本失效 -
Access-Control-Allow-Origin和Access-Control-Allow-Credentials: true不能共存于*,否则浏览器直接拒收缓存;生产环境务必写具体域名 - 确保
mod_headers已启用:apache2ctl -M | grep headers(Debian/Ubuntu)或httpd -M | grep headers(RHEL/CentOS)
为什么预检缓存还是不生效?检查这几点
即使服务端头全对,浏览器也可能拒绝缓存,常见原因:
- 后续实际请求的
Origin和预检时不同(比如从http://localhost:3000切到https://dev.example.com) - 实际请求方法(如
PUT)没出现在预检响应的Access-Control-Allow-Methods列表里 - 前端多发了一个未声明的 header(比如漏配
X-Auth-Token),导致预检失败后降级为每次都发OPTIONS - CDN 或中间代理清除了
Access-Control-Max-Age头(可抓包对比原始响应和浏览器收到的响应)
最常被忽略的是:预检缓存只认“完全一致”的请求特征——Origin、Method、Headers 三者缺一不可。改一个字母、换一个协议、多一个 header,缓存就作废。别指望它“差不多就行”。

















