Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make Microsoft XmlHttpRequest honor cache control directive

i'm issuing a request using MSXML's XmlHttpRequest object:

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

And the send succeeds, and i get my xml data.

Except that XmlHttpRequest didn't actually hit the network (i can see there no actual http request issued). And Process Monitor shows the file is actually being served from my cache:

enter image description here

So i want to instruct the XmlHttpRequest user agent that any cached content older than 0 seconds is too old. The standards way to do this is to add a request header:

Cache-Control: max-age=0 

to the send request:

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

And the send succeeds, and i get my xml data.

Except that XmlHttpRequest didn't actually hit the network (i can see there no actual http request issued). And Process Monitor shows the file is actually being served from my cache.

So what is wrong? Is max-age not doing what i think it does?

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

Other directives allow a user agent to modify the basic expiration mechanism. These directives MAY be specified on a request:

max-age
Indicates that the client is willing to accept a response whose age is no greater than the specified time in seconds. Unless max- stale directive is also included, the client is not willing to accept a stale response.

Which exactly what i want.

Is Cache-Control: max-age=0 not exactly what i want, or is MSXML's XmlHttpRequest object buggy?

Update One

This is the MSXML XmlHttpRequest COM object:

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

Update Two

The max-age directive is added by the client for all cache's to adhere to. From RFC:

The Cache-Control general-header field is used to specify directives that MUST be obeyed by all caching mechanisms along the request/response chain. The directives specify behavior intended to prevent caches from adversely interfering with the request or response. These directives typically override the default caching algorithms. Cache directives are unidirectional in that the presence of a directive in a request does not imply that the same directive is to be given in the response.

Max-age is not for the server; it makes no sense for a server. It is intended for all caching systems between the user and the server.

Update Three

From W3C XmlHttpRequest:

If the user agent implements a HTTP cache it should respect Cache-Control request headers set by the setRequestHeader() (e.g., Cache-Control: no-cache bypasses the cache). It must not send Cache-Control or Pragma request headers automatically unless the end user explicitly requests such behavior (e.g. by reloading the page).

Following their example, i tried using the no-cache directive:

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

And the XmlHttpRequest client still services requests completely from the cache, without querying the server at all.

The W3C says that if there is a cache, it must honor Cache-Control if it is set through setRequestHeader. Microsoft's XmlHttpRequest doesn't seem to honor that requirement.

like image 857
Ian Boyd Avatar asked Mar 08 '11 16:03

Ian Boyd


2 Answers

Unfortunately the XMLHttpRequest object was designed this way, because it is based on WinInet. Also, it is not recommend to be used from the server side. You should use ServerXMLHttpRequest, which has the same functionality, but depends on WinHTTP instead. See the FAQ for more information. A description from the ServerXMLHttp documentation states that:

The HTTP client stack offers longer uptimes. WinInet features that are not critical for server applications, such as URL caching, auto-discovery of proxy servers, HTTP/1.1 chunking, offline support, and support for Gopher and FTP protocols are not included in the new HTTP subset.

This means that rather than using 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(); 

you can use 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(); 

or 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(); 
like image 175
Garett Avatar answered Oct 26 '22 14:10

Garett


I found that using If-None-Match header, specifing a value that does not match the ETag of the last request will work.

Eg:

req.open("GET", url, false); req.setRequestHeader("If-None-Match", "\"doesnt-match-anything\""); req.send(); 

This might or might not require that the responses include an ETag. (I only tried it with a service that includes an ETag value in each response.)

like image 35
J. Mullaney Avatar answered Oct 26 '22 14:10

J. Mullaney