iOS 6上的Safari缓存$ .ajax结果吗?

时间:2012-09-20 06:07:23

标签: javascript jquery ajax caching mobile-safari

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

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

的函数
getNewRecordID(intRecordType)

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

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


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

getNewRecordID(intRecordType, strTimestamp)

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

26 个答案:

答案 0 :(得分:442)

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

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

所以:

  • 没有缓存控制或过期标头= iOS6 Safari将缓存
  • Cache-Control max-age = 0,立即Expires = iOS6 Safari将缓存
  • Cache-Control: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

答案 1 :(得分:145)

我希望这对其他开发者来说可以用来对付这个问题。我发现以下任何一种情况都会阻止iOS 6上的Safari缓存POST响应:

  • 在请求标头中添加[cache-control:no-cache]
  • 添加可变网址参数,例如当前时间
  • 在响应标头中添加[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()中指定的设置,因此你需要添加标题试。

答案 2 :(得分:66)

假设您正在使用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 call here

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

答案 3 :(得分:42)

我从ASP.NET webservice

获取数据时遇到了同样的问题

这对我有用:

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

答案 4 :(得分:42)

我在PhoneGap应用程序中也遇到过这个问题。我通过以下方式使用JavaScript函数getTime()来解决它:

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

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

答案 5 :(得分:23)

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

在JavaScript中:

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

PHP中:

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

答案 6 :(得分:14)

来自我自己的博客帖子 iOS 6.0 caching Ajax POST requests

如何解决:有多种方法可以防止缓存请求。建议的方法是添加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) {…}

服务器端

爪哇:

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

确保在将任何数据发送到客户端之前将其添加到页面顶部。

.NET

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.

答案 7 :(得分:7)

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

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

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

答案 8 :(得分:6)

您还可以通过修改jQuery Ajax函数来解决此问题,方法是执行以下操作(从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 : "");
}

答案 9 :(得分:5)

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

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

答案 10 :(得分:5)

这是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();
  }
});

答案 11 :(得分:4)

为了解决添加到主屏幕的WebApps的这个问题,需要遵循两个最高投票的解决方法。需要在Web服务器上关闭缓存,以防止新请求被缓存,并且需要向每个post请求添加一些随机输入,以便已经缓存的请求通过。请参阅我的帖子:

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

警告:对于通过在请求中添加时间戳而不关闭服务器上的缓存来实施解决方法的任何人。如果您的应用程序已添加到主屏幕,则现在将缓存每个帖子响应,清除safari缓存不会清除它并且它似乎没有过期。除非有人有办法清除它,否则这看起来像是潜在的内存泄漏!

答案 12 :(得分:3)

这是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);    

答案 13 :(得分:3)

对于使用iPad 4 / iOS 6的无法正常工作的事情:

我的请求包含:Cache-Control:no-cache

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

将cache:false添加到我的jQuery ajax调用

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

只有这样才能解决问题:

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

答案 14 :(得分:2)

我在ASP.NET(页面方法,网络服务等)的解决方法

protected void Application_BeginRequest(object sender, EventArgs e)
{
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
}

答案 15 :(得分:1)

虽然添加cache-buster参数以使请求看起来不同似乎是一个可靠的解决方案,但我会反对它,因为它会伤害任何依赖于实际缓存的应用程序。使API输出正确的标头是最好的解决方案,即使这比向调用者添加缓存触发器要困难一些。

答案 16 :(得分:1)

对于那些使用Struts 1的人,以下是我解决问题的方法。

<强>的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() {
    }

}

答案 17 :(得分:1)

我能够通过使用$ .ajaxSetup的组合并将时间戳附加到我的帖子的url(而不是post参数/ body)来解决我的问题。这基于以前答案的建议

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

答案 18 :(得分:1)

我认为您已经解决了问题,但让我分享一下有关网络缓存的想法。

确实,你可以在你使用的每种语言,服务器端,客户端添加许多标题,并且你可以使用许多其他技巧来避免网络缓存,但总是认为你永远不知道客户端连接到哪里您的服务器,您永远不知道他是否正在使用使用Squid或其他缓存产品的酒店“热点”连接。

如果用户使用代理来隐藏他的真实位置等...... 真正的唯一的方法是避免缓存是请求中的时间戳,如果未使用的话。

例如:

/ajax_helper.php?ts=3211321456

然后,您必须传递的每个缓存管理器都没有在缓存存储库中找到相同的URL并重新下载页面内容。

答案 19 :(得分:0)

我找到了一个解决方法让我对它的工作原理感到好奇。在阅读Tadej关于ASP.NET Web服务的答案之前,我试图提出一些可行的方法。

我并不是说这是一个很好的解决方案,但我只想在这里记录下来。

主页面:包含JavaScript函数checkStatus()。该方法调用另一个使用jQuery AJAX调用来更新html内容的方法。我使用setInterval来调用checkStatus()。当然,我遇到了缓存问题。

解决方案:使用其他页面调用更新。

在主页面上,我设置了一个布尔变量runUpdate,并将以下内容添加到body标签中:

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

在helper.html中:

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

因此,如果从主页调用checkStatus(),我将获得缓存内容。如果我从子页面调用checkStatus,我会获得更新的内容。

答案 20 :(得分:0)

根据应用程序的不同,您可以使用Safari&gt; Advanced&gt; Web Inspector在iOS 6中解决问题,以便在这种情况下提供帮助。

在Mac上将手机连接到Safari,然后使用开发人员菜单来解决网络应用的问题。

在更新到iOS6后清除iPhone上的网站数据,包括使用Web View特定于应用程序。只有一个应用程序有问题,这在IOS6 Beta测试期间解决了它,因为那时没有真正的问题。

您可能还需要查看您的应用,如果在自定义应用中的WebView中,请查看NSURLCache。

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

我想这取决于你的问题,实施等的真实性质。

参考:$ .ajax调用

答案 21 :(得分:0)

虽然我的登录和注册页面就像Firefox,IE和Chrome中的魅力......我在Safari for IOS和OSX中一直在努力解决这个问题,几个月前我在SO上找到了解决方法。

<body onunload="">

或通过javascript

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

这有点难看,但有一段时间有效。

我不知道为什么,但是返回null到onunload事件,页面不会在Safari中缓存。

答案 22 :(得分:0)

在Ruby的Sinatra中

before '*' do
  if env['REQUEST_METHOD'] == 'POST'
    headers 'Cache-Control' => 'no-cache, no-store, must-revalidate'
  end
end

答案 23 :(得分:0)

我们发现,运行iOS版本9和10的旧版iPhone和iPad有时会返回虚假的空白AJAX结果,这可能是由于Apple降低了CPU速度。返回空白结果时,iOS不会调用服务器,就像从缓存中返回结果一样。频率变化很大,大约从10%到30%的AJAX调用返回空白。

该解决方案令人难以置信。请等待1秒钟,然后再次致电。在我们的测试中,只需要重复一次即可,但是我们编写的代码最多可以调用4次。我们不确定是否需要1s等待,但是我们不想冒重复呼叫的麻烦而使服务器负担重。

我们发现问题发生在两个不同的AJAX调用上,分别调用了具有不同数据的不同API文件。但我担心它可能在任何AJAX调用中发生。我们只是不知道,因为我们不会检查每个AJAX结果,也不会在旧设备上多次测试每个呼叫。

两个问题的AJAX调用都在使用:POST,异步= true,setRequestHeader =('Content-Type','application / x-www-form-urlencoded')

问题发生时,通常只有一个AJAX调用正在进行。因此,这并不是由于重叠的AJAX调用。有时问题是在设备繁忙时发生的,而有时不是在没有DevTools的情况下发生的。

iOS 13不会执行此操作,Chrome或Firefox也不会执行此操作。我们没有运行iOS 11或12的测试设备。也许其他人可以测试那些设备?

我在这里注意到这一点,因为该问题是搜索此问题时Google的最高搜索结果。

答案 24 :(得分:-1)

仅在ASP.NET中添加pragma:no-cache标头后才能使用IISCache-Control: no-cache还不够。

答案 25 :(得分:-2)

我建议修改函数签名的解决方法是这样的:

getNewRecordID(intRecordType,strTimestamp) 然后总是传入一个TimeStamp参数,并在服务器端丢弃该值。这解决了这个问题。