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

1051

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

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

getNewRecordID(intRecordType)

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

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


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

getNewRecordID(intRecordType, strTimestamp)

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

  • 1
    Есть ли подтверждение от Apple по этому поводу?
  • 189
    Это абсолютно шокирует. Мы также просто потратили пару часов, пытаясь понять, что что-то перестало работать. Наш логин AJAX, который выполняет POST (и имеет заголовки для предотвращения кеширования), кэшируется Safari, поэтому он просто возвращает тот же JSON, который он делал в прошлый раз, даже не пытаясь использовать сервер ... невероятно! Нам придется взломать исправление, но вы никогда не должны кэшировать POST, это безумие.
Показать ещё 21 комментарий
Теги:
caching
mobile-safari

25 ответов

430

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

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

Итак:

  • Нет кеша-контроля или истекает заголовок = 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 будет одинаковым, поэтому измените любые данные POST или URL-адрес, и вы в порядке. Таким образом, вы можете, как упоминалось в другом месте, просто добавить некоторые случайные данные в URL-адрес или немного данных POST.

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

SetEnvIf Request_Method "POST" IS_POST
Header set Cache-Control "no-cache" env=IS_POST
  • 6
    Я вижу, к чему идет Apple, но мы видим кэшированные ответы на POST-запросы, даже если наши ответы не содержат заголовков Cache-Control или Expires. Разве этот экземпляр iOS6 не должен кэшировать и отправлять каждый запрос. Этого не происходит.
  • 1
    Да, это то, что я тоже вижу. Нет заголовков или просто возрастные заголовки, и он будет кешироваться, вам нужен более явный заголовок без кеша.
Показать ещё 10 комментариев
141

Я надеюсь, что это может пригодиться другим разработчикам, ударяющим головой о стену на этом. Я обнаружил, что любое из следующих действий предотвращает кэширование ответа POST Safari на iOS 6:

  • добавление [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(), поэтому вам нужно будет добавить заголовки в еще раз.

  • 4
    Это правильное решение ошибки. Ошибка заключается в том, что iOS 6 будет обслуживать POST-запросы из своего кэша, а не отправлять их на сервер. Ошибка не в том, что она кеширует ответы от POST-запросов (что разрешено). Если вы все еще хотите, чтобы ответы на POST-запросы извлекались из кэша для последующих GET-запросов к этому URI, используйте это решение.
  • 0
    ИДЕАЛЬНО! Это было так больно, спасибо!
Показать ещё 6 комментариев
64

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

Подробнее о вызове prefilter jQuery здесь.

Если вы не используете jQuery, проверьте документы для выбранной вами библиотеки. Они могут иметь схожие функции.

  • 3
    Мне это не работает, сервер отвечает: «Недопустимый примитив JSON: timeStamp» asp.net / iis 7.5
  • 3
    как насчет $ .ajax ({"cache": false ...})? будет ли он работать при добавлении _ = [TIMESTAMP]? (У меня нет такого устройства, чтобы проверить его)
Показать ещё 8 комментариев
39

У меня была эта проблема также в приложении PhoneGap. Я решил это, используя функцию JavaScript getTime() следующим образом:

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

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

  • 1
    Я собирался прокомментировать использование {cache:false} в качестве опции для $.post() или $.ajaxSetup() , но, согласно документации , эти аргументы игнорируются; jQuery никогда не будет кешировать почтовые запросы, но не учитывает браузер. Возможно, более подходящим вариантом было бы добавить $.ajaxPrefilter() метку к запросам, используя $.ajaxPrefilter() .
  • 0
    Я потратил почти 5 часов, чтобы исправить эту проблему, и, наконец, добавление метки времени выполнит трюк с function send_ajax(my_data,refresh) .. обратитесь сюда stackoverflow.com/questions/14733772/…
38

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

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

public WebService()
{
    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    ...
}
  • 0
    Это сработало для меня в C # Web Services.
  • 0
    У нас работает в WCFland
Показать ещё 3 комментария
21

Наконец, у меня есть решение проблемы с загрузкой.

В JavaScript:

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

В PHP:

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

Из моего собственного сообщения в блоге 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) {…}

Серверная сторона

Java:

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

Этот фрагмент кода отлично работает с jQuery и jQuery Mobile:

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

Просто поместите его где-нибудь в свой код JavaScript (после загрузки jQuery и лучше всего перед выполнением AJAX-запросов), и это должно помочь.

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();
  }
});
  • 1
    Добавление временных меток - плохая идея, вместо этого попробуйте решение Дэйва.
5

Быстрая работа для служб GWT-RPC заключается в том, чтобы добавить это ко всем удаленным методам:

getThreadLocalResponse().setHeader("Cache-Control", "no-cache");
  • 0
    У большинства из нас есть сотни удаленных методов в их развертываниях GWT. Есть ли универсальный способ установить заголовок управления кешем для всех запросов?
5

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

(Полный script доступен в 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 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 : "");
    }
}

To:

// 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 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 : "");
}
  • 4
    Это не очень хороший подход, чтобы изменить jQuery или любой другой код, которым вы не владеете. (Каждый раз, когда вы хотите обновить версию, вам придется вносить изменения снова. (Или другой разработчик обновляется, и программа не работает))
  • 0
    Это совершенно обоснованный подход, если вам нужно самое быстрое решение для смягчения идиотизма Apple. Это решение было использовано для решения проблемы для массивного сайта, который получает миллионы посещений в день, и это позволило нам сделать это, просто внеся изменения в один файл.
Показать ещё 3 комментария
4

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

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

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

  • 0
    Будут ли кэшированы все ответы в файл или память телефона?
  • 0
    Это было не так со мной. Я добавил метку времени к своему URL (а не к параметрам публикации), и она отлично работает, как при просмотре в safari, так и при сохранении на главном экране.
3

Вещи, которые НЕ РАБОТАЮТ для меня с 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);
  • 0
    За что проголосовал против? Это важный информационный кеш: false не работает с iPad4 / iOS6, а также //asp.net: HttpContext.Current.Response.Cache.SetCacheability (HttpCacheability.NoCache)
  • 1
    +1 противостоять ненужно -1
Показать ещё 1 комментарий
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);    
2

Мое обходное решение в ASP.NET (pagemethods, webservice и т.д.)

protected void Application_BeginRequest(object sender, EventArgs e)
{
    Response.Cache.SetCacheability(HttpCacheability.NoCache);
}
  • 0
    Это не работает для iOS6
1

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

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

  • 3
    Я не думаю, что это ваше предложение ...
1

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

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

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

Например:

/ajax_helper.php?ts=3211321456

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

  • 0
    Старый ответ, но мои два цента: как правило, это хороший совет, и его понимают большинство компетентных веб-разработчиков, но в конкретном случае с jQuery, если вы создаете $.ajax и устанавливаете опции, имеющие {cache:false} то jQuery само по себе автоматически добавит кэш-очистку за кулисы без необходимости делать что-либо еще.
1

Я смог исправить свою проблему, используя комбинацию $.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');
                }
            }
        });
    });
});
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() {
    }

}
1

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

  • 1
    Хотя я согласен с вами в большинстве случаев, я бы сказал, что реальное решение этой проблемы для Apple - правильно реализовать HTTP. Имея это в виду, я бы не стал обвинять многих разработчиков в реализации самого простого из возможных решений до того времени. Для меня изменение реализации jquery было самым простым исправлением, поскольку оно позволило мне сделать одно редактирование и быть уверенным, что оно активно для всего моего сайта.
0

В Ruby Sinatra

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

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

<body onunload="">

ИЛИ через javascript

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

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

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

0

Я нашел одно обходное решение, из-за которого мне стало интересно, почему он работает. Прежде чем прочитать ответ Tadej о веб-сервисе 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 с дочерней страницы, я получаю обновленный контент.

0

В зависимости от приложения вы можете устранить проблему сейчас в iOS 6 с помощью Safari > Advanced > Web Inspector, так что это поможет в этой ситуации.

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

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

Вам может потребоваться также посмотреть ваше приложение, проверьте NSURLCache, если в WebView в пользовательском приложении.

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

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

Ссылка: $.ajax calls

  • 0
    Хотя этот вопрос не касается непосредственно исходного вопроса, это очень полезная информация для возможности устранения неполадок на устройствах в целом, поэтому я проголосую за него.
-1

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню