题 iOS 6上的Safari是否缓存$ .ajax结果?


自升级到iOS 6以来,我们看到Safari的Web视图可以自由缓存 $.ajax 调用。这是在PhoneGap应用程序的上下文中,因此它使用Safari WebView。我们的 $.ajax 电话是 POST 方法,我们将缓存设置为false {cache:false},但这种情况仍在发生。我们尝试手动添加 TimeStamp 到标题但它没有帮助。

我们做了更多研究,发现Safari只返回具有静态功能签名并且不会因呼叫而改变的Web服务的缓存结果。例如,假设一个函数叫做:

getNewRecordID(intRecordType)

该函数反复接收相同的输入参数,但每次返回的数据应该不同。

苹果急于让iOS 6拉链令人印象深刻,他们对缓存设置感到满意。还有其他人在iOS 6上看到过这种行为吗?如果是这样,究竟是什么导致了它?


我们发现的解决方法是将函数签名修改为如下所示:

getNewRecordID(intRecordType, strTimestamp)

然后总是传递一个 TimeStamp 参数也是,只是在服务器端丢弃该值。这解决了这个问题。我希望这能帮助其他一些在这个问题上花费15个小时的穷人,就像我一样!


1031
2017-09-20 06:07


起源


这绝对令人震惊。我们也花了几个小时试图弄清楚什么东西停止了工作。我们的AJAX登录执行POST(并且具有防止缓存的标头)正在被Safari缓存,因此它只返回与上次相同的JSON,甚至没有尝试过服务器......令人难以置信!我们必须破解修复,但你永远不应该缓存POST,这很疯狂。 - Kieran
将您的解决方案作为答案发布,而不是更新问题。 - ChrisF♦
POST请求是非幂等的,这意味着它们不应被缓存 除非 响应特别建议通过其响应头进行此操作。 - James M. Greene
要让Apple解决此问题,请提交错误 bugreport.apple.com。我做了同样的事。 - Mathias Bynens
Mark Nottingham(IETF HTTPbis工作组主席)今天撰写了一篇有趣的博客文章: mnot.net/blog/2012/09/24/caching_POST - Benjamin Brizzi


答案:


经过一番调查后发现,iOS6上的Safari将缓存没有Cache-Control标头甚至“Cache-Control:max-age = 0”的POST。

我发现阻止此缓存在全局级别发生而不是在服务调用结束时破解随机查询字符串的唯一方法是设置“Cache-Control:no-cache”。

所以:

  • No Cache-Control或Expires headers = iOS6 Safari将缓存
  • Cache-Control max-age = 0,立即Expires = iOS6 Safari将缓存
  • 缓存控制:no-cache = iOS6 Safari不会缓存

我怀疑Apple在第9.5节关于POST的HTTP规范中利用了这一点:

除非响应,否则对此方法的响应不可缓存      包括适当的Cache-Control或Expires头字段。然而,      303(请参阅其他)响应可用于指导用户代理      检索可缓存的资源。

所以理论上你可以缓存POST响应......谁知道。但是直到现在还没有其他浏览器制造商认为这是一个好主意。但是,如果没有设置Cache-Control或Expires标头,那么这不会考虑缓存,只有在有一些设置时才会考虑。所以它一定是个bug。

下面是我在Apache配置的正确位置使用的目标我的整个API,因为它实际上我实际上并不想缓存任何东西,甚至得到。我不知道的是如何为POST设置这个。

Header set Cache-Control "no-cache"

更新:刚刚注意到我没有指出它只是在POST是相同的时候,所以更改任何POST数据或URL,你就没事了。所以你可以像其他地方一样在网址或一些POST数据中添加一些随机数据。

更新:如果你想在Apache中这样做,你可以将“no-cache”限制为POST:

SetEnvIf Request_Method "POST" IS_POST
Header set Cache-Control "no-cache" env=IS_POST

434
2017-09-20 16:06



我看到Apple正在采用这种方式,但即使我们的响应中没有包含任何Cache-Control或Expires标头,我们也会看到对POST请求的缓存响应。这个实例iOS6不应该缓存并发送每个请求。这不会发生。 - Kango_V
您引用的HTTP规范部分并不能证明iOS 6的缓存行为是正确的。默认行为应该是不缓存POST响应(即,未定义“Cache-Control”标头时)。该行为违反了规范,应该被视为一个错误。任何构建xml / json api Web服务的人都应该用“Cache-control:no-cache”修饰他们的POST响应来解决这个问题。 - David H
POST请求是非幂等的,这意味着它们不应被缓存 除非 响应特别建议通过其响应头进行此操作。 - James M. Greene
正如大卫所说,这明显违反了你引用的句子。如果没有“Cache-Control或Expires头字段”,则显然不包括适当的此类头。然而,你自己的调查显示它在那种情况下缓存。请编辑你的答案。 - Matthew Flaschen
有谁知道结果在设备上缓存了多长时间?我试过杀死safari并重启我的手机,但它仍然被缓存。我知道它可以清除浏览器缓存,但我想知道在它消失之前曾经遇到问题需要多长时间。不是每个人都会想到清除他们的缓存...... - Daniel Hallqvist


我希望这可以对其他开发人员在这个问题上猛烈抨击。我发现以下任何一种情况都会阻止iOS 6上的Safari缓存POST响应:

  • 在请求标头中添加[cache-control:no-cache]
  • 添加可变URL参数,例如当前时间
  • 在响应头中添加[pragma:no-cache]
  • 在响应头中添加[cache-control:no-cache]

我的解决方案是我的Javascript中的以下内容(我的所有AJAX请求都是POST)。

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

我还为我的许多服务器响应添加了[pragma:no-cache]标头。

如果你使用上面的解决方案,请注意任何$ .ajax()调用你设置为global:false将不使用$ .ajaxSetup()中指定的设置,因此你需要再次添加标题。


143
2017-10-12 09:56



这是错误的正确解决方案。问题是iOS 6将从它的缓存服务POST请求,而不是将它们发送到服务器。该错误并不是它缓存来自POST请求的响应(允许)。如果您仍希望响应从缓存中检索的POST请求,以便对该URI进行后续GET请求,请使用此解决方案。 - Nicholas Shanks
完善!这非常非常感谢! - JamesG
这对我有用,但我不明白怎么做。我已经在我的ajaxSetup中指定了cache:false,并查看了请求标头,归结为Cache-Control:no-cache和Pragma:no-cache - 但它仍将缓存在iPad上。然后,当我将标题:{“cache-control”:“no-cache”}添加到a​​jaxSetup中时,它将Cache-Control标头加倍为“no-cache,no-cache” - 并停止缓存。这里发生了什么事? - Tom Hall
工作完美 - 您还可以将请求添加为参数$ .ajax({type:'POST',headers:{'cache-control':'no-cache'}等}}) - George Filippakos
这对我不起作用.. - Vaibhav


假设您正在使用jQuery,所有Web服务请求的简单解决方案:

$.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()
    }));
});

阅读有关jQuery prefilter调用的更多信息 这里

如果您不使用jQuery,请检查所选库的文档。它们可能具有类似的功能。


64
2017-09-21 08:53



它对我不起作用,服务器响应:“无效的原始JSON:timeStamp”asp.net / iis 7.5 - Alexandre
怎么样$ .ajax({“cache”:false ...})?它会附加_ = [TIMESTAMP]吗? (我没有这样的设备来测试它) - Karussell
我已经发布了Karussell提出的解决方案的完整实现。请参阅下面的答案。 - Sam Shiles
@Karussell。刚尝试设置$ .ajax({“cache”:false ...})。这不能解决iOS6上POST请求的问题。据推测,因为JQuery根据他们的文档假设没有浏览器足以愚蠢到缓存帖子请求。 “使用POST获取的页面永远不会被缓存,因此jQuery.ajaxSetup()中的缓存和ifModified选项对这些请求没有影响。” - Brett Hannah
这不起作用。它不合并帖子参数。戴夫的帖子是一个更好的解决方案。 - Chris Muench


我从webapp服务器获取数据的webapp也遇到了同样的问题

这对我有用:

public WebService()
{
    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    ...
}

40
2017-09-20 21:28



这在C#Web Services中对我有用。 - Jay Mayu
在WCFland为我们工作 - Trent
非常感谢你!我疯狂地想弄清楚为什么iPhone的表现与其他平台差别很大。这种特定于ASP.NET的解决方案为我节省了大量时间。 - Mark Brittingham
在iOS6上没有工作,看到我的答案在线程结束 - Brian Ogden
请!!!!设置条件仅在IOS 6上应用此功能,内容缓存对任何应用程序都至关重要。 - Alexandre


我刚才也有这个问题 PhoneGap的 应用。我通过使用JavaScript函数解决了它 getTime() 以下列方式:

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

我浪费了几个小时搞清楚这一点。 Apple通知开发人员这个缓存问题本来不错。


40
2017-09-20 07:34



我打算评论使用 {cache:false} 作为任何一种选择 $.post() 要么 $.ajaxSetup()但是根据 文档,这些论点被忽略了; jQuery将“永远不会缓存”发布请求,但不会考虑浏览器。也许更简洁的选择是为使用的请求添加时间戳 $.ajaxPrefilter()。 - fwielstra
我花了将近5个小时来解决这个问题,最后添加时间戳就可以了 function send_ajax(my_data,refresh) ..请参考这里 stackoverflow.com/questions/14733772/... - rusly


最后,我找到了上传问题的解决方案。

在JavaScript中:

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

PHP

header('cache-control: no-cache');

22
2017-09-22 10:16





来自我自己的博文 iOS 6.0缓存Ajax POST请求

如何解决:有多种方法可以防止缓存请求。建议的方法是添加no-cache标头。这就是它的完成方式。

jQuery的:

检查iOS 6.0并设置Ajax标头如下:

$.ajaxSetup({ cache: false });

ZeptoJS:

检查iOS 6.0并设置Ajax标头如下:

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

服务器端

Java的:

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

在将任何数据发送到客户端之前,请务必在页面顶部添加此项。

。净

Response.Cache.SetNoStore();

要么

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.

14
2017-11-14 06:02



.NET的良好无缓存属性 stackoverflow.com/questions/10011780/... - Aran Mulholland


这个JavaScript代码段适用于jQuery和jQuery Mobile:

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

只需将它放在JavaScript代码中的某个位置(在加载jQuery之后,最好在执行AJAX请求之前),它应该有所帮助。


7
2018-01-25 22:11





GWT-RPC服务的快速解决方法是将其添加到所有远程方法:

getThreadLocalResponse().setHeader("Cache-Control", "no-cache");

5
2017-10-16 22:57



我们大多数人在他们的GWT部署中有数百种远程方法。是否有通用的方法为所有请求设置缓存控制头? - dirkoneill


这是Baz1nga答案的更新。以来 options.data 不是一个对象,而是一个字符串,我只是采用连接时间戳:

$.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();
  }
});

5
2017-10-25 12:32



添加时间戳是个坏主意,请尝试使用Dave的解决方案。 - Nicholas Shanks


您还可以通过修改此问题来解决此问题 jQuery的  阿贾克斯 函数通过执行以下(从1.7.1开始)到Ajax函数的顶部(函数从第7212行开始)。此更改将为所有POST请求激活jQuery的内置反缓存功能。

(完整的脚本可在以下位置获得 http://dl.dropbox.com/u/58016866/jquery-1.7.1.js。)

在第7221行下方插入:

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

然后修改以下内容(从第〜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 : "");
    }
}

至:

// 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 : "");
}

5
2017-09-27 14:30



这不是改变jQuery的好方法,也不是你不拥有的任何代码。 (每次您想要更新版本时,您都必须再次进行更改。(或者另一个开发人员更新,程序不起作用)) - andlrc
如果您需要尽可能快的解决方案来缓解Apple的愚蠢行为,这是一种非常有效的方法。此解决方案已用于解决每天接收数百万次点击的大型站点的问题,并且只需对一个文件进行更改即可实现此目的。 - Sam Shiles
你可以看看 jQuery.ajaxPrefiler 它允许您在创建之前修改ajax请求。您可以使用更优化和更新安全的代码对其进行归档。 - andlrc
preFilter方法的问题是您需要注册过滤器。如果你有一个在每个页面加载时运行的公共脚本,那么很好,但如果你没有,你将不得不为每个使用ajax的页面设置preFilter。我面临的情况是,我们有一个JQ文件的公共位置,该文​​件被用作7个以上个人网站的资源。由于这个错误,我们每小时损失数千英镑,我建议的方法使我们能够通过更改ONE文件在最短的时间内解决问题。我原则上同意你的意见,但你有时必须务实! - Sam Shiles
然后,您可以再次将其添加到该文件的末尾。好你解决了,你的公司一定为你感到高兴。 - andlrc