====== Основы клиентского кэширования ====== Источник: нагло украл статью и ссылку не поставлю ..!.. Кэш играет важную роль в работе практически любого веб-приложения на уровне работы с базами данных, веб-серверами, а также на клиенте. В рамках этой статьи мы попытаемся разобраться с клиентским кэшированием. В частности, разберемся с тем, какие http-заголовки используются браузерами и веб-серверами и что они значат. Но для начала давайте выясним, **зачем вообще нужно кэширование на стороне клиента?**. Веб-страницы состоят из множества различных элементов: картинок, css и js файлов и т.п. Часть этих элементов используются на нескольких (многих) страницах сайта. Под клиентским кэшированием понимают способность браузеров сохранять копии файлов (ответов сервера), чтобы не загружать их повторно. Это позволяет значительно ускорить повторную загрузку страниц, сэкономить на трафике, а также снизить нагрузку на сервер. Существует несколько различных HTTP-заголовков для того, чтобы управлять процессами кэширования на стороне клииента. Давайте поговорим о каждом из них. ===== Http заголовки для управления клиентским кэшированием ===== Для начала давайте посмотрим, как сервер и браузер взаимодействуют при отсутствии какого-либо кэширования. Для наглядного понимания я попытался представить и визуализировать процесс общения между ними в виде текстового чата. Представьте на несколько минут, что сервер и браузер – это люди, которые переписываются друг с другом :) ==== Без кэша (при отсутствии кэширующих http-заголовков) ==== {{:web:web_cache_1.png?nolink&700|}} Как мы видим, каждый раз при отображении картинки cat.png браузер будет снова загружать ее с сервера. Думаю, не нужно объяснять, что это медленно и неэффективно. ==== Заголовок ответа Last-modified и заголовок запроса if-Modified-Since ==== Идея заключается в том, что сервер добавляет заголовок ''Last-modified'' к файлу (ответу), который он отдает браузеру. Last-modified: Fri, 1 Dec 2014 01:01:01 GMT Теперь браузер знает, что файл был создан (или изменен) 1 декабря 2014. В следующий раз, когда браузеру понадобится тот же файл, он отправит запрос с заголовком ''if-Modified-Since''. if-Modified-Since: Fri, 1 Dec 2014 01:01:01 GMT Если файл не изменялся, сервер отправляет браузеру пустой ответ со статусом ''304 (Not Modified)''. В этом случае, браузер знает, что файл не обновлялся и может отобразить копию, которую он сохранил в прошлый раз. {{:web:web_cache_2.png?nolink&700|}} Таким образом, используя ''Last-modified'' мы экономим на загрузке большого файла, отделываясь пустым быстрым ответом от сервера. ==== Заголовок ответа Etag и заголовок запроса If-None-Match ==== Принцип работы ''Etag'' очень схож с ''Last-modified'', но, в отличии от него, не привязан ко времени. Время – вещь относительная. Идея заключается в том, что при создании и каждом изменении сервер помечает файл особой меткой, называемой ''ETag'', а также добавляет заголовок к файлу (ответу), который он отдает браузеру: ETag: "686897696a7c876b7e" Теперь браузер знает, что файл актуальной версии имеет ''ETag'' равный "686897696a7c876b7e". В следующий раз, когда брузеру понадобится тот же файл, он отправит запрос с заголовком ''If-None-Match: "686897696a7c876b7e"''. If-None-Match: "686897696a7c876b7e" Сервер может сравнить метки и, в случае, если файл не изменялся, отправить браузеру пустой ответ со статусом ''304 (Not Modified)''. Как и в случае с ''Last-modified'' браузер выяснит, что файл не обновлялся и сможет отобразить копию из кэша. {{:web:web_cache_3.png?nolink&700|}} Подробнее о ''ETag'' можно почитать [[https://ru.wikipedia.org/wiki/HTTP_ETag|здесь]]. ==== Заголовок Expired ==== Принцип работы этого заголовка отличается от вышеописанных ''Etag'' и ''Last-modified''. При помощи ''Expired'' определяется "срок годности" ("срок акуальности") файла. Т.е. при первой загрузке сервер дает браузеру знать, что он не планирует изменять файл до наступления даты, указанной в ''Expired'': Expired: Fri, 1 Mar 2014 01:01:01 GMT В следующий раз браузер, зная, что "дата истечения срока годности" еще не наступила, даже не будет пытаться делать запрос к серверу и отобразит файл из кэша. {{:web:web_cache_4.png?nolink&700|}} Такой вид кэша особенно актуален для иллюстраций к статьям, иконкам, фавиконкам, некоторых css и js файлов и тп. ==== Заголовок Cache-control с директивой max-age. ==== Принцип работы ''Cache-control: max-age'' очень схож с ''Expired''. Здесь тоже определяется "срок годности" файла, но он задается в секундах и не привязан к конкретному времени, что намного удобнее в большинстве случаев. {{:web:web_cache_5.png?nolink&700|}} Для справки: * 1 день = 86400 секунд * 1 неделя = 604800 секунд * 1 месяц = 2629000 секунд * 1 год = 31536000 секунд К примеру: Cache-Control: max-age=2629000; У заголовка ''Cache-control'', кроме ''max-age'', есть и другие директивы. Давайте коротко рассмотрим наиболее популярные: **public**\\ Дело в том, что кэшировать запросы может не только конечный клиент пользователя (браузер), но и различные промежуточные прокси, CDN-сети и тп. Так вот, директива ''public'' позволяет абсолютно любым прокси-серверам осуществлять кэширование наравне с браузером. **private**\\ Директива говорит о том, что данный файл (ответ сервера) является специфическим для конечного пользователя и не должен кэшироваться различными промежуточными прокси. При этом она разрешает кэширование конечному клиенту (браузеру пользователя). К примеру, это актуально для внутренних страниц профиля пользователя, запросов внутри сессии и т.п. **no-cache**\\ Позволяет указать, что клиент должен делать запрос на сервер каждый раз. Иногда используется с заголовком ''Etag'', описанным выше. **no-store**\\ Указывает клиенту, что он не должен сохранять копию запроса или частей запроса при любых условиях. Это самый строгий заголовок, отменяющий любые кэши. Он был придуман специально для работы с конфиденциальной информацией. **must-revalidate**\\ Эта директива предписывает браузеру делать обязательный запрос на сервер для ре-валидации контента (например, если вы используете eTag). Дело в том, что http в определенной конфигурации позволяет кэшу хранить контент, который уже устарел. ''must-revalidate'' обязывает браузер при любых условиях делать проверку свежести контента путем запроса к серверу. **proxy-revalidate**\\ Это то же, что и ''must-revalidate'', но касается только кэширующих прокси серверов. **s-maxage**\\ Практически не отличается от ''mах-age'', за исключением того, что эта директива учитывается только кэшем резличных прокси, но не самим браузером пользователя. Буква "**s**-" исходит из слова "**s**hared" (например, CDN). Эта директива предназначена специально для CDN-ов и других посреднических кэшей. Ее указание отменяет значения директивы ''max-age'' и заголовка ''Expired''. Впрочем, если вы не строите CDN-сети, то ''s-maxage'' вам вряд ли когда-либо понадобится. ===== Как посмотреть, какие заголовки используются на сайте? ===== Вы можете посмотреть заголовки http-запросов (request headers) и ответов (response headers) в отладчике Вашего любимого браузера. Вот например, как это выглядит в хроме: {{:web:web_cache_6.png?nolink&700|}} То-же самое можно увидеть в любом уважающем себя браузере или http-сниффере. ===== Настройка кэшировения в Аpache и Nginx ===== Мы не будем пересказывать документацию по настройке популярных серверов. Вы всегда можете посмотреть ее [[http://nginx.org/en/docs/|здесь для Nginx]] и [[http://httpd.apache.org/docs/|здесь для Apache]]. Ниже мы приведем несколько примеров из жизни для того, чтобы показать, как выглядят файлы конфигурации. ==== Пример конфигурации Apache для контроля Expires ==== Выставляем различный "срок годности" для различных типов файлов. Один год для изображений, один месяц для скриптов, стилей, pdf и иконок. Для всего остального – 2 дня. ExpiresActive On ExpiresByType image/jpg "access plus 1 year" ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/gif "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType text/css "access plus 1 month" ExpiresByType application/pdf "access plus 1 month" ExpiresByType text/x-javascript "access plus 1 month" ExpiresByType image/x-icon "access plus 1 year" ExpiresDefault "access plus 2 days" ==== Пример конфигурации Nginx для контроля Expires ==== Выставляем различный "срок годности" для различных типов файлов. Одна неделя – для изображений, один день – для стилей и скриптов. server { #... location ~* \.(gif|ico|jpe?g|png)(\?[0-9]+)?$ { expires 1w; } location ~* \.(css|js)$ { expires 1d; } #... } ==== Пример конфигурации Apache для Cache-control (max-age и public/private/no-cache) ==== Header set Cache-Control "max-age=2592000, public" Header set Cache-Control "max-age=88000, private, must-revalidate" Header set Cache-Control "private, no-store, no-cache, must-revalidate, no-transform, max-age=0" Header set Pragma "no-cache" ==== Пример конфигурации Nginx для Cache-control статических файлов ==== server { #... location ~* \.(?:ico|css|js|gif|jpe?g|png)$ { add_header Cache-Control "max-age=88000, public"; } #... } ===== В заключение ===== **"Кэшировать все то, что можно кэшировать"** – хороший девиз для веб-разработчика. Иногда можно потратить всего несколько часов на конфигурацию и при этом значительно улучшить восприятие вашего сайта пользователем, значительно сократить нагрузку на сервер и сэкономить на трафике. Главное – не переусердствовать и настроить все правильно с учетом особенностей Вашего ресурса.