Статьи

Как решить загадки кеширования

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

Установите заголовки

заголовки

Кэширование контролируется кодом состояния HTTP и заголовками Last-Modified , Etag и Cache-Control возвращаемыми при каждом запросе. Для последующих запросов на тот же URL, браузер / прокси будет:

  1. получить предыдущие данные из собственного кэша
  2. попросите сервер проверить, изменились ли данные, или
  3. сделать свежий запрос.

Заголовок Cache-Control первую очередь определяет это действие. Можно установить до трех значений, разделенных запятыми:

Нет магазина или нет кэша
no-store останавливает браузер и все прокси-серверы кэшируют возвращенные данные. Поэтому каждый запрос влечет за собой возвращение на сервер.

Альтернатива — no-cache . Браузер / прокси-сервер отправит запрос на сервер и вернет Last-Modified (дата / время) и / или Etag (хэш / контрольная сумма ответа) в заголовке. Они присутствуют в последующих запросах, и, если ответ не изменился, сервер возвращает статус 304 Not Modified , который инструктирует браузер / прокси-сервер использовать свои собственные кэшированные данные. В противном случае новые данные передаются обратно со статусом 200 OK .

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

private ответы предназначены для одного пользователя. Например, URL-адрес https://myapp.com/messages возвращает набор сообщений, уникальных для каждого вошедшего в систему пользователя, даже если оба они используют один и тот же URL-адрес. Поэтому браузер может кэшировать ответ, но кэширование прокси-сервера не разрешено.

максимальный возраст
Это указывает максимальное время в секундах, в течение которого ответ остается действительным. Например, max-age=60 указывает, что браузер / прокси-сервер может кэшировать данные в течение одной минуты, прежде чем сделать новый запрос.

Ваш сервер, язык и структура часто управляют этими настройками, поэтому вам редко приходится возиться — но вы можете. Предположим, вы хотите кэшировать JSON-ответ отдельного пользователя на Ajax-запрос в течение 30 секунд. В PHP:

 header('Cache-Control: private,max-age=30'); echo json_encode($data); 

или маршрутизатор Node.js / Express:

 res .set('Cache-Control', 'private,max-age=30') .json(data); 

Различение страниц данных и URL-адресов данных Ajax

Установка заголовков HTTP может быть недостаточно, потому что браузеры работают немного по-другому, когда вы нажимаете кнопку «Назад».

В Firefox и Safari при #hash кнопки назад попытка отобразить предыдущую страницу в ее последнем известном состоянии — при условии, что URL был изменен с обновленным #hash , или путем перехвата действий с событиями API истории .

В Chrome и Edge при нажатии на предыдущую страницу предыдущая страница отображается в ее начальном состоянии — хотя ваш JavaScript будет инициализирован и, возможно, при необходимости изменит DOM.

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

  1. Пользователь заходит на страницу по http://myapp.com/list/
  2. Отправка формы для изменения фильтров или перехода на новую страницу изменит URL-адрес и создаст новый запрос — например, http://myapp.com/list/?search=bob&page=42 . Система работает в любом браузере с или без JavaScript.
  3. Мы представили улучшения JavaScript, поэтому полное обновление страницы не требуется. Код перехватывает отправку формы и историю задних / следующих событий, поэтому, пока URL-адрес все еще изменяется, приложение выполняет Ajax-запрос в фоновом режиме.
  4. Запрос Ajax вызывает тот же URL-адрес, например http://myapp.com/list/?search=bob&page=42 но устанавливает для заголовка HTTP X-Requested-With значение XMLHttpRequest (выполняется jQuery и всеми хорошими библиотеками Ajax). Сервер распознает этот заголовок — поэтому вместо возврата HTML-кода полной страницы он возвращает данные записей в кодировке JSON. Наш JavaScript использует это для обновления DOM.

Таким образом, наш сервер может возвращать HTML или JSON для одного и того же URL-адреса в зависимости от состояния заголовка X-Requested-With запроса. К сожалению, это может вызвать проблемы с Chrome и Edge, потому что HTML или JSON могут быть кэшированы.

Предположим, что вы случайным образом перемещаетесь по списку записей, и по адресу http://myapp.com/list/?search=bob&page=42 вы нажимаете ссылку на другую (не список) страницу, а затем нажимаете кнопку возврата в браузере, чтобы вернуться. Chrome просматривает свой кэш, видит данные JSON для этого URL и представляет их пользователю! Нажатие на обновление решит проблему, потому что запрос будет сделан без заголовка X-Requested-With . Что еще более странно, так это то, что Firefox работает должным образом и восстанавливает текущее состояние страницы.

Исправление: убедитесь, что ваша страница и URL-адреса данных никогда не совпадают. При переходе по http://myapp.com/list/?search=bob&page=42 вызов Ajax должен использовать другой URL-адрес: он может быть простым, например http://myapp.com/list/?search=bob&page=42&ajax=1 . Это гарантирует, что Chrome может кэшировать запросы HTML и JSON по отдельности, но JSON никогда не отображается, потому что URL-адрес Ajax никогда не появляется в адресной строке браузера.

(Примечание: пожалуйста, не используйте этот пример в качестве оправдания для избежания постепенного улучшения ! Это может повлиять на JavaScript-зависимое одностраничное приложение — особенно на те, которые предоставляют ссылки на внешние URL-адреса.)

К сожалению, есть еще одно осложнение …

Остерегайтесь самоподписанных сертификатов SSL

безопасный

В идеале ваше приложение использует зашифрованный протокол HTTPS. Однако нет необходимости приобретать SSL-сертификаты для всех 57 членов вашей команды, поскольку вы можете использовать поддельный самозаверяющий сертификат и нажимать кнопку « Продолжить» всякий раз, когда браузер жалуется.

Имейте в виду, что Chrome (и, вероятно, большинство браузеров на основе Blink) отказывается кэшировать данные страницы при обнаружении поддельного сертификата. Это похоже на установку Cache-Control на no-store при каждом запросе.

Ваши тестовые сайты будут работать точно так, как ожидается, и вы никогда не столкнетесь с такими же проблемами URL страниц / данных, описанными выше. Кеш никогда не используется, и все запросы возвращаются на сервер. То же приложение на реальном сервере с настоящим сертификатом SSL будет кешировать данные. Ваши пользователи могут сообщать о странных ответах JSON от Chrome, которые вы не сможете воспроизвести локально .

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