如何检查 jQuery.ajax() 请求头状态是否为 "304 Not Modified"?

47
如何检查jQuery.ajax()请求头状态是否为"304 Not Modified"?
通常jqXHR.status返回200,即使请求头是"304 Not Modified"。 ifModified:true并没有帮助太多,因为它会破坏XHR数据请求。

1
简短的回答是否定的。然而,这可能是确定jQuery .ajax()将字符串重定向到哪里的重复问题。虽然不是完全相同的问题,但概念是相同的——XHR规范不需要通知3xx响应。 - JAAulde
值得注意的是,您不需要手动进行ETag检查,从您的JavaScript中,浏览器会为您处理它。因此,除非您有特殊原因这样做,一般情况下您不需要检查304响应。 - Anentropic
5个回答

68
没有手动处理缓存头是不可能的。通常,XHR API不提供304响应:
对于由用户代理生成的条件请求导致的304未修改响应,用户代理必须表现得像服务器给出了带有适当内容的200 OK响应。
jQuery通常不知道有304响应,因为浏览器对JavaScript关于网络上实际发生的事情撒了一个善意的谎言。
但是有好消息(有点):您可以通过手动设置请求中的HTTP缓存标头If-Modified-SinceIf-None-Match来使Ajax产生304响应:
用户代理必须允许setRequestHeader()通过设置请求标头(例如If-None-MatchIf-Modified-Since)覆盖自动缓存验证,在这种情况下,必须传递304未修改响应。

所以,你可以使用如下代码:

var xhr = new XMLHttpRequest();
xhr.open("GET", "foo.html");
xhr.setRequestHeader("If-Modified-Since", "Fri, 15 Feb 2013 13:43:19 GMT");
xhr.send();

一个基本难点是如何确定要发送什么样的最后修改日期或ETag 浏览器有用于发送请求的缓存信息,但它不会与JavaScript共享该信息。幸运的是,jQuery跟踪Ajax响应中的Last-ModifiedETag头,因此您可以使用ifModified:true在下一次请求该资源时由jQuery设置这些头值。

需要注意两点:

  • 304响应不携带数据,这是设计如此。假设您已选择使用缓存,则应该已经在缓存中拥有数据的副本!如果从服务器获取不到数据是一个问题(即,因为您尚未拥有该数据),那么为什么要使用缓存?缓存应该用于当您手头有旧数据并且只想要新数据时; 因此,收到 304 响应没有数据不应该是一个问题。

  • jQuery 必须具有上次修改日期或 ETag(与 If-None-Match 一起使用)存储自上一个请求。流程如下:

    • 第一次获取:jQuery 没有缓存信息,因此不发送 If-Modified-Since 或 If-None-Match。当响应返回时,服务器可能会宣布上次修改日期或 ETag,jQuery 会将其存储以供将来使用。

    • 后续获取:jQuery 具有来自上次获取的缓存信息,并将该数据转发给服务器。如果资源没有更改,则 Ajax 请求获得 304 响应。如果资源已更改,则 Ajax 请求获得 200 响应,以及 jQuery 用于下一次获取的新缓存信息。

    • 但是,jQuery 不会在页面重新加载之间保留缓存信息(例如,在 cookie 中)。因此,在页面重新加载后的资源的第一次获取将永远不会是 304,因为 jQuery 没有缓存信息要发送(即,我们重置回“第一次获取”情况)。没有理由说明 jQuery 无法保留缓存信息,但目前它不这样做。

这里的关键是,你可以使用缓存头来获取JavaScript 304响应,但无法访问特定资源的浏览器ETag或最后修改日期。因此,浏览器本身可能了解有关资源的缓存信息,但您的JavaScript代码不知道。在这种情况下,浏览器将使用其缓存头来潜在地获得真正的304响应,但将向您的JavaScript代码转发200响应,因为JavaScript没有发送任何缓存信息。
不可能使JavaScript 304请求与实际网络304响应完全对齐,因为您的浏览器所知道的缓存信息和JavaScript代码所知道的缓存信息可能以不可预测的方式不同。但是,大多数实用开发需求中正确获取304请求已经足够好了。
例子
以下是一个简短的Node.js服务器示例(但应该很容易移植到其他语言):
require("http").createServer(function (req, res) {
  console.log(req.headers["if-modified-since"]);

  // always send Last-Modifed header
  var lastModDate = "Fri, 13 Feb 2013 13:43:19 GMT";
  res.setHeader("Last-Modified", lastModDate);

  // if the request has a If-Modified-Since header,
  //   and it's newer than the last modification,
  //   then send a 304 response; otherwise send 200
  if(req.headers["if-modified-since"] &&
     Date.parse(lastModDate) <= Date.parse(req.headers["if-modified-since"])) {
    console.log("304 -- browser has it cached");
    res.writeHead(304, {'Content-Type': 'text/plain'});
    res.end();
  } else {
    console.log("200 -- browser needs it fresh");
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('some content');
  }

}).listen(8080);

当运行此服务器时,您可以在浏览器中加载页面并在浏览器控制台中执行两个不同的测试:
var xhr = new XMLHttpRequest();
xhr.open("GET", "/");
xhr.send();
xhr.onload = function() { console.log(xhr.status); }

这个脚本将始终看到一个“200”响应,即使浏览器提供了一个“If-Modified-Since”请求头并且得到一个“304”响应(在浏览器看到服务器的“Last-Modifed”响应头之后,所有请求都会发生这种情况)。
相比之下,这个脚本将始终看到304响应:
var xhr = new XMLHttpRequest();
xhr.open("GET", "/");
xhr.setRequestHeader("If-Modified-Since", "Fri, 15 Feb 2013 13:43:19 GMT");
xhr.send();
xhr.onload = function() { console.log(xhr.status); }

该脚本提供了自己的If-Modified-Since请求头(在服务器上次修改日期两天后); 它不依赖于浏览器提供的If-Modified-Since,因此被允许(根据XHR规范)查看304响应。
最后,这个脚本总是会看到一个200:
var xhr = new XMLHttpRequest();
xhr.open("GET", "/");
xhr.setRequestHeader("If-Modified-Since", "Fri, 12 Feb 2013 13:43:19 GMT");
xhr.send();
xhr.onload = function() { console.log(xhr.status); }

这是因为脚本使用的If-Modified-Since早于服务器的最后修改日期,所以服务器总是发送200。服务器不会发送304,因为它假设客户端没有缓存最新版本的副本(即客户端宣布它已经看到自2月12日以来的更改,但显然没有看到2月13日的更改)。

жҲ‘з”ЁеҺҹз”ҹзҡ„XMLHttpRequest 2жЈҖжҹҘдәҶдҪ зҡ„If-Modified-Since+Last-Modifiedж–№жі•пјҢдёҚе№ёзҡ„жҳҜпјҢChromeжҖ»жҳҜиҝ”еӣһ200пјҢиҖҢFirefoxиҝ”еӣһ301зј“еӯҳз»“жһңгҖӮзңӢиө·жқҘпјҢеңЁXMLHttpRequestдёӯжІЎжңүжңүж•Ҳзҡ„зј“еӯҳйӘҢиҜҒеҸҜиғҪгҖӮ - Binyamin
@Binyamin 我已经重现了我回答中描述的行为,并添加了完整的服务器和客户端示例。您确定您的服务器已正确配置以响应最近的 If-Modified-Since 值发送 304 响应吗?当服务器实际发送 304 响应时,脚本才能观察到 304 响应。使用显式的 If-Modified-Since 允许脚本看到现有的 304 响应(而不是隐藏为 200);它不会在实际响应中生成不存在的 304 状态。 - apsillers
@apsillers,我刚刚给您发送了一封电子邮件,其中包含与问题相关的完整代码。希望您能有想法来解决这个问题。谢谢! - Binyamin
使用日期而非Last-Modified。如果Date小于请求执行时的时间,则响应是从缓存返回的。 - Sergey Ponomarev

0

如果响应来自缓存,头信息也将被缓存。我把这个当作一个机会。我的服务器发送一个唯一的MD5头信息。这与?query=time()没有相同的效果。现在,在JS端验证最新的MD5头信息和当前的头信息是否相同。如果你收到了与之前相同的MD5头信息,那么响应来自缓存。我忘记了所有关于JQuery的事情,但是发现它可以设置和读取头信息。

<?php
$oldMD5=filter_input(INPUT_SERVER,'HTTP_CONTENT_MD5',int);
if(!empty($oldMD5)){
  header('Content-MD5: '.(1+(int)$oldMD5));
}
--------------------------------------------------------------
<script>
var oldMD5=0;
//your Ajax implementation 
Ajax.setRequestHeader('Content-MD5',''+oldMD5);
var newMD5=parseInt(Ajax.getResponseHeader('Content-MD5'),10);
if(newMD5 && oldMD5<newMD5,10){
  oldMD5=newMD5;
  //not cached
}else{
  //cached
}

这有点滥用,因为MD5应该是一个哈希函数,用于验证“解码后的数据与最初发送的数据相同”。


-3

-5

也许试试这个?

$.ajax({
    url: 'your-url',
    dataType: 'script',
    complete: function(xhr) { 
        if (xhr.status == 304) return;
        // or better: if (xhr.status != 200) return;
        // your code goes here
    }
});

XHR的readystatechange事件在3xx响应中不会被触发,所以当status为304时,也不会进入complete状态。 - JAAulde

-5

打开浏览器的检查器,它会显示有关Ajax请求的信息,包括状态。

对于Chrome,请右键单击并选择检查元素,然后转到“网络”选项卡。

对于Firefox,只需使用Firebug并按照相同的步骤操作即可。


2
这只是帮助提问者作为一个观察者了解情况。我相信他希望他的程序能够“知道”并根据这些信息做出决策。 - JAAulde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接