关于javascript:iOS 6上的Safari是否缓存$ .ajax结果?

Is Safari on iOS 6 caching $.ajax results?

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

我们做了更多的研究,发现Safari只返回Web服务的缓存结果,这些服务的函数签名是静态的,并且不会随调用而变化。例如,想象一个函数名为:

1
getNewRecordID(intRecordType)

这个函数一次又一次地接收相同的输入参数,但是它返回的数据每次都应该不同。

必须是在苹果的匆忙使iOS6压缩令人印象深刻,他们对缓存设置太满意。有人在iOS 6上看到过这种行为吗?如果是这样的话,到底是什么原因造成的?

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

1
getNewRecordID(intRecordType, strTimestamp)

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


经过一番调查,发现ios6上的safari将缓存没有缓存控制头甚至"cache control:max age=0"的帖子。

我发现防止这种缓存在全局级别发生的唯一方法是设置"缓存控制:不缓存"。

所以:

  • no cache control or expires headers=ios6 safari将缓存
  • 缓存控制最大年龄=0,立即到期=IOS6 Safari将缓存
  • 缓存控制:no cache=ios6 safari将不缓存

我怀疑苹果正在利用第9.5节关于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.

所以理论上,你可以缓存发帖回复…谁知道呢。但到目前为止,没有其他浏览器制造商认为这是一个好主意。但是,当没有设置缓存控制或Expires头文件时,只有在设置了某些头文件时,这并不能解释缓存。所以一定是个虫子。

下面是我在Apache配置的正确位置中使用的,它以整个API为目标,因为实际上我不想缓存任何东西,甚至不想获取。我不知道该如何设置这一点。

1
Header set Cache-Control"no-cache"

更新:只是注意到我没有指出只有当文章是相同的时候,所以更改任何文章数据或URL,你就可以了。所以你可以像其他地方提到的那样,只需向URL添加一些随机数据或者一些post数据。

更新:如果您希望在Apache中这样做,可以将"无缓存"限制为只发布:

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


我希望这对其他开发者来说是有用的。我发现以下任何一项都会阻止iOS 6上的Safari缓存后响应:

  • 在请求头中添加[缓存控制:无缓存]
  • 添加变量url参数,如当前时间
  • 在响应头中添加[pragma:no cache]
  • 在响应头中添加[缓存控制:无缓存]

我的解决方案在我的javascript中如下(我的所有Ajax请求都是post)。

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

我还将[pragma:no cache]头添加到我的许多服务器响应中。

如果使用上述解决方案,请注意,设置为global:false的任何$.ajax()调用都不会使用$.ajaxsetup()中指定的设置,因此需要再次添加头。


假设您使用jquery,那么所有Web服务请求的简单解决方案是:

1
2
3
4
5
6
$.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,请检查您选择的库的文档。它们可能具有类似的功能。


我在从ASP.NET WebService获取数据时遇到了同样的问题

这对我很有用:

1
2
3
4
5
public WebService()
{
    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    ...
}


我在PhoneGap应用程序中也遇到了这个问题。我通过使用javascript函数getTime()解决了这个问题,方法如下:

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

我花了几个小时才弄明白。如果苹果能把这个缓存问题通知开发者,那就太好了。


最后,我有了一个解决上传问题的方法。

在JavaScript中:

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

在PHP中:

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

从我自己的博客帖子iOS6.0缓存Ajax帖子请求:

如何修复它:有各种方法可以防止缓存请求。建议的方法是添加无缓存头。就是这样做的。

jQuery:

检查iOS 6.0并按如下方式设置Ajax头文件:

1
$.ajaxSetup({ cache: false });

ZeptoJS:

检查iOS 6.0并按如下方式设置Ajax头文件:

1
2
3
4
5
6
7
$.ajax({
    type: 'POST',
    headers : {"cache-control":"no-cache" },
    url : ,
    data:,
    dataType : 'json',
    success : function(responseText) {}

服务器端爪哇:

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

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

.NET

1
Response.Cache.SetNoStore();

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

PHP

1
2
header('Cache-Control: no-cache, no-store, must-revalidate'); // HTTP 1.1.
header('Pragma: no-cache'); // HTTP 1.0.


此javascript代码段适用于jquery和jquery mobile:

1
2
3
4
5
6
$.ajaxSetup({
    cache: false,
    headers: {
        'Cache-Control': 'no-cache'
    }
});

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


您还可以通过修改jquery ajax函数来解决这个问题,方法是在ajax函数的顶部执行以下操作(从1.7.1开始)(函数从第7212行开始)。此更改将激活jquery针对所有post请求的内置反缓存功能。

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

在第7221行下方插入:

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

然后修改以下内容(从第7497行开始)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 :"");
    }
}

到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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 :"");
}


这是Baz1nga答案的更新。由于options.data不是一个对象,而是一个字符串,所以我只是使用了连接时间戳的方法:

1
2
3
4
5
6
7
8
9
10
11
$.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();
  }
});


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

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


为了解决添加到主屏幕的webapps的这个问题,需要遵循两个投票最多的解决方法。需要在Web服务器上关闭缓存,以防止将来缓存新请求,并且需要向每个Post请求添加一些随机输入,以便已缓存的请求能够通过。请参考我的职位:

ios6-有没有一种方法可以清除添加到主屏幕的webapp的缓存Ajax Post请求?

警告:任何通过向请求添加时间戳而不关闭服务器上的缓存来实现变通方案的人。如果您的应用程序被添加到主屏幕,那么现在每个post响应都将被缓存,清除safari缓存不会清除它,而且似乎也不会过期。除非有人能清除它,否则这看起来像是潜在的内存泄漏!


这就是GWT-RPC的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);

iPad 4/iOS 6不适合我的东西:

我的请求包含:缓存控制:无缓存

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

向jquery ajax调用中添加cache:false

1
2
3
4
5
6
 $.ajax(
        {
            url: postUrl,
            type:"POST",
            cache: false,
            ...

只有这样才能做到:

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


我在ASP.NET中的解决方案(PageMethods、WebService等)

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


对于那些使用Struts 1的用户,下面是我如何解决这个问题的。

Web.XML

1
2
3
4
5
6
7
8
9
10
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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() {
    }

}

我可以通过使用$ajaxsetup的组合并在我的文章的URL(而不是文章的参数/正文)上附加一个时间戳来解决我的问题。这是基于先前答案的建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$(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');
                }
            }
        });
    });
});

虽然添加cache buster参数使请求看起来不同似乎是一个可靠的解决方案,但我建议不要这样做,因为这样会损害任何依赖实际缓存的应用程序。让API输出正确的报头是最好的解决方案,即使这比向调用者添加缓存buster稍微困难一些。


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

的确,你可以在你使用的每种语言、服务器端、客户机端添加许多头文件,你可以使用许多其他技巧来避免Web缓存,但你总是认为你永远无法知道客户机从何处连接到你的服务器,你永远不知道他是否在使用酒店的"热点"连接,该连接使用Squid或其他缓存产品。

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

例如:

1
/ajax_helper.php?ts=3211321456

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


根据应用程序的不同,使用Safari>Advanced>Web Inspector在iOS 6中解决该问题时可能会遇到问题,因此这对这种情况很有帮助。

将手机连接到Mac和Safari,然后使用"开发人员"菜单来解决Web应用程序故障。

更新到ios6后,清除iPhone上的网站数据,包括使用Web视图的应用程序的特定数据。只有一个应用程序出现了问题,这在以前的ios6 beta测试中解决了问题,从那时起就没有真正的问题。

您可能还需要查看您的应用程序,如果在自定义应用程序的Web视图中,请查看nsurlcache。

http://Deavix.COM/Goo/Loo/Os/Ont/COCOA/Realth/Base/Base/NSurLCaseEx类/参考/引用。

我想这取决于你的问题的真实性质、实现等等…

参考:.ajax调用


当我的登录页面和注册页面在Firefox、IE和Chrome中发挥着魅力的时候…几个月前,我在iOS和OSX的Safari上找到了解决问题的方法。

1
<body onunload="">

或者通过javascript

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

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

我不知道为什么,但是返回空值到onunload事件,页面在safari中没有缓存。


在鲁比的西纳特拉

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

我找到了一个解决方法,让我好奇为什么它会起作用。在阅读Tadej关于ASP.NET Web服务的答案之前,我试图想出一些有用的方法。

我不是说这是一个好的解决方案,但我只是想把它记录在这里。

主页:包括一个javascript函数,checkstatus()。该方法调用另一个方法,该方法使用jquery-ajax调用来更新HTML内容。我使用setInterval调用checkStatus()。当然,我遇到了缓存问题。

解决方案:使用另一个页面调用更新。

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

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

在of helper.html中:

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

所以,如果从主页面调用checkstatus(),我会得到缓存的内容。如果我从子页面调用checkstatus,我会得到更新的内容。


它只在添加了IIS中的pragma:no-cache头之后才与ASP.NET一起工作。Cache-Control: no-cache还不够。


我建议一个解决方法,将函数签名修改为如下所示:

GetNewRecordID(intRecordType,strTimestamp)然后总是传入时间戳参数,并在服务器端丢弃该值。这可以解决这个问题。