正确实现PHP API浏览器缓存需同时设置Cache-Control与ETag/Last-Modified校验逻辑:手动比对If-None-Match或If-Modified-Since,生成稳定ETag(如md5内容)并规范处理引号与弱校验,Last-Modified须用GMT格式时间戳且精度为秒,二者可共存但需全部校验通过才返回304。

怎么让 PHP API 响应走浏览器缓存
核心是控制 Cache-Control 和配合校验头,而不是靠 PHP 框架自动塞值。很多开发者以为设了 header('Cache-Control: public, max-age=3600') 就完事,结果发现每次请求还是 200 —— 因为没配校验逻辑,浏览器根本不会发 If-None-Match 或 If-Modified-Since。
必须手动判断请求头 + 计算响应标识(ETag / Last-Modified),再决定返回 200 还是 304。PHP 不会替你做这个决策。
- 只设
Cache-Control不设校验头 → 浏览器缓存但不校验,过期才重发请求 - 设了
ETag或Last-Modified但没检查请求头 → 每次都 200,白搭 - 两个校验头一起设 → 浏览器优先用
If-None-Match(ETag),If-Modified-Since仅作 fallback
PHP 怎么正确生成和比对 ETag
ETag 是服务端生成的资源唯一标识,推荐用内容哈希(如 md5($data))或版本号(如数据库记录更新时间戳),别用随机值或固定字符串。重点在于:它得稳定、可复现、能反映内容变化。
比对时不能直接用 ==,要处理引号包裹(RFC 要求 ETag 带双引号)、弱校验(W/"xxx")等边界情况。PHP 没内置解析函数,得自己 trim 和忽略弱标记。
立即学习“PHP免费学习笔记(深入)”;
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
$clientEtag = trim($_SERVER['HTTP_IF_NONE_MATCH'], '"');
$serverEtag = md5($responseData);
if ($clientEtag === $serverEtag) {
header('HTTP/1.1 304 Not Modified');
exit;
}
}
- 生成 ETag 时避免用
time()或microtime()—— 它们会导致每次响应不同,缓存失效 - 不要在 ETag 里暴露敏感信息(如用户 ID、路径明文),哈希前可加 salt
- 如果响应体很大,别用
md5(file_get_contents(...)),改用流式 hash 或缓存预计算的 ETag
Last-Modified 头该用什么时间戳
Last-Modified 必须是 GMT 格式的时间字符串,不是 Unix 时间戳。常见错误是直接 echo time() 或 date('Y-m-d H:i:s') —— 这会导致响应头非法,浏览器忽略该字段。
它适合资源有明确“最后变更时间”的场景,比如读取文件用 filemtime(),查数据库用更新时间字段。但注意:如果内容逻辑上没变(比如模板渲染数据相同),但时间戳变了,就会误判为新内容,破坏缓存。
- 生成写法必须是:
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $timestamp) . ' GMT'); - 比对
If-Modified-Since时,PHP 不会自动解析,需用strtotime()转成时间戳再比较(注意时区,一律用 GMT 输入) - 精度只有秒级,1 秒内多次更新会被视为同一版本;高频更新场景建议只用 ETag
ETag 和 Last-Modified 能不能同时用
可以,而且推荐:浏览器会同时发送 If-None-Match 和 If-Modified-Since,PHP 需要两个都检查。但注意校验顺序 —— RFC 规定只要任一校验失败,就必须返回 200;只有两个都通过,才能返回 304。
实际中,ETag 优先级更高(精确到字节),Last-Modified 是兜底。比如某接口内容不变但数据库时间戳被误更新,ETag 不变仍可命中 304;反之若 ETag 计算出错,Last-Modified 还能保底。
- 别只检查一个就 return 304,漏掉另一个等于留缓存漏洞
- 如果资源没有天然修改时间(比如聚合多个 API 的结果),就别硬塞
Last-Modified,只用 ETag 更安全 - Apache/Nginx 可能自动加
Last-Modified(如静态文件),但 PHP 输出的动态响应不会,必须手动 set
最麻烦的不是写这几行 header,而是 ETag 的生成逻辑要和业务数据变更严格对齐 —— 数据库字段改了但没更新 ETag 计算规则,缓存就变成脏数据了。











