1096

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

我们进行了更多研究,发现 Safari 仅返回具有静态函数签名且不会因调用而变化的 Web 服务的缓存结果。例如,想象一个名为的函数:

getNewRecordID(intRecordType)

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

一定是 Apple 急于让 iOS 6 快速运行,他们对缓存设置太满意了。有没有其他人在 iOS 6 上看到过这种行为?如果是这样,究竟是什么原因造成的?


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

getNewRecordID(intRecordType, strTimestamp)

然后总是传入一个TimeStamp参数,然后在服务器端丢弃该值。这可以解决这个问题。

4

25 回答 25

449

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

我发现防止这种缓存在全局级别发生而不是将随机查询字符串破解到服务调用结束的唯一方法是设置“缓存控制:无缓存”。

所以:

  • 没有 Cache-Control 或 Expires 标头 = 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 就可以了。因此,您可以像其他地方提到的那样,向 URL 中添加一些随机数据或一些 POST 数据。

更新:如果您希望在 Apache 中这样,您可以将“无缓存”限制为 POST:

SetEnvIf Request_Method "POST" IS_POST
Header set Cache-Control "no-cache" env=IS_POST
于 2012-09-20T16:06:19.120 回答
148

我希望这对其他在这个问题上碰壁的开发人员有用。我发现以下任何一项都会阻止 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() 中指定的设置,因此您需要再次添加标头。

于 2012-10-12T09:56:46.610 回答
69

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.

于 2012-09-21T08:53:07.397 回答
44

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

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

我浪费了几个小时来弄清楚这一点。Apple 最好通知开发人员这个缓存问题。

于 2012-09-20T07:34:42.023 回答
42

我从 ASP.NET webservice 获取数据的 webapp 遇到了同样的问题

这对我有用:

public WebService()
{
    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    ...
}
于 2012-09-20T21:28:27.890 回答
24

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

在 JavaScript 中:

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

PHP中:

header('cache-control: no-cache');
于 2012-09-22T10:16:00.273 回答
15

从我自己的博客文章iOS 6.0 缓存 Ajax POST 请求

如何解决:有多种方法可以防止请求缓存。推荐的方法是添加一个无缓存标头。这就是它的完成方式。

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");

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

。网

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.
于 2012-11-14T06:02:35.647 回答
7

这个 JavaScript 片段非常适用于 jQuery 和 jQuery Mobile:

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

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

于 2013-01-25T22:11:59.940 回答
6

您还可以通过修改jQuery Ajax函数来解决此问题,方法是在 Ajax 函数的顶部执行以下操作(从 1.7.1 开始)(函数从第 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 : "");
}
于 2012-09-27T14:30:19.170 回答
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();
  }
});
于 2012-10-25T12:32:47.980 回答
5

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

getThreadLocalResponse().setHeader("Cache-Control", "no-cache");
于 2012-10-16T22:57:52.277 回答
4

为了解决添加到主屏幕的 WebApp 的此问题,需要遵循两个投票最多的解决方法。需要在网络服务器上关闭缓存以防止新请求被缓存,并且需要向每个发布请求添加一些随机输入,以便已经缓存的请求能够通过。请参考我的帖子:

iOS6 - 有没有办法清除添加到主屏幕的 webapp 的缓存 ajax POST 请求?

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

于 2012-09-28T15:57:22.513 回答
4

iPad 4/iOS 6适合我的事情:

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

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

在我的 jQuery ajax 调用中添加 cache: false

 $.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);
于 2013-04-29T06:43:01.680 回答
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);    
于 2012-10-12T06:55:45.497 回答
2

我在ASP.NET中的解决方法(页面方法、Web 服务等)

protected void Application_BeginRequest(object sender, EventArgs e)
{
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
}
于 2012-09-24T13:10:51.593 回答
1

我想你已经解决了你的问题,但让我分享一个关于网络缓存的想法。

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

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

例如:

/ajax_helper.php?ts=3211321456

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

于 2014-06-12T16:40:18.693 回答
1

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

于 2012-09-24T07:39:33.970 回答
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() {
    }

}
于 2013-01-09T22:24:28.333 回答
1

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

$(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');
                }
            }
        });
    });
});
于 2013-01-19T17:27:18.520 回答
0

根据应用程序,您现在可以在 iOS 6 中使用 Safari>Advanced>Web Inspector 解决问题,这有助于解决这种情况。

将手机连接到 Mac 上的 Safari,然后使用开发人员菜单对 Web 应用程序进行故障排除。

更新到 iOS6 后清除 iPhone 上的网站数据,包括特定于使用 Web 视图的应用程序。只有一个应用程序有问题,这在 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 调用

于 2012-09-21T21:57:13.430 回答
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,我会得到更新的内容。

于 2012-10-08T15:54:21.610 回答
0

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

<body onunload="">

或通过 javascript

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

这有点丑陋,但可以工作一段时间。

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

于 2014-07-19T21:12:36.537 回答
0

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

很难相信解决方案。只需等待 1 秒,然后再次调用。在我们的测试中,只需要重复一次,但我们编写的代码最多可以调用 4 次。我们不确定是否需要等待 1 秒,但我们不想冒着让服务器承受大量重复调用的风险。

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

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

当问题发生时,通常只有一个 AJAX 调用正在进行。所以这不是由于重叠的 AJAX 调用。有时问题会在设备繁忙时发生,但有时不会,而且如果没有 DevTools,我们真的不知道当时发生了什么。

iOS 13 没有这样做,Chrome 或 Firefox 也没有。我们没有任何运行 iOS 11 或 12 的测试设备。也许其他人可以测试这些设备?

我在这里注意到这一点,因为在搜索此问题时,此问题是 Google 搜索结果中排名靠前的。

于 2020-01-26T03:00:45.913 回答
-1

只有在IIS中添加标头后,它才能与ASP.NET一起使用。还不够。pragma:no-cacheCache-Control: no-cache

于 2012-09-24T22:05:41.437 回答
-2

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

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

于 2016-07-28T16:33:15.027 回答