Как решать головоломки с кэшированием
Оригинал статьи: How to Solve Caching Conundrums
Оглавление:- Позаботьтесь о заголовках
- Разделяйте URL страницы и данных Ajax
- Избегайте самоподписанных SSL-сертификатов
Веб не может работать без кэширования. Между вами и сервером есть браузер и некоторое количество прокси-серверов, кэширующих ответы. Большинство из них незаметно обрабатывается приложениями, что значительно снижает интернет-трафик. Однако, кэширование также может вызвать странности в поведении веб-приложений, если вы будете неосторожны.
Позаботьтесь о заголовках
Кэширование контролируется кодом статуса HTTP и заголовками Last-Modified
, Etag
и Cache-Control
, возвращаемыми на каждый запрос. При последовательных запросах одного URL, браузер или прокси будут делать следующее:
- Извлекать предыдущие данные из собственного кэша;
- Спрашивать у сервера, изменялись ли данные;
- Делать новый запрос.
Все это делается прежде всего заголовком Cache-Control
. Он передает три значения, разделенных запятыми:
- no-store или no-cache
-
no-store
прекращает кэширование возвращаемых данных браузером и всеми прокси. Каждый запрос будет направляться непосредственно на сервер.
Альтернативой является no-cache
. Браузер/прокси делает запрос к серверу и возвращает Last-Modified
(дату и время) и/или Etag
(хэш/контрольная сумма) в заголовке. Они присутствуют в последующих запросах и, если ответ не изменился, сервер возвращает статус 304 Not Modified
, что заставляет браузер/прокси использовать кэшированные данные. Иначе передаются новые данные со статусом 200 OK
.
- public или private
-
Установка
Cache-Control
вpublic
означает, что ответ сервера будет одинаковым для всех и данные будут кэшироваться браузером или прокси. Это дефолтное поведение и его не нужно дополнительно устанавливать.
Ответы private
ориентированы на отдельных пользователей. Например, адрес https://myapp.com/messages
возвращает набор сообщений, уникальных для каждого залогиненного пользователя, несмотря на использование одного URL. Это значит, что кэширование будет производится только в браузере, кэширование прокси запрещено.
- max-age
-
Определяет время в секундах, в течение которого ответ будет оставаться действительным. Например, ответ
max-age=60
означает, что браузер/прокси могут кэшировать данные на одну минуту перед тем, как делать новый запрос.
Ваш сервер, язык программирования или фреймворк, как правило, могут контролировать эти настройки и у вас нет необходимости с этим возиться — но вы можете. Предположим, вы хотите на 30 секунда кэшировать индивидуальный ответ пользователю в JSON при Ajax запросе, в 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 клавиша “назад” попытается вернуть вас на предыдущую страницу в ее последнем состоянии, подразумевая, что URL поменялся и был обновлен с #hash
или за счет перехвата действий с событиями History API.
В Chrome и Edge, клавиша “назад” возвращает вас на предыдущую страницу в ее изначальном состоянии - хотя для этого JavaScript будет инициализировать страницу и изменять при необходимости DOM.
На практике редко имеет значение, какой браузер вы используете, но некоторые странности случаются. Представьте, что в вашем приложении есть разбитая на страницы таблица с записями, в которой пользователь может производить поиск и перемещаться с помощью навигационных клавиш. Хорошие разработчики используют метод прогрессивного улучшения, чтобы обеспечить работу приложения во всех браузерах.
- Пользователь переходит по адресу:
http://myapp.com/list/
. - Отправка формы, изменяющей поисковый фильтр или переход на новую страницу, меняет URL и создает новый запрос, например,
http://myapp.com/list/?search=bob&page=42
. Приложение работает во всех браузерах, независимо от JavaScript. - Добавляются улучшения с JavaScript, после чего нам уже не нужно обновлять всю страницу. Код перехватывает отправку формы и события истории вперед-назад и пока URL меняется, приложение в фоновом режиме делает Ajax запросы.
- Запрос Ajax вызывает тот же URL
http://myapp.com/list/?search=bob&page=42
, но с HTTP-заголовком XMLHttpRequest (это делает любая хорошая библиотека Ajax, включая jQuery). Сервер распознает этот заголовок — и вместо возвращения полной страницы HTML, возвращает нужные данные в кодировке JSON. DOM обновляется с помощью JavaScript.
В итоге наш сервер может вернуть на выбор HTML или JSON для того же URL в зависимости от состояния заголовка X-Requested-With
. К сожалению, это может повлечь проблемы в Chrome и Edge, так как закэшированы могут быть и JSON, и HTML.
Предположим, вы после навигации по списку оказались на странице 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 никогда не будет выводится, так как Ajax UR никогда не появляется в адресной строке браузера.
Примечание: пожалуйста, не используйте этот пример в качестве оправдания для отказа от прогрессивного улучшения. Это может затронуть одностраничные приложения на JavaScript — особенно предоставляющие ссылки на сторонние URL.
К сожалению, этим список проблем не исчерпывается…
Избегайте самоподписанных SSL-сертификатов
В идеале, ваше приложение использует зашифрованный протокол HTTPS. Однако при разработке нет необходимости покупать SSL-сертификаты для всех 57 членов команды, потому что у вас есть возможность использовать фиктивный, самоподписанный сертификат и нажимать “продолжить” всякий раз, когда браузер сообщает об этом.
Остерегайтесь этого, так как Chrome (и все браузеры на движке Blink) отказываются кэшировать данные страницы, когда сталкиваются с фиктивными сертификатами. Такое поведение аналогично наличию заголовка Cache-Control
со значением no-store
в каждом запросе.
Ваш тестовый сайт будет работать так как ожидается и вы никогда не столкнетесь с проблемой одинакового URL для страницы и данных, описанной в предыдущей части статьи. Кэш просто не используется и все запросы идут на сервер. Там приложение с настоящим SSL-сертификатом будет кэшировать данные. И ваши пользователи будут сообщать о странных JSON-ответах в Chrome, которые вы не сможете воспроизвести на локальной машине.
Да, это один из тех кошмаров, которые продолжают преследовать веб-разработку. И я надеюсь, что мой обзор поможет вам этого избежать.