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);
Чтобы решить эту проблему для веб-приложений, добавленных на главный экран, необходимо следовать обоим лучшим решениям. Кэширование должно быть отключено на веб-сервере, чтобы предотвратить дальнейшее кэширование новых запросов, и к каждому пост-запросу необходимо добавлять случайный ввод, чтобы запросы, которые уже были кэшированы, проходили. Пожалуйста, обратитесь к моему сообщению:
ВНИМАНИЕ: любому, кто внедрил обходной путь, добавив временную метку к своим запросам, не отключая кэширование на сервере. Если ваше приложение добавлено на домашний экран, КАЖДЫЙ пост-ответ теперь будет кэшироваться, очистка кэша сафари не очищает его и, похоже, не истекает. Если у кого-то нет способа очистить его, это выглядит как потенциальная утечка памяти!
Это обходной путь для 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);
Я смог исправить свою проблему, используя комбинацию $.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 в пользовательском приложении.
Я думаю, в зависимости от истинной природы вашей проблемы, реализации и т. Д..
Ссылка: $.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 со страницы ребенка, я получаю обновленный контент.
Я предлагаю обходной путь, чтобы изменить сигнатуру функции так:
getNewRecordID (intRecordType, strTimestamp) и затем всегда передают также параметр TimeStamp и просто отбрасывают это значение на стороне сервера. Это работает вокруг проблемы.