Подтвердить что ты не робот

ASP.NET MVC и кэширование IE - управление заголовками ответов неэффективно

Фон

Я пытаюсь помочь коллеге отладить проблему, которая не была проблемой в течение последних 6 месяцев. После последнего развертывания приложения ASP.NET MVC 2 ответы FileResult, которые заставляют PDF файл у пользователя открывать или сохранять, имеют проблемы, которые достаточно долго сохраняются на клиентской машине для чтения PDF файлов, чтобы открыть их.

Более ранние версии IE (expecially 6) являются единственными затронутыми браузерами. Firefox и Chrome и более новые версии IE ( > 8) ведут себя так, как ожидалось. Имея это в виду, следующий раздел определяет действия, необходимые для воссоздания проблемы.

Поведение

  • Пользователь нажимает ссылку, указывающую на метод действия (простая гиперссылка с атрибутом href).
  • Метод действия генерирует PDF, представляемый как поток байтов. Метод всегда воссоздает PDF.
  • В методе действий заголовки настроены на указание браузерам, как кэшировать ответ. Это:

    response.AddHeader("Cache-Control", "public, must-revalidate, post-check=0, pre-check=0");
    response.AddHeader("Pragma", "no-cache");
    response.AddHeader("Expires", "0");
    

    Для тех, кто не знаком с тем, что делают заголовки:

    а. Cache-Control: public

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

    б. Cache-Control: must-revalidate

    Когда директива must-revalidate присутствует в ответе, полученном кешем, этот кеш НЕ ДОЛЖЕН использовать запись после того, как она станет устаревшей, чтобы ответить на последующий запрос без предварительной проверки его с сервером происхождения

    с. Cache-Control: предварительная проверка (представлена ​​с IE5)

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

    д. Cache-Control: post-check (введен с IE5)

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

    е. Pragma: no-cache (для обеспечения обратной совместимости с HTTP/1.0)

    Когда директива no-cache присутствует в сообщении-запросе, приложение ДОЛЖНО пересылать запрос на исходный сервер, даже если у него есть кэшированная копия того, что запрашивается.

    ф. Истекает

    В поле Expires entity-header указывается дата/время, после которого ответ считается устаревшим.

  • Мы возвращаем файл из действия

    return File(file, "mime/type", fileName);
    
  • Пользователь получает диалоговое окно "Открыть/Сохранить"

  • Нажатие кнопки "Сохранить" работает так, как ожидалось, но нажатие "Открыть" запускает PDF-ридер, но временный файл IE, который был сохранен, уже был удален к тому моменту, когда читатель пытается открыть файл, поэтому он жалуется, что файл отсутствует (и это так).

Здесь есть еще полдюжины других приложений, которые используют одни и те же заголовки, чтобы заставить Excel, CSV, PDF, Word и тонну другого контента у пользователей, и никогда не возникало проблем.

Вопрос

  • Правильны ли заголовки для того, что мы пытаемся сделать? Мы хотим, чтобы файл существовал временно (получить кеширование), но всегда должен быть заменен новыми версиями, хотя запросы могут быть идентичными).

Заголовки ответа устанавливаются в методе действий перед возвратом a FileResult. Я попросил своего коллегу попытаться создать новый класс, который наследует от FileResult, и вместо этого переопределить метод ExecuteResult, чтобы он изменял заголовки, а затем вместо него base.ExecuteResult() - никакого статуса.

У меня есть подозрение, что заголовок "Истекает" "0" является виновником. Согласно этой статье W3C, установка его в "0" означает "уже истек". Я действительно хочу, чтобы он истек, я просто не хочу, чтобы IE удалял его из файловой системы до того, как приложение обработало его, и он может открыть его.

Как всегда, спасибо!

Изменить: решение

При дальнейшем тестировании (используя Fiddler для проверки заголовков) мы видели, что заголовки ответов, которые, как мы думали, устанавливались, не были интерпретированы браузером. Не знакомый с кодом сам, я не знал об основной проблеме: заголовки попадали вне метода действий.

Тем не менее, я собираюсь оставить этот вопрос открытым. И все-таки выдающийся это: похоже, существует некоторая разница между заголовком Expires, имеющим значение 0 vs. -1. Если кто-то может претендовать на различия по дизайну, то в отношении IE я все равно хотел бы услышать об этом. Что касается решения, то вышеупомянутые заголовки работают так, как предполагалось, с значением Expires, установленным на -1 во всех браузерах.

Обновление 1

Сообщение Как контролировать кэширование веб-страниц во всех браузерах? подробно описывает, что кэширование можно предотвратить во всех браузерах с помощью установки Expires = 0. Я все еще не продаюсь в этом аргументе 0 vs -1...

4b9b3361

Ответ 1

Я думаю, вы должны просто использовать

HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0));

или

HttpContext.Current.Response.Headers.Set ("Cache-Control", "private, max-age=0");

установить max-age=0, что больше не значит, что повторная проверка кеша (см. здесь). Если вы еще добавите ETag в заголовке с некоторой пользовательской контрольной суммой хэша из данных, ETag из предыдущего запроса будет отправлен на сервер. Сервер может либо возвращать данные, либо, в случае, если данные точно такие же, как и раньше, он может возвращать пустые тела и HttpStatusCode.NotModified в качестве кода состояния. В случае, если веб-браузер получит данные из локального кеша браузера.

Я рекомендую использовать Cache-Control: private, которые заставляют две важные вещи: 1) отключить кеширование данных на прокси, которые иногда имеют очень агрессивные настройки кеширования; 2) это позволит кэшировать данные, но не разрешать совместное использование кеша с другими пользователями. Он может решить проблемы конфиденциальности, поскольку данные, которые вы возвращаете одному пользователю, не могут быть прочитаны другими пользователями. Кстати, код HttpContext.Current.Response.Cache.SetMaxAge (new TimeSpan (0)) по умолчанию устанавливает Cache-Control: private, max-age=0 в заголовке HTTP. Если вы хотите использовать Cache-Control: public, вы можете использовать SetCacheability (HttpCacheability.Public); для перезаписывания поведения или использования Headers.Set вместо Cache.SetMaxAge.

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

ОБНОВЛЕНО. Я решил написать дополнительную информацию, чтобы очистить свою позицию. Соответствует информации из Википедии, даже такие старые веб-браузеры, как Mosaic 2.7, Netscape 2.0 и Internet Explorer 3.0, поддерживают в марте 1996 года предварительный стандарт HTTP/1.1, описанный в RFC 2068. Поэтому я предполагаю (но не проверять его), что старые веб-браузеры поддерживают max-age=0 HTTP-заголовок. В любом случае Netscape 2.06 и Internet Explorer 4.0 окончательно поддерживают HTTP 1.1.

Итак, вы должны сначала спросить: какие стандарты HTML вы используете? Вы все еще используете HTML 2.0 вместо более позднего HTML 3.2, опубликованного в январе 1997 года? Я полагаю, вы используете хотя бы HTML 4.0, опубликованный в декабре 1997 года. Поэтому, если вы создадите приложение, по крайней мере, в HTML 4.0, ваш сайт может быть ориентирован на веб-клиентов, поддерживающих HTTP 1.1, и игнорировать (не поддерживать) веб-клиентов, которые не поддерживают HTTP 1.1.

Теперь о других заголовках "Cache-Control" как "private, max-age = 0". Включение заголовков - это, на мой взгляд, чистая паранойя. Поскольку у меня есть проблема с кешированием, я попытался также включить другие заголовки, но позже, внимательно прочитав раздел 14.9 RFC2616, я использую только "Cache-Control: private, max-age = 0".

Единственный заголовок "Cache-Control" , который может быть дополнительно обсужден, "должен-revalidate", описанный в разделе 14.9.4, на который я ссылался ранее. Вот цитата:

Необходимая директива revalidate необходима для поддержки надежного для определенных функций протокола. При всех обстоятельствах Кэш HTTP/1.1 ДОЛЖЕН подчиняться директиве must-revalidate; в частности, если по какой-либо причине кеш не может дойти до исходного сервера, он ДОЛЖЕН генерируйте ответ 504 (время ожидания шлюза).

Серверы ДОЛЖНЫ отправлять директиву must-revalidate тогда и только тогда, когда невозможность повторной проверки запроса на объект может привести к неправильная операция, такая как беспроблемная финансовая сделка. Получатели не должны предпринимать никаких автоматических действий, которые нарушает эту директиву и НЕ ДОЛЖЕН автоматически предоставлять неутвержденная копия объекта, если проверка не выполняется.

Хотя это не рекомендуется, пользовательские агенты, работающие под строгой связью ограничения МОГУТ нарушать эту директиву, но если это так, ДОЛЖНО предупредите пользователя о том, что был предоставлен неутвержденный ответ. предупреждение ДОЛЖНО быть предоставлено при каждом неутвержденном доступе, и СЛЕДУЕТ требуют явного подтверждения пользователя.

Когда-нибудь, если у меня возникла проблема с подключением к Интернету, я вижу пустую страницу с сообщением "Время ожидания шлюза". Он исходит из использования директивы "must-revalidate". Я не думаю, что сообщение "Gateway Timeout" действительно помогает пользователю.

Таким образом, люди, как предпочитают начинать саморазрушительную процедуру, если он слышит сигнал "Занятый" при вызове своему боссу, должны дополнительно использовать директиву "must-revalidate" в заголовке "Cache-Control" . Другие люди, которых я рекомендую, просто используют "Cache-Control: private, max-age = 0" и не более того.

Ответ 3

Для IE я помню, что нужно установить Expires: -1. Как предотвратить кеширование в Internet Explorer, похоже, подтверждает это с помощью следующего фрагмента кода.

<% Response.CacheControl = "no-cache" %>
<% Response.AddHeader "Pragma", "no-cache" %>
<% Response.Expires = -1 %>

Оглядываясь назад в коде, это то, что я нашел. Кроме того, я смутно помню, что если вы установили Cache-Control: private, возможно, он не корректно ведет себя с SSL.

Response.AddHeader("Cache-Control", "no-cache");
Response.AddHeader("Expires", "-1");

Кроме того, Итак, вы не хотите кэшировать, а? упоминает -1, но вместо этого использует методы Response.Cache:

// Stop Caching in IE
Response.Cache.SetCacheability(System.Web.HttpCacheability.NoCache);
// Stop Caching in Firefox
Response.Cache.SetNoStore();

Однако Проблема кэширования ASP-страницы (IE8) говорит, что этот код не работает.