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

Позаботьтесь о заголовках

Заголовки кэширования

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

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

Все это делается прежде всего заголовком 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.

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

  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-заголовком 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, которые вы не сможете воспроизвести на локальной машине.

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