Hard refresh and XMLHttpRequest caching in Internet Explorer/Firefox - javascript

I make an Ajax request in which I set the response cacheability and last modified headers:
if (!String.IsNullOrEmpty(HttpContext.Current.Request.Headers["If-Modified-Since"]))
{
HttpContext.Current.Response.StatusCode = 304;
HttpContext.Current.Response.StatusDescription = "Not Modified";
return null;
}
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.Public);
HttpContext.Current.Response.Cache.SetLastModified(DateTime.UtcNow);
This works as expected. The first time I make the Ajax request, I get 200 OK. The second time I get 304 Not Modified.
When I hard refresh in Chrome (Ctrl+F5), I get 200 OK - fantastic!
When I hard refresh in Internet Explorer/Firefox, I get 304 Not Modified. However, every other resource (JS/CSS/HTML/PNG) returns 200 OK.
The reason is because the "If-Not-Modified" header is sent for XMLHttpRequest's regardless of hard refresh in those browsers. I believe Steve Souders documents it here.
I have tried setting an ETag and conditioning on "If-None-Match" to no avail (it was mentioned in the comments on Steve Souders page).
Has anyone got any gems of wisdom here?
Thanks,
Ben
Update
I could check the "If-Modified-Since" against a stored last modified date. However, hopefully this question will help other SO users who find the header to be set incorrectly.
Update 2
Whilst the request is sent with the "If-Modified-Since" header each time. Internet Explorer won't even make the request if an expiry isn't set or is set to a future date. Useless!
Update 3
This might as well be a live blog now. Internet Explorer doesn't bother making the second request when localhost. Using a real IP or the loopback will work.

Prior to IE10, IE does not apply the Refresh Flags (see http://blogs.msdn.com/b/ieinternals/archive/2010/07/08/technical-information-about-conditional-http-requests-and-the-refresh-button.aspx) to requests that are not made as a part of loading of the document.
If you want, you can adjust the target URL to contain a nonce to prevent the cached copy from satisfying a future request. Alternatively, you can send max-age=0 to force IE to conditionally revalidate the resource before each reuse.
As for why the browser reuses a cached resource that didn't specify a lifetime, please see http://blogs.msdn.com/b/ie/archive/2010/07/14/caching-improvements-in-internet-explorer-9.aspx

The solution i came upon for consistent control was managing the cache headers for all request types.
So, I forced standard requests the same as XMLHttpRequests, which was telling IE to use the following cache policy: Cache-Control: private, max-age=0.
For some reason, IE was not honoring headers for various requests types. For example, my cache policy for standard requests defaulted to the browser and for XMLHttpRequests, it was set to the aforementioned control policy. However, making a request to something like /url as a standard get request, render the result properly. Unfortunately, making the same request to /url as an XMLHttpRequest, would not even hit the server because the get request was cached and the XMLHttpRequest was hitting the same url.
So, either force your cache policy on all fronts or make sure you're using different access points (uri's) for your request types. My solution was the former.

Related

Using XHR to precache resources not behaving as expected

I'm simply trying to use XHR to precache some resources, but the cache is not behaving as expected.
Here are the bounds of the problem:
I know the resource URLs in advance (of course).
I don't know their content-types (mix of CSS, images, and other).
They will always be same-origin.
I control the cache headers.
They can be cached forever for all I care.
I've always been under the impression that XHR used the browser cache more or less like any other resource, but never rigorously tested that. Here's what I'm doing:
Request all resources up-front with XHR.
Explicitly set request header Cache-Control: max-age=3600 (Chrome was setting max-age=0 for some reason).
Set the following response headers on the server:
Cache-control: public; max-age=3600
Date: now
Expires: now + 1 hour
[Content-Type, Content-Length]
Here's what I'm seeing:
XHR always fetches the resource (confirmed on server and with dev tools).
Subsequent requests (via image/css/etc elements) always fetch (even after the XHRs have completed) on a cold cache.
But they always use the cache when it's warm.
I've poked at it in various ways, but this behavior never seems to change.
After much wailing and gnashing of teeth, I believe I've proven that this approach simply won't work on all browsers. Here's my experience thus far:
Firefox: Works a charm.
Chrome: Seldom works -- it's as though XHR uses a different cache than the elements (even though I'm pretty sure that's not the case; I haven't had time to delve into Chrome code to figure out exactly what's going on.
Safari: Apparently random. Sometimes resource requests kicked off from elements retrieve from the cache, sometimes not. I'm sure there's a method, but it appears to be madness from the outside.
In the end, I had to switch to the somewhat more craptastic-but-reliable approach of creating a hidden iframe, injecting script/img elements into it, then waiting on the iframe window's onload event. This works, but gives no fine-grained feedback in terms of which elements are loaded (getting reliable onload events from the individual elements is more "cross-browser tricky" than just waiting on the whole frame.
I'd love to understand more precisely what's going on in Chrome/Safari, but sadly don't have the time to dig in further.

Make REST call in JavaScript without using JSON?

(extremely ignorant question, I freely admit)
I have a simple web page with a button and a label. When I click the button, I want to make a REST call to an entirely different domain (cross-domain, I know that much) and display the results (HTML) in the label.
With other APIs, I've played around with using JSON/P and adding a element on the fly, but this particular API doesn't support JSON so I'm not sure how to go about successfully getting through.
The code I have is:
function getESVData() {
$.get('http://www.esvapi.org/v2/rest/passageQuery?key=IP&passage=John+1', function (data) {
$('#bibleText').html(data);
app.showNotification("Note:", "Load performed.");
});
}
I get an "Access denied." Is there anyway to make this call successfully without JSON?
First off, JSON and JSONP are not the same. JSON is a way of representing information, and JSONP is a hack around the same-origin policy. JSONP works by requesting information from another domain, and that domain returns a script which calls a function (with the name you provided) with the information. You are indeed executing a script on your site that another domain gave to you, so you should trust this other domain.
Now when trying to make cross domain requests you basically have 3 options:
Use JSONP. This has limitations, including the fact that it only works for GET requests, and the server you are sending the request to has to support it.
Make a Cross Origin Resource Sharing (CORS) request. This also must be supported by the server you are sending the request to.
Set up a proxy on your own server. In this situation you set an endpoint on your site that simply relays requests. ie you request the information from your server, your server gets it from the other server and returns it to you.
For your situation, it the other server doesn't have support for other options, it seems like you will have to go with options 3.

How to tell if an XMLHTTPRequest hit the browser cache

If it possible to tell (within javascript execution) if a GET XMLHTTPRequest hit the browser cache instead of getting its response from the server?
From the XMLHttpRequest spec:
For 304 Not Modified responses that are a result of a user agent
generated conditional request the user agent must act as if the server
gave a 200 OK response with the appropriate content.
In other words, the browser will always give status code 200 OK, even for requests that hit the browser cache.
However, the spec also says:
The user agent must allow author request headers to override automatic cache
validation (e.g. If-None-Match or If-Modified-Since), in which case
304 Not Modified responses must be passed through.
So, there is a workaround to make the 304 Not Modified responses visible to your JavaScript code.
When making an ajax request, You get the response code
if (request.readyState == 4) {
if (request.status == 200) { // this number.
...
status 200 means you are getting a fresh copy of the data:
The request has succeeded. The information returned with the response is dependent on the method used in the request -
status 304 means the data has not changed and you will get it from the browser cache:
If the client has performed a conditional GET request and access is allowed, but the document has not been modified, the server SHOULD respond with this status code.
Read more on Status Code
Update:
You can add a cache buster to your URL to guarantee that you always hit the server:
var ajaxUrl = "/path?cache="+(Math.random()*1000000);
From http://www.w3.org/TR/2012/WD-XMLHttpRequest-20121206/
For 304 Not Modified responses that are a result of a user agent
generated conditional request the user agent must act as if the server
gave a 200 OK response with the appropriate content. The user agent
must allow author request headers to override automatic cache
validation (e.g. If-None-Match or If-Modified-Since), in which case
304 Not Modified responses must be passed through. [HTTP]
I find this rather vague. My assumption would be if a resource is conditionally requested, you would see the 304 response code. But, as I explained in another comment (source: https://developers.google.com/speed/docs/best-practices/caching), there might not even be a request if the last response server http header for that resource had set Cache-Control: max-age or Expires set sometime in the future. In this case, I'm not sure what ought to happen.
This answer is based on the assumption that you mean browser only cache, with no 304's taking place (modified-since, etag etc).
Check how long the request took - if it was resolved from cache then it should take close to 0ms.
Do you use Firefox's Firebug?
Firebug has a "Net" panel with an "XHR" filtered view. You should be able to inspect the cache info via the request phase bar, checking the status and/or clicking the triangle to inspect "Headers".
Cached or not cached
Not all network requests are equal - some of them are loaded from the
browser cache instead of the network. Firebug provides status codes
for every request so you can quickly scan and see how effectively your
site is using the cache to optimize page load times.
Firebug Net Panel docs are here.
Chrome/Safari/Opera all have similar debugging tools. Just found a good list here (most should have tools to inspect XHR).
EDIT:
In order to somewhat redeem myself...
As ibu has answered, I'd also start by checking the status code of the response.
If you're using jQuery:
statusCode(added 1.5)
Map Default: {}
A map of numeric HTTP codes and functions to be called when the
response has the corresponding code. For example, the following will
alert when the response status is a 404:
$.ajax({
statusCode: {
404: function() {
alert("page not found");
}
}
});
If the request is successful, the status code functions take the same
parameters as the success callback; if it results in an error, they
take the same parameters as the error callback.
jQuery sure does make life easy. :)
To check from a browser such as Google Chrome, hit F12 to open DevTools, navigate to Network, refresh to grab some data, filter by XHR, then click on the correct XHR request. Click on the "headers" sub-tab, then look at Response Headers -> cache-control.
If it says things like no-cache and max-age=0, then you are not caching.
If it says private, then your browser is caching, but the server is not.
If it says public, then you are caching both server side and client side.
More info at Mozilla.org

Ajax / Header mismatch?

I'm hoping someone can answer this question for me, I'm not an expert on servers so please excuse me if I'm completely off base.
I'm using Android webview (PhoneGap 1.4.1) to make Ajax calls but I keep getting a ready state 4 status 0 on each call. I've spent the last couple hours investigating this and I may have figured out why. I used xhaus.com/headers to check my requests and found that in web view my "Accept" header is:
text/xml, text/html, application/xhtml+xml, image/png, text/plain, /;q=0.8
however, if I pull up the Android browser and check my header that way, I see that my "Accept" header is:
application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png, /;q=0.5
I checked the server that is providing the XML and found that the return heads are "Content-Type" is set to:
application/xml
My first question is: Webview doesn't seem to support "application/xml" type, so could this be the reason I'm having my issue? Or am I completely off base here?
Second question: Is there anything I can do on the client side to fix this or will the server admin need to make the change? I am using GET to make the request.
Third question: Is this normal? why would web view / browser have this sort of mismatch?
My app has been tested on 10+ handsets and only 2 have this issue... Very strange.
Thank you,
I may be wrong, but it sounds like you're calling a web service? Some services support mutliple calling strategies depending on the content-type header that you pass.
For example SharePoint web services support both SOAP 1.0 (if Content-type is sent as "text/xml; charset=utf-8") and SOAP 1.2 (if Content-type is set as "application/soap+xml").
In your case I would try setting a content-type of "text/xml; charset=utf-8" and see what happens.
As for the "accept" header if the server response isn't acceptable based on the passed value it should pass a status of 406 (not acceptable). It may simply be a bug in the server or in any of the steps between tho' that it's not. Using a tool like Fiddler you should be able to recreate both requests (both variations on "accept") and see exactly what the server responses with however - that may be the easiest way to truly get to the bottom of things.

Cross-domain website promotion

I'd like to offer a way to my users to promote my website, blog etc. on their website.
I can make a banner, logo whatever that they can embed to their site, but I'd like to offer dynamic content, like "the 5 newest entry's title from my blog".
The problem is the same origin policy. I know there is a solution (and I use it): they embed a simple div and a JavaScript file. The JS makes an XmlHttpRequest to my server and gets the data as JSONP, parses the data and inserts into the div.
But is it the only way? Isn't there a better way I could do this?
On the Internet there are tons of widget (or whatever, I don't know how they call...) that gain the data from another domain. How they do that?
A common theme of many of the solutions, instead, is getting JavaScript to call a proxy program (either on the client or the server) which, in turn, calls the web service for you.
The output can be written to the response stream and then is available, via the normal channels, such as the responseText and responseXML properties of XMLHttpRequest.
you can find more solution here :
http://developer.yahoo.com/javascript/howto-proxy.html
or here :
http://www.simple-talk.com/dotnet/asp.net/calling-cross-domain-web-services-in-ajax/
CORS is a different way than JSONP.
Plain AJAX. All your server has to do is to set a specific header: Access-Control-Allow-Origin
More here: http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/
If you go the JSONP route, you will implicitly ask your users to trust you, as they will give you full access to the resources of their page (content, cookies,...). If they know that they main complain.
While if you go the iframe route there is no problems.One famous example today of embeddable content by iframe is the Like button of facebook.
And making that server side with a proxy or other methods would be much more complex, as there are plenty of environments out there. I don't know other ways.
You can also set the HTTP Access-Control headers in the server side. This way you're basically controlling from the server side on whether the client who has fired the XMLHttpRequest is allowed to process the response. Any recent (and decent) webbrowser will take action accordingly.
Here's a PHP-targeted example how to set the headers accordingly.
header('Access-Control-Allow-Origin: *'); // Everone may process the response.
header('Access-Control-Max-Age: 604800'); // Client may cache this for one week.
header('Access-Control-Allow-Methods: GET, POST'); // Allowed request methods.
The key is Access-Control-Allow-Origin: *. This informs the client that requests originating from * (in fact, everywhere) is allowed to process the response. If you set it to for example Access-Control-Allow-Origin: http://example.com, then the webbrowser may only process the response when the initial page is been served from the mentioned domain.
See also:
MDC - HTTP Access Control

Categories

Resources