За даними наведеного опитування лише 4% стверджують, що добре розуміються на кешуванні та заголовку Cache-Control
, 54% взагалі не розуміють, що відбувається.
Більшість розробників втрачають корисні можливості через недостачу чи навіть відсутність знань про кешування. Можливо це пов'язано з надмірною увагою до перших відвідувань сайту або просто відсутністю потрібних навичок. Що б то не було, трохи освіжімо пам'ять.
Cache-Control
Найбільш поширений та ефективний спосіб управляти кешуванням
ваших ресурсів – HTTP-заголовок Cache-Control
. Заголовок застосовується до окремих ресурсів, тобто кешування буде вибірковим. З таким обсягом контролю можна реалізувати складні та потужні стратегії кешування.
Заголовок Cache-Control
може виглядати наступним чином:
Cache-Control: public, max-age=31536000
public
та max-age=31536000
називаються директивами. Заголовок Cache-Control
може приймати одну чи більше директив. Детальніше про директиви, їх значення та приклади застосування поговоримо у статті.
public
та private
Директива public
вказує, що будь-який кеш може зберігати копію відповіді. Це можуть бути CDN, проксі-сервери тощо. Часто немає потреби вказувати public
, тому що наявність таких директив як max-age
є неявною вказівкою, що кеш може зберігати копію.
З іншого боку, private
явно вказує, що лише кінцевий одержувач відповіді (клієнт або браузер) може зберігати копію файлу. Хоч директива private
сама по собі не відноситься до фіч безпеки, вона запобігає зберіганню відповіді, яка містить унікальні для користувача дані, в загальнодоступних кешах (наприклад, CDN).
max-age
max-age
визначає проміжок часу у секундах (відносно часу запиту), протягом якого відповідь вважається «свіжою».
Cache-Control: max-age=60
Ця директива Cache-Control
вказує браузеру, що він може використовувати файл з кешу наступні 60 секунд і не турбуватися про повторну перевірку. Коли 60 секунд збігають, браузер повторно звернеться до сервера, щоб підтвердити актуальність файлу.
Якщо на сервері є новий файл, повернеться відповідь зі статусом 200
, а браузер завантажить його. Старий файл буде витягнуто з HTTP-кешу, а новий замінить його та буде використовувати його заголовки для кешування.
Якщо на сервері немає більш свіжої копії для завантаження, повернеться відповідь зі статусом 304
, потреби завантажувати новий файл не буде, а для кешованої копії буде оновлено заголовки. Тобто до файлу із заголовком Cache-Control: max-age=60
відлік часу почнеться спочатку. Маємо 120 секунд загального часу кешування для одного файлу.
Будьте обережні: при використанні max-age
варто звернути увагу на один важливий нюанс. max-age
вказує браузеру, що даний ресурс є застарілим, але не вказує, що браузер абсолютно не може використовувати застарілу копію. У браузера можуть бути власні алгоритми, за якими він вирішує чи використовувати застарілу копію файлу без повторної валідації. Така поведінка не визначена, тому досить важко точно передбачити як вчинить браузер. Для цього існує перелік більш чітких директив, що можуть доповнити max-age
.
s-maxage
Директива s-maxage
матиме перевагу над max-age
, але лише у контексті загальних кешей. Використовуючи max-age
та s-maxage
разом, ви отримаєте різні проміжки часу «свіжості» файлів для приватних та публічних кешів (тобто проксі, CDN).
no-store
Cache-Control: no-store
Якщо ж ми не хочемо кешувати файл? Що якщо файл містить конфіденційну інформацію? Наприклад, HTML-сторінка з банківськими реквізитами. Або ж актуальність інформації дуже залежить від часу. Наприклад, сторінка з курсами акцій. Ми зовсім не хочемо зберігати або обробляти такі відповіді з кешу: ми уникаємо конфіденційну інформацію та прагнемо отримувати найсвіжішу інформацію. Для таких випадків використовуємо no-store
.
no-store
— сувора директива, що не дозволяє зберігати інформацію у будь-якому кеші, приватному чи будь-якому іншому. Будь який ресурс з директивою no-store
попаде у мережу, не зважаючи ні на що.
no-cache
Cache-Control: no-cache
no-cache
, насправді, не означає «не кешувати», що може збити з пантелику. Директива означає «не опрацьовувати копію з кешу, поки ви не підтвердите її на сервері, який дозволить використання копії». Мабуть, назва must-revalidate
пасувала б цій директиві більше.
no-cache
— досить розумний спосіб завжди гарантувати найсвіжіший контент з можливістю використовувати кешовану копію. no-cache
завжди звертатиметься до мережі, тому що перед випуском кешованої копії (якщо немає свіжішого екземпляру), її необхідно повторно перевірити на сервері. Якщо ж відповідь сервера сприятлива, мережею передаються лише заголовки файлу: тіло можна взяти з кешу, щоб не перезавантажувати.
Отже, маємо вдалий спосіб поєднати актуальність ресурсу та можливість отримати його з кешу, але з таким підходом необхідно буде звертатися до мережі як мінімум для отримання HTTP-заголовків.
Використання no-cache
буде доречним для майже будь-якої динамічної HTML-сторінки. Пригадайте головну сторінку сайту з новинами: немає потреби у режимі реального часу, немає конфіденційної інформації, але в ідеалі ми хотіли б, щоб сторінка завжди показувала найсвіжіший контент. Можна використати cache-control: no-cache
, щоб вказати браузеру спочатку перевірити ресурс на сервері, і якщо «свіжішої» версії немає (статус 304
), використати кешовану версію. Якщо ж сервер запропонує свіжіший контент, він надішле відповідь зі статусом 200
та новий файл.
Порада: Немає сенсу надсилати директиву max-age
разом з no-cache
, тому що ліміт часу для повторної валідації становитиме 0 секунд.
must-revalidate
Ви ще більше заплутаєтесь, коли дізнаєтесь,що директива must-revalidate
таки існує і має інший, хоч дуже схожий сенс.
Cache-Control: must-revalidate, max-age=600
must-revalidate
необхідно використовувати з max-age
(вище ми встановили значення останньої як 10 хвилин).
no-cache
одразу повторно перевірить копію на сервері, і використає кешований варіант лише з дозволу сервера. must-revalidate
схожа на no-cache
з відстроченням. Повернемося до нашого прикладу: перші 10 хвилин браузер не буде повторно перевіряти ресурс на сервері. По завершенню вказаного періоду він звернеться до сервера, і якщо для нас нічого нового немає, повернеться відповідь зі статусом 304
, а для кешованого файлу будуть застосовані нові заголовки Cache-Control
. Наші 10 хвилин починаються спочатку: якщо по їх завершенню на сервері з'явилась новіша версія файлу, отримаємо відповідь зі статусом 200
та тілом, а локальний кеш оновиться.
must-revalidate
стане у пригоді для статичних сторінок, що рідко змінюються. Звичайно, бажано мати актуальний контент, але враховуючи частоту змін на сторінках, немає потреби такого контролю, як з no-cache
. Натомість, припустимо, що протягом цих 10 хвилин нам не потрібно буде робити новий запит.
proxy-revalidate
Подібно до s-maxage
, proxy-revalidate
– версія must-revalidate
для публічних кешів. Вона просто ігнорується приватними кешами.
immutable
immutable
— досить нова директива, що дає браузеру трохи більше інформації про тип вмісту надісланого файлу: змінний він чи ні. Перш ніж поговоримо про immutable
, визначимо які проблеми вирішує директива.
Коли користувач перезавантажує сторінку, браузер повторно перевіряє «свіжість» файлу, тому що перезавантаження свідчить про одну з двох причин:
- Щось пішло не так на сторінці;
- Її вміст виглядає застарілим.
Якщо на сервері з'явився новіший файл, ми звичайно хочемо завантажити його. Таким чином, ми отримаємо відповідь 200
, свіжий файл — і проблему розв'язано. Якщо ж на сервері не виявилось нового файлу, він поверне відповідь зі статусом 304
без нового файлу. Якщо ми будемо повторно перевіряти файл і сервер часто відправлятиме нам 304
, усе закінчиться непотрібними витратами на очікування.
immutable
— спосіб вказати браузеру, що файл ніколи не зміниться, тому не треба турбуватися про його повторну перевірку. Ми можемо повністю уникнути витрат, пов'язаних з очікуванням відповіді.
Що ми маємо на увазі під змінним або незмінним файлом?
style.css
: Коли ми змінюємо вміст файлу, ми не чіпаємо його назву. Файл завжди існує, а його вміcт завжди змінюється. Ось приклад змінюваного файлу.style.ae3f66.css
: Такий файл унікальний, тому що в його назві є відбиток браузера, що залежить від вмісту файлу. Якщо його вміст змінюється, ми отримуємо абсолютно новий файл. Ось приклад незмінюваного файлу.
Якщо ми можемо якось повідомити браузеру, що наш файл незмінюваний, тоді ми також можемо повідомити, що браузеру не потрібно турбуватися про перевірку більш свіжої версії: ніколи не буде свіжішої версії, оскільки файл просто перестає існувати в момент зміни його вмісту.
Тут на допомогу приходить директива immutable
:
Cache-Control: max-age=31536000, immutable
У браузерах, що підтримують immutable
, перезавантаження не призведе до повторної валідації файлу у межах періоду «свіжості». Тобто немає потреби в очікуванні 304
-ої відповіді від сервера, що потенційно економить нам багато затримок на важливому етапі (CSS блокує рендеринг). При з'єднаннях з високою затримкою така економія може бути відчутною.
Зауважте: Не слід застосовувати immutable
для файлів, що є змінюваними. Вам також слід мати надійну стратегію кешування, щоб випадково не кешувати файл з директивою immutable
.
stale-while-revalidate
Про повторну валідацію вже було сказано багато: браузер здійснює запит до сервера, щоб перевірити наявність свіжішої копії файлу. При з'єднаннях з високою затримкою, тривалість такої перевірки може бути відчутною. Тобто ми марно втрачаємо час, поки не отримаємо від сервера дозвіл на використання кешованої копії (304
) або завантажимо новий файл (200
).
stale-while-revalidate
визначає додатковий період, під час якого браузер може використовувати застарілий ресурс, поки ми перевіряємо наявність свіжішої версії.
Cache-Control: max-age=31536000, stale-while-revalidate=86400
Тут ми кажемо браузеру: «цей файл актуальний протягом року, але по закінченню цього терміну, у тебе є додатковий тиждень, щоб обслуговувати застарілий ресурс, поки ти перевіряєш наявність нової версії у фоновому режимі».
stale-while-revalidate
— чудовий вибір для некритично важливих ресурсів, коли ми хочемо мати найсвіжішу версію, але знаємо, що не буде ніякої шкоди, якщо користуватимемось застарілою версію, поки чекаємо оновлення.
stale-if-error
Подібно до stale-while-revalidate
, stale-if-error
дає браузеру додатковий період, протягом якого він може використовувати застарілий ресурс, якщо його повторна перевірка, завершується помилками 5xx
.
Cache-Control: max-age=2419200, stale-if-error=86400
Тут ми вказуємо кешу, що файл актуальний протягом 28 днів, і якщо після цього часу ми зіткнемося з помилкою, нам дається додатковий день, протягом якого можемо обслуговувати застарілу версію ресурсу.
Скидання кешу
Було б неправильно не торкнутися у цій статті теми скидання кешу. На думку автора, стратегію скидання кешу варто продумати у першу чергу.
Скидання кешу розв'язує такий вид проблеми: «я вказав браузеру використовувати файл протягом наступного року, але я щойно змінив його і не хочу, щоб користувачі чекали рік перед отриманням свіжішої копії. Як можна виправити це?»
Без скидання: style.css
Найгірший підхід: абсолютно не скидати кеш, якщо маємо справу зі змінюваним файлом.
Варто бути обережним з кешуванням будь-який файлів, на зразок style.css
, тому що ми майже повністю втрачаємо контроль над ними, коли вони потрапляють до користувача.
HTML-сторінки також важливо контролювати. Ми не можемо змінити ім'я файлу веб-сторінки (уявіть, який хаос це викличе!), саме тому ми намагаємося не зберігати їх у кеші.
Рядок запиту: style.css?v=1.2.14
Тут у нас також змінюваний файл, але з рядком запиту у шляху. Краще, ніж нічого, але все ще не ідеально. Якщо рядок із запитом буде видалено, ми знову зіткнемося з відсутністю скидання кешу. Багато проксі-серверів та CDN не кешуватимуть будь-що з рядком запиту або через власну конфігурацію (наприклад, з документації Cloudflare: 'запит style.css?something
буде нормалізовано до звичайного style.css
при обробці з кешу') або з метою захисту (рядок запиту може містити інформацію, що стосується конкретної відповіді).
Відбиток браузера: style.ae3f66.css
Перейдемо до найкращого способу скидання кешу для файлу. Змінюючи назву файлу кожного разу, коли модифікується його вміст, ми технічно не скидаємо кеш: ми отримуємо повністю новий файл! Такий підхід надійний та дозволяє використовувати immutable
. Реалізовуйте описаний спосіб для своїх статичних ресурсів, якщо це можливо. Після того, як вам вдалося використати таку стратегію перебору кешу, ви можете застосувати найбільш агресивне кешування:
Cache-Control: max-age=31536000, immutable
Особливість реалізації
Сенс такого підходу у зміні назви файлу, але це не повинен бути відбиток браузера. Всі наступні приклади діють однаково:
/assets/style.ae3f66.css
: скидання з хеш-кодом вмісту файлу;/assets/style.1.2.14.css
: скидання з версією випуску;/assets/1.2.14/style.css
: скидання шляхом заміни директорії у URL.
Однак, в останньому прикладі мається на увазі, що ми керуємо версіями кожного випуску, а не окремими файлами. Тобто якщо нам необхідно було лише скинути кеш для стилів, нам довелося б повторити таку операцію і для усіх статичних файлів випуску. Такий підхід не дуже вигідний, тому віддавайте перевагу першому та другому варіантам.
Clear-Site-Data
Інвалідація кешу — це складно, як вже добре відомо. На даний момент існує специфікація, що допомагає розробникам повністю очистити кеш сайту одним махом: Clear-Site-Data
.
У статті ми не будемо багато зупинятися на Clear-Site-Data
, тому що це повністю інший HTTP-заголовок, який не стосується Cache-Control
.
Clear-Site-Data: "cache"
Застосування заголовка до будь-якого з ресурсів джерела призведе до очищення кешу для всього джерела, а не лише для файлу, до якого він приєднаний. Тобто якщо вам треба повністю очистити ваш сайт від кешів відвідувачів, ви можете використати згаданий заголовок до корисного завантаження вашого HTML.
Підтримка браузерами на момент написання обмежується Chrome, Android Webview, Firefox та Opera.
Порада: існує ряд директив, які приймає Clear-Site-Data
: cookies
, storage
, executionContexts
та *
, що означає «все перераховане вище».
Приклади та поради
Розглянемо які саме заголовки Cache-Control
застосовувати на реальних прикладах.
Сторінка онлайн-банкінгу
Уявімо застосунок для онлайн банкінгу, де перераховані ваші останні транзакції, поточний баланс та можливо конфіденційні дані банківського рахунку, які повинні бути актуальними (уявіть, що ви обслуговуєте сторінку з балансом тижневої давності!), а також конфіденційними (ви не хочете, щоб ваші банківські реквізити зберігалися у будь-якому кеші).
Спробуймо такий варіант:
Request URL: /account/
Cache-Control: no-store
Відповідно до специфікації цього було б достатньо, щоб браузер не зберігав відповідь на диск взагалі, через приватні та загальні кеші:
Директива відповіді no-store вказує, що кеш НЕ ПОВИНЕН зберігати будь-яку частину запиту чи відповіді. Застосовується до приватних і загальних кешей. «НЕ ПОВИНЕН зберігати» у даному контексті означає, що кеш НЕ ПОВИНЕН навмисно зберігати інформацію в енергонезалежному сховищі, а ПОВИНЕН зробити все можливе, щоб видалити інформацію з енергозалежного сховища якомога швидше після її відправлення.
Але якщо ви хочете максимально захиститися, спробуйте такий варіант:
Request URL: /account/
Cache-Control: private, no-cache, no-store
Так ви явно вказуєте не зберігати будь-що у публічних кешах (наприклад, CDN), щоб завжди працювати з найсвіжішою копією та не зберігати її у пам'ять.
Сторінка розкладу потягів
Якщо ми створюємо сторінку, яка відображає інформацію, близьку до реального часу, ми хочемо гарантувати, що користувач завжди бачить найактуальніший контент, який ми можемо надати. Для цього спробуймо такий підхід:
Request URL: /live-updates/
Cache-Control: no-cache
Така проста директива означатиме, що браузер не буде відображати відповідь прямо з кешу без дозволу сервера. Тобто користувачу ніколи не буде відображатися застаріла інформація про потяги, але перевага у тому, що можна використовувати файл з кешу, якщо сервер визначає цей файл актуальним.
Згадане рішення стандартне майже для усіх веб-сторінок: отримуємо останній вміст, але при можливості використовуємо швидкість кешу.
Сторінка FAQ
Сторінки, на зразок FAQ, зазвичай оновлюються рідко і їх вміст не чутливий до часу (на відміну від спортивних результатів у реальному часі або статусів польоту). В такому разі ми можемо кешувати HTML-сторінку на деякий час та періодично перевіряти наявність свіжішого контенту. Реалізуємо таким чином:
Request URL: /faqs/
Cache-Control: max-age=604800, must-revalidate
Тут ми вказали браузеру кешувати HTML-сторінку протягом тижня, та по закінченню вказаного терміну перевірити сервер на наявність оновлень.
Зауважте: Різні стратегії кешування для різних сторінок одного веб-сайту можуть призвести до проблеми, коли ваша домашня сторінка з no-cache
надсилає запит для свіжішої версії файлу style.f4fa2b.css
, на який вона має посилання, а ваша сторінка FAQ з триденним періодом кешування досі посилається на style.ae3f66.css
. Наслідки можуть бути незначними, але ви повинні знати про такий нюанс.
Статичний JS (або CSS) пакет застосунку
Уявімо, що файл app.[fingerprint].js
оновлюється досить часто (потенційно з кожним випуском), але ми також організували створення відбитку кожного разу, коли файл змінюється. В такому разі можемо зробити щось подібне:
Request URL: /static/app.1be87a.js
Cache-Control: max-age=31536000, immutable
Не важливо як часто ми оновлюємо наш JS: з надійною стратегією скидання кешу, ми можемо кешувати файл як завгодно довго. Тут в якості періоду кешування вказано рік. По-перше, це досить довгий проміжок часу, а, по-друге, дуже малоймовірно, що браузер так довго зберігатиме файл (браузери мають обмежений обсяг сховища, яке вони можуть використовувати для HTTP-кеша, тому вони періодично очищають його; користувачі також можуть очистити кеш свого браузера). Якщо вказати період, що перевищуватиме рік, ми навряд чи доб'ємося збільшення ефективності.
Крім того, оскільки вміст нашого файлу ніколи не змінюється, ми можемо сповістити браузер, що файл immutable
. Нам не потрібно повторно перевіряти його протягом усього року, навіть якщо користувач перезавантажує сторінку. Окрім переваги у швидкості використання кешу, ми уникаємо затримки при повторній перевірці.
Зображення
Уявіть фотографію, необхідну лише для прикраси статті. Це не інфографіка або діаграма, вона не містить ніякого контенту, критично важливого для розуміння решти сторінки, і користувач навіть не помітить, якщо це фото прибрати.
Зображення — важкий ресурс для завантаження, тому ми хотіли б кешувати їх. Вони не критично важливі для сторінки, тому немає потреби отримувати останню версію. До того ж ми не захочемо обслуговувати зображення, якщо воно не буде актуальним. Спробуймо таку стратегію:
Request URL: /content/masthead.jpg
Cache-Control: max-age=2419200, must-revalidate, stale-while-revalidate=86400
Тут ми вказали браузеру зберігати зображення протягом 28 днів. Коли цей термін завершиться, ми перевіримо наявність оновлень на сервері. Якщо ж зображення застаріло, ми зможемо використовувати кешовану версію ще протягом тижня, поки отримуємо останню версію у фоновому режимі.
Ключові моменти
- Скидання кешу надзвичайно важливе. Розробіть стратегію скидання кешу у першу чергу.
- Як правило, кешування HTML — погана ідея. URL-адреси HTML не можуть бути розірвані, і оскільки ваша HTML-сторінка зазвичай посилається на інші ресурси сторінки, ви також будете кешувати посилання на них. Користі з такого підходу мало.
- Якщо ви збираєтесь кешувати якісь HTML-файли з різними стратегіями кешування, це призведе до неузгодженості: одні дані братимуться з кеша, а інші будуть завжди свіжими.
- Якщо ви можете організувати надійну стратегію скидання кешу статичних ресурсів (використовуючи відбиток), можна встановити період кешування як рік, одночасно з використанням директиви
immutable
. - Некритично важливий контент може мати додатковий період кешування з директивами, на зразок
stale-while-revalidate
. immutable
таstale-while-revalidate
не лише дають нам класичні переваги кешу, але й дозволяють уникнути затримки при повторній валідації ресурсу.
Уникаючи звернення до мережі, де це можливо, ми можемо покращити користувацький досвід нашого застосунку (та зменшити навантаження на інфраструктуру). Оцінивши ресурси та пригадавши доступні директиви, можна створити дуже деталізовану та ефективну стратегію кешування для вашого застосунку.
Ще немає коментарів