Посібник з Cache-Control

18 хв. читання

За даними наведеного опитування лише 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, визначимо які проблеми вирішує директива.

Коли користувач перезавантажує сторінку, браузер повторно перевіряє «свіжість» файлу, тому що перезавантаження свідчить про одну з двох причин:

  1. Щось пішло не так на сторінці;
  2. Її вміст виглядає застарілим.

Якщо на сервері з'явився новіший файл, ми звичайно хочемо завантажити його. Таким чином, ми отримаємо відповідь 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

Особливість реалізації

Сенс такого підходу у зміні назви файлу, але це не повинен бути відбиток браузера. Всі наступні приклади діють однаково:

  1. /assets/style.ae3f66.css: скидання з хеш-кодом вмісту файлу;
  2. /assets/style.1.2.14.css: скидання з версією випуску;
  3. /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 не лише дають нам класичні переваги кешу, але й дозволяють уникнути затримки при повторній валідації ресурсу.

Уникаючи звернення до мережі, де це можливо, ми можемо покращити користувацький досвід нашого застосунку (та зменшити навантаження на інфраструктуру). Оцінивши ресурси та пригадавши доступні директиви, можна створити дуже деталізовану та ефективну стратегію кешування для вашого застосунку.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 4.8K
Приєднався: 10 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація