Safari на iOS 6 кеширует результаты $.ajax?

После обновления до iOS 6 мы видим, что веб-представление Safari берет на себя кеширование. $.ajax звонки. Это в контексте приложения PhoneGap, поэтому оно использует Safari WebView. наш $.ajax звонки POST методы и у нас кеш установлен в false {cache:false}, но все же это происходит. Мы пытались вручную добавить TimeStamp к заголовкам, но это не помогло.

Мы провели дополнительные исследования и обнаружили, что Safari возвращает кэшированные результаты только для веб-сервисов, которые имеют статическую сигнатуру функции и не меняются от вызова к вызову. Например, представьте себе функцию, которая называется что-то вроде:

getNewRecordID(intRecordType)

Эта функция снова и снова получает одни и те же входные параметры, но возвращаемые данные должны каждый раз отличаться.

Должно быть, в спешке Apple, чтобы сделать iOS 6 впечатляющим, они были слишком довольны настройками кэша. Кто-нибудь еще видел такое поведение на iOS 6? Если так, что именно вызывает это?


Обходной путь, который мы нашли, состоял в том, чтобы изменить сигнатуру функции так:

getNewRecordID(intRecordType, strTimestamp)

а потом всегда переходить в TimeStamp параметр, и просто отбросьте это значение на стороне сервера. Это работает вокруг проблемы. Я надеюсь, что это поможет какой-то другой бедной душе, которая потратит 15 часов на эту проблему, как я!

25 ответов

После небольшого исследования выясняется, что Safari на iOS6 будет кэшировать POST, которые не имеют заголовков Cache-Control или даже "Cache-Control: max-age=0".

Единственный способ предотвратить кеширование на глобальном уровне вместо того, чтобы взламывать случайные строки запросов в конце вызовов службы, - установить "Cache-Control: no-cache".

Так:

  • Нет заголовков Cache-Control или Expires = iOS6 Safari будет кешировать
  • Cache-Control max-age=0 и немедленное Expires = iOS6 Safari будет кешировать
  • Cache-Control: no-cache = iOS6 Safari НЕ будет кешировать

Я подозреваю, что Apple использует это из спецификации HTTP в разделе 9.5 о POST:

Ответы на этот метод не могут быть кэшированы, если ответ не включает в себя соответствующие поля заголовка Cache-Control или Expires. Однако ответ 303 (см. "Другое") можно использовать для того, чтобы указать пользовательскому агенту извлечь кэшируемый ресурс.

Так что в теории вы можете кешировать POST-ответы... кто знал. Но никто другой производитель браузеров никогда не думал, что это будет хорошей идеей до сих пор. Но это НЕ учитывает кэширование, когда не установлены заголовки Cache-Control или Expires, только когда они есть. Так что это должно быть ошибка.

Ниже приведено то, что я использую в правильной части конфигурации Apache для целевого использования всего моего API, потому что в действительности я не хочу ничего кешировать, даже получать. То, что я не знаю, как установить это только для POST.

Header set Cache-Control "no-cache"

Обновление: только что заметил, что я не указал, что это только когда POST одинаков, поэтому измените любые данные или URL POST, и все в порядке. Таким образом, вы можете, как упоминалось в другом месте, просто добавить некоторые случайные данные в URL или немного данных POST.

Обновление: вы можете ограничить "no-cache" только POST, если вы хотите, чтобы это было в Apache:

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

Я надеюсь, что это может быть полезно для других разработчиков, которые бьют головой об стену на этом. Я обнаружил, что любое из следующих действий не позволяет Safari на iOS 6 кэшировать ответ 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(), которые имеют глобальный характер: false НЕ будут использовать параметры, указанные в $.ajaxSetup(), поэтому вам нужно будет снова добавить заголовки.

Простое решение для всех ваших запросов веб-сервисов, если вы используете 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()
    }));
});

Узнайте больше о вызове префильтра 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);

Я потратил несколько часов, чтобы понять это. Было бы неплохо, если бы Apple уведомила разработчиков об этой проблеме с кэшированием.

У меня была та же проблема с веб-приложением, получающим данные из веб-службы ASP.NET

Это сработало для меня:

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-запросы:

Как это исправить: Существуют различные способы предотвращения кэширования запросов. Рекомендуемый метод - добавление заголовка без кэширования. Вот как это делается.

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), и это должно помочь.

Вы также можете устранить эту проблему, изменив функцию jQuery Ajax, выполнив следующее (начиная с 1.7.1) в верхней части функции Ajax (функция начинается со строки 7212). Это изменение активирует встроенную функцию защиты от кеша jQuery для всех запросов POST.

(Полный сценарий доступен на 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();
  }
});

Вещи, которые не работали для меня с iPad 4/iOS 6:

Мой запрос, содержащий: Cache-Control:no-cache

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

Добавление кэша: 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);

Чтобы решить эту проблему для веб-приложений, добавленных на главный экран, необходимо следовать обоим лучшим решениям. Кэширование должно быть отключено на веб-сервере, чтобы предотвратить дальнейшее кэширование новых запросов, и к каждому пост-запросу необходимо добавлять случайный ввод, чтобы запросы, которые уже были кэшированы, проходили. Пожалуйста, обратитесь к моему сообщению:

iOS6 - Есть ли способ очистки кэшированных POST-запросов ajax для веб-приложения, добавленных на домашний экран?

ВНИМАНИЕ: любому, кто внедрил обходной путь, добавив временную метку к своим запросам, не отключая кэширование на сервере. Если ваше приложение добавлено на домашний экран, КАЖДЫЙ пост-ответ теперь будет кэшироваться, очистка кэша сафари не очищает его и, похоже, не истекает. Если у кого-то нет способа очистить его, это выглядит как потенциальная утечка памяти!

Это обходной путь для 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);
}

Я смог исправить свою проблему, используя комбинацию $.ajaxSetup и добавив метку времени к URL-адресу моего сообщения (не к параметрам / телу сообщения). Это основано на рекомендациях предыдущих ответов

$(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 - лучшее решение, даже если это немного сложнее, чем добавление кешей для вызывающих.

Мы обнаружили, что старые iPhone и iPad с iOS версий 9 и 10 иногда возвращают ложные пустые результаты AJAX, возможно, из-за того, что Apple снизила скорость процессора. При возврате пустого результата iOS не вызывает сервер, как будто возвращает результат из кеша. Частота варьируется в широких пределах: примерно от 10% до 30% вызовов AJAX не возвращаются.

В решение сложно поверить. Просто подождите 1 сек и позвоните снова. В нашем тестировании требовалось всего лишь одно повторение, но мы написали код для вызова до 4 раз. Мы не уверены, требуется ли ожидание в одну секунду, но мы не хотели рисковать, обременяя наш сервер пакетами повторных вызовов.

Мы обнаружили, что проблема возникла с двумя разными вызовами 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 при поиске этой проблемы.

Для тех, кто использует 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() {
    }

}

Я думаю, что вы уже решили свою проблему, но позвольте мне поделиться идеей о веб-кэшировании.

Это правда, что вы можете добавить много заголовков на каждый используемый вами язык, на стороне сервера, на стороне клиента, и вы можете использовать множество других приемов, чтобы избежать веб-кэширования, но всегда думайте, что вы никогда не узнаете, откуда клиент подключается к вашему серверу, Вы никогда не знаете, использует ли он соединение "Hot-Spot" в отеле, которое использует Squid или другие продукты для кеширования.

Если пользователи используют прокси, чтобы скрыть свою реальную позицию и т. Д., Реальный единственный способ избежать кеширования - это отметка времени в запросе, даже если она не используется.

Например:

/ajax_helper.php?ts=3211321456

Тогда каждый менеджер кеша, который вы должны передать, не нашел тот же URL в хранилище кеша и перезагружал содержимое страницы.

В зависимости от приложения вы можете решить проблему сейчас в iOS6, используя Safari> Дополнительно> Веб-инспектор, что поможет в этой ситуации.

Подключите телефон к Safari на Mac, а затем используйте меню разработчика для устранения неполадок в веб-приложении.

Очистите данные веб-сайта на iPhone после обновления до iOS6, в том числе специфичные для приложения с помощью веб-представления. Только у одного приложения была проблема, и это решило ее во время бета-тестирования IOS6, с тех пор никаких реальных проблем не было.

Вам также может понадобиться взглянуть на ваше приложение, проверьте NSURLCache, если есть в Web View в пользовательском приложении.

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

Я думаю, в зависимости от истинной природы вашей проблемы, реализации и т. Д..

Ссылка: $.ajax звонки

Хотя мои страницы входа в систему и регистрации в Firefox, IE и Chrome работают как чудо... Я боролся с этой проблемой в Safari для IOS и OSX, несколько месяцев назад я нашел обходной путь для SO.

<body onunload="">

ИЛИ через JavaScript

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

Это довольно уродливо, но работает какое-то время.

Я не знаю почему, но возвращаю ноль в onunload Если страница не кэшируется в Safari.

В Рубиновой Синатре

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

Я нашел один обходной путь, который заставляет меня интересоваться, почему это работает. Прежде чем прочитать ответ Тадея о веб-сервисе ASP.NET, я пытался придумать что-нибудь, что сработало бы.

И я не говорю, что это хорошее решение, но я просто хотел документировать его здесь.

главная страница: включает функцию 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 со страницы ребенка, я получаю обновленный контент.

Он работал с ASP.NET только после добавления pragma:no-cache Заголовок в IIS. Cache-Control: no-cache было недостаточно.

Я предлагаю обходной путь, чтобы изменить сигнатуру функции так:

getNewRecordID (intRecordType, strTimestamp) и затем всегда передают также параметр TimeStamp и просто отбрасывают это значение на стороне сервера. Это работает вокруг проблемы.

Другие вопросы по тегам