You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

浏览器收到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;

     }

问题原因

  1. Expires头配置无效:设置的Expires: 1不是符合HTTP标准的GMT格式日期,浏览器无法解析时会忽略该头,结合max-age=1的规则,收到304后可能错误判定缓存无需再验证。
  2. 304响应缺失缓存策略头:返回304时仅发送了Etag和Last-Modified,没有同步返回Cache-Control、Expires等核心缓存规则,浏览器无法维持一致的缓存验证逻辑。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 00:02:24