1102

Since the upgrade to iOS 6, we are seeing Safari's web view take the liberty of caching $.ajax calls. This is in the context of a PhoneGap application so it is using the Safari WebView. Our $.ajax calls are POST methods and we have cache set to false {cache:false}, but still this is happening. We tried manually adding a TimeStamp to the headers but it did not help.

We did more research and found that Safari is only returning cached results for web services that have a function signature that is static and does not change from call to call. For instance, imagine a function called something like:

getNewRecordID(intRecordType)

This function receives the same input parameters over and over again, but the data it returns should be different every time.

Must be in Apple's haste to make iOS 6 zip along impressively they got too happy with the cache settings. Has anyone else seen this behavior on iOS 6? If so, what exactly is causing it?


The workaround that we found was to modify the function signature to be something like this:

getNewRecordID(intRecordType, strTimestamp)

and then always pass in a TimeStamp parameter as well, and just discard that value on the server side. This works around the issue.

Sreeram Nair
  • 2,369
  • 12
  • 27
user1684978
  • 8,569
  • 3
  • 13
  • 6
  • 1
    Is there any confirmation from apple about this issue? – epeleg Sep 20 '12 at 11:37
  • 192
    This is absolutely shocking. We have also just spent a couple of hours trying to work out what something just stopped working. Our AJAX login that does a POST (and has headers to prevent caching too) is being cached by Safari so it just returns the same JSON that it did last time without even trying the server...unbelievable! We'll have to hack a fix, but you should never cache a POST, it's crazy. – Kieran Sep 20 '12 at 14:55
  • Just to report the we tested an existing phonegap application which uses Strophe for XMPP and everything seems working out of the box. – Davide Vernizzi Sep 21 '12 at 09:12
  • I had similar problems with desktop Chrome after having enabled the cache manifest. Apparently it triggers some aggressive caching and I was setting no cache headers to no avail. – Lorenzo Sep 21 '12 at 09:13
  • Thanks for updating with an answer. I've had this happen to me while trying to develop a web application… it's only happened to me with POST ajax requests too. – Vince Sep 21 '12 at 09:24
  • 16
    Post your solution as an answer rather than an update to the question. – ChrisF Sep 21 '12 at 14:40
  • 50
    POST requests are non-idempotent, which means they should not be cached _unless_ the response specifically advises doing so via its response headers. – James M. Greene Sep 21 '12 at 15:14
  • @Kieran Not only does this break your current efforts, it breaks all previous web apps that use POST... – Boris Smus Sep 22 '12 at 18:58
  • 1
    Just to ask, you did try sending a regular ajax request without using jQuery/other library, right? Just to rule out that it's not the library doing something bizarre. – rossisdead Sep 22 '12 at 23:45
  • 6
    To get Apple to fix this, file a bug at [bugreport.apple.com](https://bugreport.apple.com/). I’ve done the same. – Mathias Bynens Sep 23 '12 at 10:13
  • I don't have an iOS 6 device at hand...does this only occur with AJAX calls, or is it caching responses to form POST submissions as well? And does this occur in the Safari browser, or only when used as a WebView? – Annabelle Sep 23 '12 at 16:35
  • 1
    Apparently you can cache a POST in some circumstances, but should only return the contents of the cache in response to an appropriate subsequent GET. Not to other POSTs. – Jonathan Hartley Sep 23 '12 at 21:53
  • It looks like latest versions of Chrome do the same. I had to add `Cache-Control: max-age=0, no-cache, no-store, must-revalidate, proxy-revalidate` to my web application that worked fine with earlier versions of Chrome. – Maxim Egorushkin Sep 24 '12 at 08:13
  • 11
    Mark Nottingham (chair of the IETF HTTPbis working group) wrote an interesting blog post about this today: http://www.mnot.net/blog/2012/09/24/caching_POST – Benjamin Brizzi Sep 24 '12 at 15:02
  • This is how new iOS Safari is. I think people will gradually get habituated and accept it how it is. – Imdad Sep 26 '12 at 03:44
  • Post made it to arstechnica: http://arstechnica.com/apple/2012/09/developers-claim-safari-in-ios-6-breaks-web-apps-with-aggressive-caching/ – Ja͢ck Sep 27 '12 at 10:36
  • @BenjaminBrizzi, I agree, particularly the part about the Content-Location exception. However, it does bury the lede, "even without the benefit of this context, they're still clearly violating the spec; the original permission to cache in 2616 was contingent upon there being explicit freshness information (basically, Expires or Cache-Control: max-age)." – Matthew Flaschen Sep 29 '12 at 04:28
  • @Imdad, I really hope not. Apple needs to fix their bug. – Matthew Flaschen Sep 29 '12 at 04:28
  • I have implemented my server side fix for this but for it to be effective I have to uninstall the app and reinstall it - so will all of my users have to do the same to start seeing fresh data? This is a ridiculous move by apple – Dean Wild Oct 05 '12 at 16:51
  • 2
    Does anyone know if this gets fixed in later versions of IOS ? – Michael Jun 04 '13 at 09:57
  • 1
    Try to add timestamp to the URL example.com/my/url?t=$time_variable – CORSAIR Apr 17 '15 at 14:16
  • this behaviour still exists – RozzA Oct 02 '17 at 06:29
  • still exits for get requests on ios 12.1.3 --> had to ad a timestamp queryparameter. most annoying thing --> no infos about using cached result in debug console, when using chrome on windows to see what happens: i only discovered this because there was a request missing, that should be sent to an api – Brandy23 Feb 04 '19 at 13:52

25 Answers25

450

After a bit of investigation, turns out that Safari on iOS6 will cache POSTs that have either no Cache-Control headers or even "Cache-Control: max-age=0".

The only way I've found of preventing this caching from happening at a global level rather than having to hack random querystrings onto the end of service calls is to set "Cache-Control: no-cache".

So:

  • No Cache-Control or Expires headers = iOS6 Safari will cache
  • Cache-Control max-age=0 and an immediate Expires = iOS6 Safari will cache
  • Cache-Control: no-cache = iOS6 Safari will NOT cache

I suspect that Apple is taking advantage of this from the HTTP spec in section 9.5 about POST:

Responses to this method are not cacheable, unless the response includes appropriate Cache-Control or Expires header fields. However, the 303 (See Other) response can be used to direct the user agent to retrieve a cacheable resource.

So in theory you can cache POST responses...who knew. But no other browser maker has ever thought it would be a good idea until now. But that does NOT account for the caching when no Cache-Control or Expires headers are set, only when there are some set. So it must be a bug.

Below is what I use in the right bit of my Apache config to target the whole of my API because as it happens I don't actually want to cache anything, even gets. What I don't know is how to set this just for POSTs.

Header set Cache-Control "no-cache"

Update: Just noticed that I didn't point out that it is only when the POST is the same, so change any of the POST data or URL and you're fine. So you can as mentioned elsewhere just add some random data to the URL or a bit of POST data.

Update: You can limit the "no-cache" just to POSTs if you wish like this in Apache:

SetEnvIf Request_Method "POST" IS_POST
Header set Cache-Control "no-cache" env=IS_POST
Kieran
  • 5,906
  • 3
  • 24
  • 34
  • 8
    I see where Apple is going with this, but we are seeing Cached responses to POST requests even when our responses did not include any Cache-Control or Expires headers. Is this instance iOS6 should not cache and send every request. This is not happening. – Kango_V Sep 20 '12 at 16:52
  • 1
    Yeah, that's what I see as well. No headers or just age based headers and it will cache, you need a more explicit no-cache type header. – Kieran Sep 20 '12 at 16:54
  • 140
    The part of the HTTP spec you quoted does not justify iOS 6's caching behavior. The default behavior should be to not cache POST responses (i.e. when "Cache-Control" header is not defined). The behavior violates the spec and should be considered a bug. Anyone building xml/json api web services should decorate their POST responses with "Cache-control: no-cache" to work around this issue. – David H Sep 20 '12 at 23:06
  • Is this behaviour true for all POST requests, including normal interactive ones through direct user control, or just those that go through xmlHTTPRequuest? – David Spillett Sep 21 '12 at 12:38
  • 40
    POST requests are non-idempotent, which means they should not be cached _unless_ the response specifically advises doing so via its response headers. – James M. Greene Sep 21 '12 at 15:13
  • 4
    As David says, it's a clear violation of the sentence you quoted. If there's no "Cache-Control or Expires header fields", appropriate such headers are obviously not included. Yet your own investigation shows it caches in that scenario. Please edit your answer. – Matthew Flaschen Sep 22 '12 at 06:16
  • 1
    I've clarified in my answer that it must indeed be considered a bug, sorry I was not clear enough in that part of my answer. Does anyone know if Apple have come out and admitted this problem yet? – Kieran Sep 24 '12 at 07:27
  • 3
    Does anyone know how long the result is cached on a device? I have tried killing safari and restarting my phone, but it's still cached. I know it works with clearing the browser cache but I'm wondering how long it will take for users that once had the problem before it goes away. Not everyone will think of clearing their cache... – Daniel Hallqvist Sep 25 '12 at 18:22
  • Simple solution, as Kieran hinted at, just add a random string to each post request. ios6_no_cache: get_rand_num(). Works perfectly. – Ryan Martin Nov 29 '12 at 01:35
  • 3
    I'm finding that changing the POST data does not upset the cache - it appears to be based solely on the url. – Aaron Dufour Nov 30 '12 at 18:24
  • *"I suspect that Apple is taking advantage of this from the HTTP spec in section 9.5 about POST:"* That still gives the impression what you're quoting justifies the behavior, which it patently does not. – T.J. Crowder Jul 09 '13 at 07:53
149

I hope this can be of use to other developers banging their head against the wall on this one. I found that any of the following prevents Safari on iOS 6 from caching the POST response:

  • adding [cache-control: no-cache] in the request headers
  • adding a variable URL parameter such as the current time
  • adding [pragma: no-cache] in the response headers
  • adding [cache-control: no-cache] in the response headers

My solution was the following in my Javascript (all my AJAX requests are POST).

$.ajaxSetup({
    type: 'POST',
    headers: { "cache-control": "no-cache" }
});

I also add the [pragma: no-cache] header to many of my server responses.

If you use the above solution be aware that any $.ajax() calls you make that are set to global: false will NOT use the settings specified in $.ajaxSetup(), so you will need to add the headers in again.

Dave
  • 1,541
  • 2
  • 9
  • 9
  • 4
    This is THE CORRECT solution to the bug. The bug is that iOS 6 will service POST requests from it's cache instead of sending them to the server. The bug is not that it caches responses from POST requests (which is allowed). If you still want responses to POST requests retrieved from the cache for subsequent GET requests to that URI, use this solution. – Nicholas Shanks Nov 12 '12 at 16:32
  • 2
    This works for me, but I don't understand how. I had already specified cache: false in my ajaxSetup, and looking at the request headers, that boils down to Cache-Control: no-cache and Pragma: no-cache - but it will still cache on the iPad. Then when I add headers: { "cache-control": "no-cache" } into ajaxSetup, it doubles up the Cache-Control header to be "no-cache, no-cache" - and stops the caching. What's happening here? – Tom W Hall Nov 26 '12 at 22:18
  • Works perfectly - you can also add to the request as a parameter $.ajax({type: 'POST', headers: { 'cache-control': 'no-cache' }, etc.}) – George Filippakos Jan 02 '13 at 13:05
  • What is [pragma: no-cache]? What is the pragma key used for? – zakdances Mar 19 '13 at 20:34
  • I also think this is the best approach, rather than a workaround with an additional parameter. We have added this on just the calls where we needed it, for calls that always have the same return, the caching is probably a good thing for the end user. – germankiwi May 20 '13 at 22:37
  • This answer ***actually*** answers the question. – Amal Murali Aug 04 '13 at 08:43
70

Simple solution for all your web service requests, assuming you're using jQuery:

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
    // you can use originalOptions.type || options.type to restrict specific type of requests
    options.data = jQuery.param($.extend(originalOptions.data||{}, { 
      timeStamp: new Date().getTime()
    }));
});

Read more about the jQuery prefilter call here.

If you aren't using jQuery, check the docs for your library of choice. They may have similar functionality.

Charles
  • 50,943
  • 13
  • 104
  • 142
Baz1nga
  • 15,485
  • 3
  • 35
  • 61
  • 3
    It doesn't work to me, server responds: "Invalid primitive JSON: timeStamp" asp.net / iis 7.5 – Alexandre Sep 24 '12 at 00:10
  • 3
    what about the $.ajax({ "cache": false ...}) ? will it work as it append a _=[TIMESTAMP]? (I don't own such a device to test it) – Karussell Sep 24 '12 at 12:59
  • I have posted a full implementation of the solution proposed by Karussell. See my answer below. – Sam Shiles Sep 27 '12 at 14:45
  • 1
    @Karussell. Just tried setting $.ajax({ "cache": false ...}). This doesn't resolve the issue for POST requests on iOS6. Presumably because JQuery as per their docs assumes no browser is stupid enough to cache post requests. "Pages fetched with POST are never cached, so the cache and ifModified options in jQuery.ajaxSetup() have no effect on these requests." – Brett Hannah Oct 02 '12 at 11:02
  • 1
    This doesn't work. It doesn't merge post parameters. The post by Dave is a better solution. – Chris Muench Oct 15 '12 at 23:33
  • This worked for me with jQuery 1.8.2 but I added `if (originalOptions.type == "post")` to prevent uncachable GETs. – remcoder Oct 25 '12 at 09:40
  • This code doesn't merge the options, like Chris said. I updated this snippet with a fix http://stackoverflow.com/a/13068682/1144194 – remcoder Oct 25 '12 at 12:35
  • Agree with Chris Muench, the solution by "Dave", to use Cache-Control on POST requests, is a better one. – Nicholas Shanks Nov 12 '12 at 16:35
  • This was working in production code for a couple of years. Then I tried to use the BlueImpl File Upload jQUery plugin and spent ages wondering why the ajax call failed. I eventually traced it back to this bit of code since this code doesn't deal with the object which the File Upload plugin passes as the originalOptions. Dave's solution (no relation ;) ) above works well (add this to the end of the URL to go to Dave's answer: #12856562 ). – RedYeti Oct 15 '14 at 14:37
45

I just had this issue as well in a PhoneGap application. I solved it by using the JavaScript function getTime() in the following manner:

var currentTime = new Date();
var n = currentTime.getTime();
postUrl = "http://www.example.com/test.php?nocache="+n;
$.post(postUrl, callbackFunction);

I wasted a few hours figuring this out. It would have been nice of Apple to notify developers of this caching issue.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bashevis
  • 1,507
  • 16
  • 21
  • 1
    I was going to comment on using `{cache:false}` as an option to either `$.post()` or `$.ajaxSetup()`, but according to [the docs](http://api.jquery.com/jQuery.post/), these arguments are ignored; jQuery will 'never cache' post requests, but does not take the browser into consideration. Perhaps a neater option would be to add a timestamp to requests using `$.ajaxPrefilter()`. – cthulhu Sep 21 '12 at 08:33
  • i spend almost 5 hours to fix this issue, and finally adding timestamp will do the trick `function send_ajax(my_data,refresh) `.. refer here http://stackoverflow.com/questions/14733772/how-do-i-not-cache-ajax-post-in-mobile-safari-app – rusly Nov 23 '13 at 15:01
43

I had the same problem with a webapp getting data from ASP.NET webservice

This worked for me:

public WebService()
{
    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    ...
}
Tadej
  • 439
  • 3
  • 2
25

Finally, I've a solution to my uploading problem.

In JavaScript:

var xhr = new XMLHttpRequest();
xhr.open("post", 'uploader.php', true);
xhr.setRequestHeader("pragma", "no-cache");

In PHP:

header('cache-control: no-cache');
goker
  • 2,690
  • 22
  • 20
16

From my own blog post iOS 6.0 caching Ajax POST requests:

How to fix it: There are various methods to prevent caching of requests. The recommended method is adding a no-cache header. This is how it is done.

###jQuery:

Check for iOS 6.0 and set Ajax header like this:

$.ajaxSetup({ cache: false });

###ZeptoJS:

Check for iOS 6.0 and set the Ajax header like this:

$.ajax({
    type: 'POST',
    headers : { "cache-control": "no-cache" },
    url : ,
    data:,
    dataType : 'json',
    success : function(responseText) {…}

##Server side

###Java:

httpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");

Make sure to add this at the top the page before any data is sent to the client.

###.NET

Response.Cache.SetNoStore();

Or

Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);

###PHP

header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
header('Pragma: no-cache'); // HTTP 1.0.
kiranvj
  • 32,342
  • 7
  • 71
  • 76
8

This JavaScript snippet works great with jQuery and jQuery Mobile:

$.ajaxSetup({
    cache: false,
    headers: {
        'Cache-Control': 'no-cache'
    }
});

Just place it somewhere in your JavaScript code (after jQuery is loaded, and best before you do AJAX requests) and it should help.

Jonathan
  • 6,572
  • 1
  • 30
  • 46
7

You can also fix this issue by modifying the jQuery Ajax function by doing the following (as of 1.7.1) to the top of the Ajax function (function starts at line 7212). This change will activate the built-in anti-cache feature of jQuery for all POST requests.

(The full script is available at http://dl.dropbox.com/u/58016866/jquery-1.7.1.js.)

Insert below line 7221:

if (options.type === "POST") {
    options.cache = false;
}

Then modify the following (starting at line ~7497).

if (!s.hasContent) {
    // If data is available, append data to URL
    if (s.data) {
        s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
        // #9682: remove data so that it's not used in an eventual retry
        delete s.data;
    }

    // Get ifModifiedKey before adding the anti-cache parameter
    ifModifiedKey = s.url;

    // Add anti-cache in URL if needed
    if (s.cache === false) {
        var ts = jQuery.now(),
        // Try replacing _= if it is there
        ret = s.url.replace(rts, "$1_=" + ts);

        // If nothing was replaced, add timestamp to the end.
        s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
    }
}

To:

// More options handling for requests with no content
if (!s.hasContent) {
    // If data is available, append data to URL
    if (s.data) {
        s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
        // #9682: remove data so that it's not used in an eventual retry
        delete s.data;
    }

    // Get ifModifiedKey before adding the anti-cache parameter
    ifModifiedKey = s.url;
}

// Add anti-cache in URL if needed
if (s.cache === false) {
    var ts = jQuery.now(),
    // Try replacing _= if it is there
    ret = s.url.replace(rts, "$1_=" + ts);

    // If nothing was replaced, add timestamp to the end.
    s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sam Shiles
  • 10,529
  • 9
  • 60
  • 72
  • 4
    It's not a good approach to change the jQuery or for that matter any code you doesn't own. (Everytime you want to update the version you'll have to make the change again. (Or Another developer updates and the program doesn't work)) – Andreas Louv Oct 31 '12 at 16:40
  • It's a perfectly valid approach if you need to the quickest possible solution to mitigate Apple's idiocy. This solution has been used to resolve the issue for a massive site that receives millions of hits a day and it enabled us to do it just by making a change to one file. – Sam Shiles Nov 01 '12 at 14:53
  • You can look at `jQuery.ajaxPrefiler` it lets you modify your ajax request right before making it. You can archive the same with more optimized and update safe code. – Andreas Louv Nov 01 '12 at 15:16
  • 1
    The problem with the preFilter approach is that you need to register the filter. If you have a common script that runs when each page loads, then fine, but if you don't you would have to setup the preFilter for each page that uses ajax. The scenario I faced, we had a common location for the JQ file that was used as a resource for 7+ individual web sites. We were losing thousands of pounds an hour due to this bug and the approach I've suggested enabled us to get it resolved in the shortest possible time by changing ONE file. I agree with you in principal but you have to be pragmatic sometimes! – Sam Shiles Nov 01 '12 at 15:28
  • You can then again add it to the end of that file. Good you solved it, your company must be happy for you. – Andreas Louv Nov 01 '12 at 15:41
6

A quick work-around for GWT-RPC services is to add this to all the remote methods:

getThreadLocalResponse().setHeader("Cache-Control", "no-cache");
Lars Høidahl
  • 460
  • 4
  • 7
  • Most of us have hundreds of remote methods in their GWT deployments. Is there a universal way set the cache control header for all requests? – YoungDinosaur Nov 09 '12 at 04:37
6

This is an update of Baz1nga's answer. Since options.data is not an object but a string I just resorted to concatenating the timestamp:

$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
  if (originalOptions.type == "post" || options.type == "post") {

    if (options.data && options.data.length)
      options.data += "&";
    else
      options.data = "";

    options.data += "timeStamp=" + new Date().getTime();
  }
});
remcoder
  • 302
  • 1
  • 5
5

In order to resolve this issue for WebApps added to the home screen, both of the top voted workarounds need to be followed. Caching needs to be turned off on the webserver to prevent new requests from being cached going forward and some random input needs to be added to every post request in order for requests that have already been cached to go through. Please refer to my post:

iOS6 - Is there a way to clear cached ajax POST requests for webapp added to home screen?

WARNING: to anyone who implemented a workaround by adding a timestamp to their requests without turning off caching on the server. If your app is added to the home screen, EVERY post response will now be cached, clearing safari cache doesn't clear it and it doesn't seem to expire. Unless someone has a way to clear it, this looks like a potential memory leak!

Community
  • 1
  • 1
fbader
  • 149
  • 4
  • Will all responses be cached to file or memory on the phone? – Eydun Oct 05 '12 at 11:56
  • This was not the case with me. I appended a time stamp to my url (not post parameters) and it works just fine, both when browsing from safari and when saving to home screen. – ShadeTreeDeveloper Jan 19 '13 at 17:17
5

Things that DID NOT WORK for me with an iPad 4/iOS 6:

My request containing: Cache-Control:no-cache

//asp.net's:
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache)

Adding cache: false to my jQuery ajax call

 $.ajax(
        {
            url: postUrl,
            type: "POST",
            cache: false,
            ...

Only this did the trick:

var currentTime = new Date();
var n = currentTime.getTime();
postUrl = "http://www.example.com/test.php?nocache="+n;
$.post(postUrl, callbackFunction);
Brian Ogden
  • 18,439
  • 10
  • 97
  • 176
  • What is the down vote for? This is important information cache:false does not work with iPad4/iOS6 nor does //asp.net's: HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache) – Brian Ogden May 10 '13 at 07:40
  • For posterity: as of 2017, `$.ajax` `cache: false` appends the url with the query parameter `_=Date.prototype.getTime()`, so manually appending the timestamp should no longer be needed. – cowbert Jan 15 '18 at 22:02
4

That's the work around for GWT-RPC

class AuthenticatingRequestBuilder extends RpcRequestBuilder 
{
       @Override
       protected RequestBuilder doCreate(String serviceEntryPoint) 
       {
               RequestBuilder requestBuilder = super.doCreate(serviceEntryPoint);           
               requestBuilder.setHeader("Cache-Control", "no-cache");

               return requestBuilder;
       }
}

AuthenticatingRequestBuilder builder = new AuthenticatingRequestBuilder();
((ServiceDefTarget)myService).setRpcRequestBuilder(builder);    
Spiff
  • 3,873
  • 4
  • 25
  • 50
3

My workaround in ASP.NET (pagemethods, webservice, etc.)

protected void Application_BeginRequest(object sender, EventArgs e)
{
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alexandre
  • 7,004
  • 5
  • 54
  • 72
2

While adding cache-buster parameters to make the request look different seems like a solid solution, I would advise against it, as it would hurt any application that relies on actual caching taking place. Making the APIs output the correct headers is the best possible solution, even if that's slightly more difficult than adding cache busters to the callers.

Ivo Jansch
  • 1,438
  • 1
  • 15
  • 18
  • 1
    Whilst I would agree with you most circumstances, I would argue that the real solution to this problem is for Apple to correctly implement HTTP. With this in mind, I wouldn't blame a lot of developers for implementing the simplest possible solution until that time. For me, modifying the jquery implementation was a the simplest fix as it allowed me to make one edit and be confident it was active for my whole site. – Sam Shiles Sep 28 '12 at 07:32
2

I was able to fix my problem by using a combination of $.ajaxSetup and appending a timestamp to the url of my post (not to the post parameters/body). This based on the recommendations of previous answers

$(document).ready(function(){
    $.ajaxSetup({ type:'POST', headers: {"cache-control","no-cache"}});

    $('#myForm').submit(function() {
        var data = $('#myForm').serialize();
        var now = new Date();
        var n = now.getTime();
        $.ajax({
            type: 'POST',
            url: 'myendpoint.cfc?method=login&time='+n,
            data: data,
            success: function(results){
                if(results.success) {
                    window.location = 'app.cfm';
                } else {
                    console.log(results);
                    alert('login failed');
                }
            }
        });
    });
});
ShadeTreeDeveloper
  • 1,553
  • 2
  • 12
  • 20
1

For those that use Struts 1, here is how I fixed the issue.

web.xml

<filter>
    <filter-name>SetCacheControl</filter-name>
    <filter-class>com.example.struts.filters.CacheControlFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>SetCacheControl</filter-name>
    <url-pattern>*.do</url-pattern>
    <http-method>POST</http-method>
</filter-mapping>

com.example.struts.filters.CacheControlFilter.js

package com.example.struts.filters;

import java.io.IOException;
import java.util.Date;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;

public class CacheControlFilter implements Filter {

        public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {

        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setHeader("Expires", "Mon, 18 Jun 1973 18:00:00 GMT");
        resp.setHeader("Last-Modified", new Date().toString());
        resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
        resp.setHeader("Pragma", "no-cache");

        chain.doFilter(request, response);
    }

    public void init(FilterConfig filterConfig) throws ServletException {
    }

    public void destroy() {
    }

}
cbmeeks
  • 11,248
  • 22
  • 85
  • 136
1

I think you have already resolved your issue, but let me share an idea about web caching.

It is true you can add many headers in each language you use, server side, client side, and you can use many other tricks to avoid web caching, but always think that you can never know from where the client are connecting to your server, you never know if he are using a Hotel “Hot-Spot” connection that uses Squid or other caching products.

If the users are using proxy to hide his real position, etc… the real only way to avoid caching is the timestamp in the request also if is unused.

For example:

/ajax_helper.php?ts=3211321456

Then every cache manager you have to pass didnt find the same URL in the cache repository and go re-download the page content.

Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
Lanello
  • 59
  • 3
  • Old answer, but my two cents: This is generally good advice and understood by most competent web developers, but in the specific case of jQuery, if you make an `$.ajax` and have set the options to have `{cache:false}` then jQuery itself will automatically add a cache-busting behind the scenes without you ever needing to do anything else. – Giacomo1968 Dec 03 '15 at 07:30
1

We found that older iPhones and iPads, running iOS versions 9 & 10, occasionally return bogus blank AJAX results, perhaps due to Apple's turning down CPU speed. When returning the blank result, iOS does not call the server, as if returning a result from cache. Frequency varies widely, from roughly 10% to 30% of AJAX calls return blank.

The solution is hard to believe. Just wait 1s and call again. In our testing, only one repeat was all that was ever needed, but we wrote the code to call up to 4 times. We're not sure if the 1s wait is required, but we didn't want to risk burdening our server with bursts of repeated calls.

We found the problem happened with two different AJAX calls, calling on different API files with different data. But I'm concerned it could happen on any AJAX call. We just don't know because we don't inspect every AJAX result and we don't test every call multiple times on old devices.

Both problem AJAX calls were using: POST, Asynchronously = true, setRequestHeader = ('Content-Type', 'application/x-www-form-urlencoded')

When the problem happens, there's usually only one AJAX call going on. So it's not due to overlapping AJAX calls. Sometimes the problem happens when the device is busy, but sometimes not, and without DevTools we don't really know what's happening at the time.

iOS 13 doesn't do this, nor Chrome or Firefox. We don't have any test devices running iOS 11 or 12. Perhaps someone else could test those?

I'm noting this here because this question is the top Google result when searching for this problem.

tijko
  • 7,599
  • 11
  • 44
  • 64
CaptureWiz
  • 1,685
  • 1
  • 15
  • 15
0

Depending on the app you can trouble shoot the issue now in iOS 6 using Safari>Advanced>Web Inspector so that is helpful with this situation.

Connect the phone to Safari on a Mac an then use the developer menu to trouble shoot the web app.

Clear the website data on the iPhone after update to iOS6, including specific to the app using a Web View. Only one app had an issue and this solved it during IOS6 Beta testing way back, since then no real problems.

You may need to look at your app as well, check out NSURLCache if in a WebView in a custom app.

https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSURLCache_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40003754

I guess depending on the true nature of your problem, implementation, etc. ..

Ref: $.ajax calls

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • While this doesn't directly address the original question, it's very useful information for being able to troubleshoot on-devices issues in general, so I'm up-voting it. – Kris Giesing Oct 07 '12 at 20:33
0

I found one workaround that makes me curious as to why it works. Before reading Tadej's answer concerning ASP.NET web service, I was trying to come up with something that would work.

And I'm not saying that it's a good solution, but I just wanted to document it here.

main page: includes a JavaScript function, checkStatus(). The method calls another method which uses a jQuery AJAX call to update the html content. I used setInterval to call checkStatus(). Of course, I ran into the caching problem.

Solution: use another page to call the update.

On the main page, I set a boolean variable, runUpdate, and added the following to the body tag:

<iframe src="helper.html" style="display: none; visibility: hidden;"></iframe>

In the of helper.html:

<meta http-equiv="refresh" content="5">
<script type="text/javascript">
    if (parent.runUpdate) { parent.checkStatus(); }
</script>

So, if checkStatus() is called from the main page, I get the cached content. If I call checkStatus from the child page, I get updated content.

CM Kanode
  • 1,436
  • 1
  • 11
  • 14
0

While my login and signup pages works like a charm in Firefox, IE and Chrome... I've been struggling with this issue in Safari for IOS and OSX, few months ago I found a workaround on the SO.

<body onunload="">

OR via javascript

<script type="text/javascript">
window.onunload = function(e){
    e.preventDefault();
    return;
};
</script>   

This is kinda ugly thing but works for a while.

I don't know why, but returning null to the onunload event the page do not get cached in Safari.

Adriano Rosa
  • 8,303
  • 1
  • 25
  • 25
-1

It worked with ASP.NET only after adding the pragma:no-cache header in IIS. Cache-Control: no-cache was not enough.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Boris
  • 119
  • 2
  • 11
-2

I suggest a workaround to modify the function signature to be something like this:

getNewRecordID(intRecordType, strTimestamp) and then always pass in a TimeStamp parameter as well, and just discard that value on the server side. This works around the issue.

fred1234
  • 429
  • 1
  • 4
  • 26