47

如何检查jQuery.ajax()请求标头状态是否为“304 未修改”?

jqXHR.status200即使请求的标头是“304 Not Modified”,通常也会返回。

ifModified:true 没有太大帮助,因为它破坏了 XHR 数据请求。

4

5 回答 5

68

如果不手动处理缓存头,这是不可能的。通常,无法通过XHR API获得 304 响应:

对于作为用户代理生成的条件请求的结果的 304 Not Modified 响应,用户代理必须像服务器给出带有适当内容的 200 OK 响应一样行事

jQuery 通常不知道有 304 响应,因为浏览器会礼貌地向 JavaScript 撒谎,告诉 JavaScript 网络上实际发生的事情。

但是有一个好消息(有点):您可以让 Ajax 产生 304 响应,但只能通过手动设置HTTP 缓存标头 If-Modified-SinceIf-None-Match在请求中:

用户代理必须允许setRequestHeader()通过设置请求标头(例如,,)来覆盖自动缓存验证If-None-MatchIf-Modified-Since在这种情况下,必须传递 304 Not Modified 响应。

因此,您可以使用如下代码:

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-Modified和标头,因此您可以使用jQuery 在下次发送对该资源的请求时设置这些标头值。ETagifModified:true

对此有两点需要注意:

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

  • jQuery 必须有一个上次修改日期或一个 ETag(与 一起使用If-None-Match)存储自先前请求。过程是这样的:

    • 第一次获取:jQuery 没有缓存信息,所以它不会发送If-Modified-SinceIf-None-Match. 当响应返回时,服务器可能会宣布最后修改的数据或 ETag,jQuery 将其存储以供将来使用。

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

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

这里的底线是您可以使用缓存标头来获取 JavaScript 304 响应,但您不能访问浏览器自己的ETag 或特定资源的最后修改日期。因此,浏览器本身可能知道有关资源的缓存信息,但您的 JavaScript 代码不知道。在这种情况下,浏览器将使用其缓存标头来潜在地获得真正的 304 响应,但将 200 响应转发到您的 JavaScript 代码,因为 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); }

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

于 2012-09-19T19:39:04.690 回答
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 应该是用于验证“解码数据与最初发送的数据相同”的哈希值。

于 2014-09-09T13:32:22.873 回答
-3

我在https://github.com/laukstein/ajax-seo/blob/cbed03222d89f6f7e75dc49ce8882c30201bbb86/index.php#L266-274有一种实现

var cache;

$.ajax({
    ...
    beforeSend: function() {
        cache = setTimeout(function() {
            console.log('The content not cached jet.');
        }, 300);
    },
    success: function(data) {
        if (cache) {
            clearTimeout(cache);
        }
    }
});
于 2012-09-20T07:16:37.657 回答
-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
    }
});
于 2011-03-02T21:29:24.000 回答
-5

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

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

对于 firefox,只需使用 firebug 并按照相同的步骤操作。

于 2012-03-23T14:07:30.163 回答