
Yii2 中怎么设置 Cache-Control 和 Expires 响应头
直接在控制器里调用 Response::headers 就能控制,但得注意时机——必须在输出内容前设置,否则会报“Headers already sent”错误。
常见写法是:
Yii::$app->response->headers->set('Cache-Control', 'public, max-age=3600');
Yii::$app->response->headers->set('Expires', gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
注意两点:
• max-age 是秒数,别写成字符串或带单位
• Expires 必须是 GMT 时间格式,用 gmdate,不是 date
- 如果用了
yii\filters\HttpCache,它会自动覆盖你手动设的Cache-Control,优先级更高 - 开发时浏览器可能忽略缓存头(比如开了 DevTools 的 Network 标签页),建议关掉再测
- PHP 输出缓冲(
ob_start)开启时,头仍可设;但一旦echo或var_dump触发实际输出,就来不及了
为什么加了 HttpCache 还不返回 304
根本原因:Yii 默认只对 GET/HEAD 请求做 ETag/Last-Modified 校验,且要求响应内容稳定可复现。只要控制器里有随机值、时间戳、用户态数据,ETag 就总变,304 永远不会触发。
典型踩坑点:
-
time()、microtime()、uniqid()直接混进响应体 → ETag 计算结果每次不同 - 没显式配置
lastModified或etagSeed,Yii 用整个响应内容生成 ETag,稍有变动就失效 - 开启了调试模式(
YII_DEBUG = true),HttpCache会强制禁用缓存逻辑
实操建议:
public function behaviors()
{
return [
'httpCache' => [
'class' => 'yii\filters\HttpCache',
'lastModified' => function ($action, $params) {
return filemtime('@app/views/site/index.php'); // 稳定时间源
},
'etagSeed' => function ($action, $params) {
return 'v1.2'; // 固定种子,或基于关键数据哈希
}
],
];
}
HttpCache 和 Nginx 缓存头冲突怎么办
Yii 设置的 Cache-Control 会被 Nginx 的 add_header 覆盖(后者后执行),但 ETag/Last-Modified 不会——它们是响应体的一部分,Nginx 默认透传。
所以重点不是“谁管头”,而是“谁决定是否返回 304”。只要 Yii 正确生成并校验了 ETag,Nginx 就能转发 304,无需额外配置。
- 别在 Nginx 里用
expires指令强行设缓存时间,和 Yii 的max-age冲突时,以响应头中最后一个为准 - 如果 Nginx 启用了
proxy_buffering on,它可能缓存整个响应(包括 200),导致后续请求绕过 Yii 的 304 逻辑 —— 这时要关掉 buffering 或确保 proxy_cache_valid 包含 304 - CDN(如 Cloudflare)默认不缓存带 Cookie 或 Set-Cookie 的响应,即使 Yii 返回了
Cache-Control: public
调试缓存行为时最常漏看的三处
浏览器开发者工具 Network 面板只显示最终结果,容易忽略中间环节。真正卡住的地方往往在服务端链路末端。
- 检查响应头里有没有
X-Powered-By: Yii Framework—— 如果有,说明没走 Nginx 缓存,还在走 PHP;没有则可能是 Nginx 直接返回了缓存 - 用
curl -I http://yoursite.com看原始头,比浏览器更干净,尤其注意ETag是否一致、Last-Modified时间是否合理 - Yii 日志里搜
HTTP_CACHE关键字(需开启yii\filters\HttpCache的 debug 日志),能看到它是否执行了校验、为什么跳过了 304
ETag 不是魔法,它依赖确定性输出;304 不是开关,它是协商结果。所有环节都得稳住,少一个,缓存就断在半路。











