如何使Microsoft XmlHttpRequest遵守缓存控制指令

34

我正在使用MSXML的XmlHttpRequest对象发出请求:

IXMLHttpRequest http = new XmlHttpRequest();
http.open("GET", "http://www.bankofcanada.ca/stat/fx-xml.xml", False, "", "");
http.send();

当然send方法成功执行,我也得到了XML数据。

但是XmlHttpRequest实际上并没有发送网络请求(我可以看到没有实际的HTTP请求),而且进程监视器显示文件实际上是从我的缓存中提供的:

enter image description here

所以我想让XmlHttpRequest用户代理知道任何缓存内容超过0秒就太旧了。按照标准方式,需要添加一个请求头来实现这一点:

Cache-Control: max-age=0

发送请求的代码:

http = new XmlHttpRequest();
http.open("GET", "http://www.bankofcanada.ca/stat/fx-xml.xml", False, "", "");
http.setRequestHeader("Cache-Control", "max-age=0");
http.send();

发送请求成功并获取了XML数据,但XmlHttpRequest没有实际访问网络(我可以看到没有发出实际的HTTP请求)。而且"Process Monitor"显示文件实际上是从我的缓存中提供的。

那么问题出在哪里呢?是max-age不做我想做的吗?

来自RFC 2616 - Hypertext Transfer Protocol,Part 14:Header Field Definitions

其他指令允许用户代理修改基本过期机制。这些指令可以在请求中指定:

max-age
表示客户端愿意接受其年龄不超过指定时间(以秒为单位)的响应。除非还包括max-stale指令,否则客户端不愿意接受陈旧的响应。

这正是我想要的。

Cache-Control:max-age = 0不完全是我想要的吗?还是MSXML的XmlHttpRequest对象存在错误?

更新一

这是MSXML XmlHttpRequest COM对象:

  • CLSID:{88d96a0a-f192-11d4-a65f-0040963251e5}
  • ProgID:Msxml2.XMLHTTP.6.0

更新二

max-age指令由客户端为所有缓存添加以遵循。来自RFC:

Cache-Control通用头字段用于指定必须遵守请求/响应链上所有缓存机制的指令。这些指令指定旨在防止缓存与请求或响应产生不利干扰的行为。这些指令通常会覆盖默认的缓存算法。高速缓存指令是单向的,因为请求中存在指令并不意味着相同的指令也应该在响应中给出。

Max-age不适用于服务器;对于服务器而言是没有意义的。它是针对用户和服务器之间的所有缓存系统。

更新三

来自W3C XmlHttpRequest

如果用户代理实现了HTTP缓存,则应尊重由setRequestHeader()设置的Cache-Control请求标头(例如,Cache-Control:no-cache跳过缓存)。它不应该自动发送Cache-ControlPragma请求标头,除非最终用户明确请求这种行为(例如通过重新加载页面)。

遵循他们的示例,我尝试使用no-cache指令:

http = new XmlHttpRequest();
http.open("GET", "http://www.bankofcanada.ca/stat/fx-xml.xml", False, "", "");
http.setRequestHeader("Cache-Control", "no-cache");
http.send();

XmlHttpRequest客户端仍然完全从缓存中服务请求,而不会查询服务器。

根据W3C的规定,如果存在缓存,则必须遵守Cache-Control(如果通过setRequestHeader设置了该值)。然而,微软的XmlHttpRequest似乎不遵守这个要求。

10个回答

25

不幸的是,XMLHttpRequest 对象是基于 WinInet 设计的,因此它被设计成这样。另外,不建议从服务器端使用它。你应该使用具有相同功能但依赖于 WinHTTPServerXMLHttpRequest。有关更多信息,请参见FAQ。来自 ServerXMLHttp 文档的描述如下:

HTTP 客户端堆栈提供更长的正常运行时间。不适用于服务器应用程序的 WinInet 功能,例如 URL 缓存、代理服务器的自动发现、HTTP/1.1 分块、离线支持以及对 Gopher 和 FTP 协议的支持都未包含在新的 HTTP 子集中。

这意味着,与其使用 XmlHttpRequest:

IXMLHTTPRequest http = CreateComObject("Msxml2.XMLHTTP.6.0");     http.open("GET", "http://www.bankofcanada.ca/stat/fx-xml.xml", False, "", "");
http.setRequestHeader("Cache-Control", "max-age=0");
http.send();

您可以使用 ServerXmlHttpRequest

IXMLHTTPRequest http = CreateComObject("Msxml2.ServerXMLHTTP");
http.open("GET", "http://www.bankofcanada.ca/stat/fx-xml.xml", False, "", "");
http.setRequestHeader("Cache-Control", "max-age=0");
http.send();

或者 WinHttpRequest

IWinHttpRequest http = CreateComObject("WinHttp.WinHttpRequest.5.1");
http.open("GET", "http://www.bankofcanada.ca/stat/fx-xml.xml", False, "", "");
http.setRequestHeader("Cache-Control", "max-age=0");
http.send();

你是对的。我刚刚使用 ServerXmlHttpRequestWinHttpRequest 进行了测试。这两个都不会进行任何缓存。而 XmlHttpRequest 确实执行缓存,但它不遵循 W3C 的规范来绕过该缓存的请求。+1 并接受。 - Ian Boyd
我尝试使用WinHttp.WinHttpRequest.5.1和MSXML2.ServerXMLHTTP.6.0,但对我没有用...那可能是服务器设置或其他什么问题吗? - thiagoleite
@thiagoleite 你遇到了什么问题?你使用的是哪个操作系统?你收到了错误信息吗? - Garett
@Garret 这是一个由5个Windows Server 2008 64位和IIS7组成的Webfarm。我在ASP Classic中创建了一个代理来消费RSS Feed,它只是发出请求并解析XML。我没有收到任何错误信息,只是当我在Feed中进行更新时,我的代理无法获取更新后的信息。我在某个地方读到过关于ServerXmlHttpRequest存在缓存bug的信息,但我记不清在哪里看到的了。 - thiagoleite
@thiagoleite 你好,这可能需要一个新的问题来获取更多反馈。这很可能是一个错误,但我也想看到更多细节,包括您正在使用的代码片段。 - Garett

8
我发现使用If-None-Match头部,指定一个与上次请求的ETag不匹配的值可以起作用。
例如:
req.open("GET", url, false);
req.setRequestHeader("If-None-Match", "\"doesnt-match-anything\"");
req.send();

这可能需要响应包含一个 ETag ,也可能不需要。(我只在包含 ETag 值的服务中尝试过。)


1

我使用这个来保持会话活动,效果非常好。
诀窍是使用带有比浏览器缓存的更新值的标题"If-Modified-Since"。

g_AjaxObj.onreadystatechange = function() { if(g_AjaxObj.readyState === 4) { AjaxOnComplete_("KeepAlive"); }};
g_AjaxObj.open('GET', URL, true);
g_AjaxObj.setRequestHeader("If-Modified-Since", new Date().toUTCString());
g_AjaxObj.send(null);

1
If-Modified-Since 不是我想要的相反效果。加入 If-Modified-Since 允许服务器返回 304 Not Modified。我想要绕过缓存 - 而不是与其一起工作。 - Ian Boyd

1

你能否在URI的末尾添加一个虚假参数,并且每次请求时更改它吗?

http.open("GET", "http://www.bankofcanada.ca/stat/fx-xml.xml?requestID=42", False, "", "");

4
问题在于它并未绕过缓存,而只是发出了另一个请求;这会使我的缓存中充满了数千个相同资源的副本。我希望知道如何按照缓存规则进行操作。 - Ian Boyd

1
我的快速而简单的解决方法是在标准Windows客户端上进行以下操作:
- Internet选项
- 常规
- 浏览历史设置
- 检查存储页面的新版本:
勾选“(x)每次访问网页时”
现在我的Msxml2.XMLHTTP.x.0对象不再使用缓存...

0

尝试将'cache-control: private'作为标头发送。这对我有用:

var request = new XMLHttpRequest();
request.open("GET", 'http://myurl.com' , false); 

request.setRequestHeader("cache-control", "private");

我正在为Windows 8编写一个HTML和Javascript应用程序,其中无论是no-cache还是max-age都被忽略了。对我来说,上述内容都很好用。

我之前不熟悉这个头部信息,所以对cache-control: private进行了一些调查...

Indicates that all or part of the response message is intended for a single user and MUST NOT be cached by a shared cache, such as a proxy server.

来自 什么是Cache-Control: private?http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html

基本上,这不会创建缓存条目,因此不会添加我们知道是多余的缓存条目,如“cache-buster”随机数参数。


0
缺点是会在缓存中洪泛多个相同内容的副本。这可能是解决有缺陷的HTTP代理的一种方法,但真正的解决方案是与缓存机制合作,而不是对抗它们。
我同意这不是理想的解决方案,但Mozilla实际上建议这样做作为解决方法,所以我认为它不会太糟糕-https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest 此外,我曾经为解决这个问题而苦苦挣扎。我不得不依赖用户清除他们的浏览器缓存(他们总是忘记这样做)。所以这对我来说是一个救星!

0

这让我疯狂了。这个SO线程最接近提供答案。不幸的是,在测试期间,它们中的任何一个都没有真正为我工作。我发现的唯一正确工作的解决方案是设置:

Header Pragma: no-cache

我希望它能帮助其他遇到IE头痛的人。

顺便说一句,这个StackOverflow线程很好地阐明了Pragma和Cache-control之间的区别: Pragma和Cache-control标头之间的区别是什么?


0

这个头部是为了服务器而设计的,因为浏览器并没有发出任何请求,所以它是无用的。

一个简单的技巧是像这样加载页面:

http.open("GET", "http://www.bankofcanada.ca/stat/fx-xml.xml?"+Math.random(), False, "", "");

问题在于它并不能绕过缓存,而只是发出了一个单独的请求;这将会在我的缓存中填充成千上万次相同资源的副本。我想知道如何遵循缓存规则。 - Ian Boyd
服务器只在响应中发送一个“etag”头,因此缓存是纯客户端的。您可以在选项->Internet选项->临时文件->始终检查新文件中强制IE不进行缓存。 - jujule
这不是来自ie,而是使用XmlHttpRequest的本地应用程序。 - Ian Boyd
在我看来,本地的XMLHttpRequest应用程序使用IE缓存设置,因为它们使用IE代理。 - jujule
我不能在运行我的应用程序的每台机器上更改缓存策略(那样就不是一个好程序)。此外,W3C 规定如果我在请求中指定了 Cache-Control 标头,则 XmlHttpRequest 实现应该遵守它。 - Ian Boyd

0

1
缺点是会在缓存中洪泛多个相同内容的副本。这可能是对于有缺陷的HTTP代理的一种解决方法,但真正的解决方案是与缓存机制合作,而不是反对它们。 - Ian Boyd

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