簡體   English   中英

iOS 6 上的 Safari 是否緩存 $.ajax 結果?

[英]Is Safari on iOS 6 caching $.ajax results?

自從升級到 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參數,並在服務器端丟棄該值。 這解決了這個問題。

經過一番調查,結果表明 iOS6 上的 Safari 會緩存沒有 Cache-Control 標頭甚至“Cache-Control: max-age=0”的 POST。

我發現阻止這種緩存在全局級別發生而不是必須在服務調用結束時破解隨機查詢字符串的唯一方法是設置“緩存控制:無緩存”。

所以:

  • 沒有 Cache-Control 或 Expires 標頭 = iOS6 Safari 將緩存
  • Cache-Control max-age=0 和一個立即 Expires = iOS6 Safari 將緩存
  • 緩存控制:no-cache = iOS6 Safari 不會緩存

我懷疑 Apple 正在從關於 POST 的第 9.5 節中的 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

我希望這對其他開發人員在這個問題上撞牆有用。 我發現以下任何一項都會阻止 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() 中指定的設置,因此您需要再次添加標頭。

假設您使用的是 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 預過濾器調用的更多信息。

如果您不使用 jQuery,請查看您選擇的庫的文檔。 它們可能具有相似的功能。

我剛剛在PhoneGap應用程序中也遇到了這個問題。 我通過以下方式使用 JavaScript 函數getTime()了這個問題:

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

我浪費了幾個小時來弄清楚這一點。 蘋果公司最好通知開發人員這個緩存問題。

我在 webapp 從 ASP.NET webservice 獲取數據時遇到了同樣的問題

這對我有用:

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

最后,我找到了上傳問題的解決方案。

在 JavaScript 中:

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

PHP 中

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

從我自己的博客文章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) {…}

服務器端

爪哇:

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.

這個 JavaScript 片段非常適合 jQuery 和 jQuery Mobile:

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

只需將它放在 JavaScript 代碼中的某個位置(在加載 jQuery 之后,最好在執行 AJAX 請求之前),它應該會有所幫助。

您還可以通過在Ajax函數的頂部(函數從第 7212 行開始)執行以下操作(從 1.7.1 開始)修改jQuery Ajax函數來解決此問題。 此更改將為所有 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 : "");
}

GWT-RPC 服務的一個快速解決方法是將其添加到所有遠程方法中:

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

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

為了解決添加到主屏幕的 Web 應用程序的此問題,需要遵循兩種投票最多的解決方法。 需要在網絡服務器上關閉緩存以防止新請求被緩存,並且需要向每個發布請求添加一些隨機輸入,以便已經緩存的請求通過。 請參考我的帖子:

iOS6 - 有沒有辦法清除添加到主屏幕的 webapp 緩存的 ajax POST 請求?

警告:對於通過向其請求添加時間戳而不關閉服務器上的緩存來實施變通方法的任何人。 如果您的應用程序被添加到主屏幕,現在每個帖子響應都將被緩存,清除 safari 緩存不會清除它,它似乎也不會過期。 除非有人有辦法清除它,否則這看起來像是潛在的內存泄漏!

使用 iPad 4/iOS 6 對我不起作用的事情:

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

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

向我的 jQuery ajax 調用添加緩存: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);

這就是 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);    

我在ASP.NET 中的解決方法(頁面方法、網絡服務等)

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

雖然添加緩存破壞器參數使請求看起來不同似乎是一個可靠的解決方案,但我建議不要這樣做,因為它會損害任何依賴實際緩存發生的應用程序。 使 API 輸出正確的標頭是最好的解決方案,即使這比向調用者添加緩存破壞器稍微困難一些。

對於那些使用Struts 1 ,這是我解決問題的方法。

網頁.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() {
    }

}

我能夠通過組合使用 $.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');
                }
            }
        });
    });
});

我想你已經解決了你的問題,但讓我分享一個關於網絡緩存的想法。

確實,您可以在您使用的每種語言中添加許多標頭,服務器端,客戶端,並且您可以使用許多其他技巧來避免 Web 緩存,但始終認為您永遠無法知道客戶端從哪里連接到您的服務器,你永遠不知道他是否在使用使用 Squid 或其他緩存產品的酒店“熱點”連接。

如果用戶使用代理來隱藏他的真實位置等……避免緩存的真正唯一方法是請求中的時間戳,如果未使用。

例如:

/ajax_helper.php?ts=3211321456

然后您必須傳遞的每個緩存管理器都沒有在緩存存儲庫中找到相同的 URL,並重新下載頁面內容。

根據應用程序,您現在可以在 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 調用

我找到了一種解決方法,讓我好奇它為什么起作用。 在閱讀 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,我會得到更新的內容。

雖然我的登錄和注冊頁面在 Firefox、IE 和 Chrome 中就像一個魅力......我一直在為 IOS 和 OSX 的 Safari 解決這個問題,幾個月前我在 SO 上找到了一個解決方法。

<body onunload="">

或通過javascript

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

這有點丑陋,但可以使用一段時間。

我不知道為什么,但是向onunload事件返回 null 頁面不會在 Safari 中緩存。

在Ruby的Sinatra中

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

我們發現,運行 iOS 9 和 10 版本的舊 iPhone 和 iPad 偶爾會返回虛假的空白 AJAX 結果,這可能是由於 Apple 降低了 CPU 速度。 返回空白結果時,iOS 不會調用服務器,就像從緩存中返回結果一樣。 頻率變化很大,從大約 10% 到 30% 的 AJAX 調用返回空白。

解決方案令人難以置信。 只需等待 1 秒,然后再次調用。 在我們的測試中,只需要重復一次,但我們編寫了最多可調用 4 次的代碼。 我們不確定是否需要 1s 等待,但我們不想冒着重復調用突發而給服務器帶來負擔的風險。

我們發現問題發生在兩個不同的 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 的最高結果。

只有在IIS 中添加pragma:no-cache標頭后,它才能與ASP.NET一起使用。 Cache-Control: no-cache是不夠的。

我建議一種解決方法來修改函數簽名是這樣的:

getNewRecordID(intRecordType, strTimestamp) 然后總是傳入一個 TimeStamp 參數,並在服務器端丟棄該值。 這解決了這個問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM