浏览器收到304后不再发送If-Modified-Since/If-None-Match请求头问题
浏览器收到304响应后不再发送If-Modified-Since/If-None-Match请求头的解决方法
问题背景
本地LAMP(PHP)开发环境,测试浏览器为Chromium和Librewolf(Firefox衍生版),二者表现一致:首次重请求时会正常发送If-Modified-Since和/或If-None-Match请求头,收到服务器返回的304 Not Modified响应后,后续重请求不再发送这些验证头,导致缓存验证机制失效,推测是HTTP头配置存在问题。
当前服务器返回的响应头
自定义PHP代码发送的头
"Connection : close" "Content-Type : text/html; charset=UTF-8" "Date : ".gmdate("D, d M Y H:i:s")." GMT"; "Last-Modified : ".$lastmod; "Etag : ".$etag; "Expires : 1" //can't have the browser doesn't check if file was modified on server "Pragma : public" "Cache-Control : max-age=1,must-revalidate"
ob_start("ob_gzhandler")发送的头
Content-Encoding : gzip
Apache服务器发送的头
Connection : Keep-Alive Keep-Alive : timeout=5, max=99 Server : Apache/2.4.46 (Unix) OpenSSL/1.1.1j PHP/8.0.3 mod_perl/2.0.11 Perl/v5.32.1 Transfer-Encoding : chunked Vary : Accept-Encoding X-Powered-By : PHP/8.0.3
服务器端缓存校验代码
if ( ((isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $_SERVER['HTTP_IF_MODIFIED_SINCE'] == $lastmod ) || (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) ) ) { header("HTTP/1.1 304 Not Modified"); header("Content-Length:0"); header('Etag:'. $etag); header('Last-Modified:'.$lastmod); exit; }
问题原因
Expires头配置无效:设置的Expires: 1不是符合HTTP标准的GMT格式日期,浏览器无法解析时会忽略该头,结合max-age=1的规则,收到304后可能错误判定缓存无需再验证。- 304响应缺失缓存策略头:返回304时仅发送了
Etag和Last-Modified,没有同步返回Cache-Control、Expires等核心缓存规则,浏览器无法维持一致的缓存验证逻辑。 Connection头冲突:自定义代码发送Connection: close,但Apache又自动添加Connection: Keep-Alive,头信息冲突可能干扰浏览器对缓存规则的解析。
解决方案
1. 修正Expires头格式
将Expires设置为标准GMT格式,与max-age=1的规则保持一致:
"Expires : ".gmdate("D, d M Y H:i:s", time() + 1)." GMT";
2. 统一Connection头
删除PHP代码中发送Connection: close的语句,保留Apache默认的Keep-Alive配置,避免头信息冲突。
3. 304响应中补充完整缓存策略
返回304时,除了Etag和Last-Modified,还要重新发送缓存策略头,确保浏览器始终明确验证规则:
if ( ((isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $_SERVER['HTTP_IF_MODIFIED_SINCE'] == $lastmod ) || (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) ) ) { header("HTTP/1.1 304 Not Modified"); // 重新发送缓存策略头 header("Cache-Control: max-age=1, must-revalidate"); header("Expires: ".gmdate("D, d M Y H:i:s", time() + 1)." GMT"); header('Etag:'. $etag); header('Last-Modified:'.$lastmod); // 304响应无需设置Content-Length:0,规范中304不应包含实体内容 exit; }
4. 优化缓存校验逻辑
当前逻辑是互斥校验(二选一),但浏览器可能同时发送两个验证头,修改为同时校验、任一通过即返回304的逻辑,更符合HTTP规范:
$needUpdate = true; // 校验If-Modified-Since if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $_SERVER['HTTP_IF_MODIFIED_SINCE'] == $lastmod) { $needUpdate = false; } // 校验If-None-Match,优先级高于If-Modified-Since if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) { $needUpdate = false; } if (!$needUpdate) { header("HTTP/1.1 304 Not Modified"); header("Cache-Control: max-age=1, must-revalidate"); header("Expires: ".gmdate("D, d M Y H:i:s", time() + 1)." GMT"); header('Etag:'. $etag); header('Last-Modified:'.$lastmod); exit; }
内容的提问来源于stack exchange,提问作者mikakun
相关产品推荐
相关产品推荐

