Налаштування HTTP-кешування (ETag, Last-Modified, Vary) для API

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування HTTP-кешування (ETag, Last-Modified, Vary) для API
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Налаштування HTTP-кешування (ETag, Last-Modified, Vary) для API

HTTP-кешування вбудоване в протокол, але більшість API його ігнорують—повертають Cache-Control: no-cache або нічого. В результаті клієнти роблять одні й ті самі запити повторно, передаючи однакові дані по мережі. Правильно налаштоване умовне кешування зменшує трафік і навантаження на сервер при мінімальних змінах в коді.

Моделі кешування

Сильне кешування (Cache-Control: max-age): клієнт не звертається до сервера взагалі, поки не закінчиться TTL. Підходить для статичних даних: довідники, версіоновані ресурси.

Умовні запити (ETag / Last-Modified): клієнт звертається до сервера, але передає валідатор. Сервер відповідає 304 Not Modified без тіла, якщо дані не змінилися. Економить трафік, але не час відповіді.

Для API реального часу потрібна комбінація: коротка max-age для проміжних кешів + ETag для умовних запитів.

ETag

ETag—хеш представлення ресурсу. Клієнт отримує його у відповіді, зберігає, передає назад в If-None-Match:

# Перший запит
GET /api/v1/products/42 HTTP/1.1

# Відповідь сервера
HTTP/1.1 200 OK
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Cache-Control: private, max-age=0, must-revalidate
Content-Type: application/json

{"id": 42, "name": "Widget", "price": 99.00}
# Повторний запит
GET /api/v1/products/42 HTTP/1.1
If-None-Match: "d41d8cd98f00b204e9800998ecf8427e"

# Якщо не змінилось
HTTP/1.1 304 Not Modified
ETag: "d41d8cd98f00b204e9800998ecf8427e"

Реалізація в Laravel:

public function show(Product $product): JsonResponse
{
    $etag = md5($product->updated_at . $product->id);

    if (request()->hasHeader('If-None-Match')) {
        $clientEtag = trim(request()->header('If-None-Match'), '"');
        if ($clientEtag === $etag) {
            return response()->json(null, 304)
                ->header('ETag', '"' . $etag . '"');
        }
    }

    return response()->json($product)
        ->header('ETag', '"' . $etag . '"')
        ->header('Cache-Control', 'private, max-age=0, must-revalidate');
}

Для колекцій ETag обчислюється за максимальним updated_at в вибірці:

$maxUpdated = $products->max('updated_at');
$count = $products->count();
$etag = md5($maxUpdated . $count . $page);

Last-Modified

Простіше за ETag, але менш точний—точність до секунди. Використовується разом або замість ETag:

$lastModified = $product->updated_at->toRfc7231String();

if (request()->hasHeader('If-Modified-Since')) {
    $since = Carbon::parse(request()->header('If-Modified-Since'));
    if ($product->updated_at->lte($since)) {
        return response(null, 304)
            ->header('Last-Modified', $lastModified);
    }
}

return response()->json($product)
    ->header('Last-Modified', $lastModified)
    ->header('Cache-Control', 'public, max-age=60');

Vary

Vary говорить проміжним кешам (CDN, proxy), які заголовки запиту впливають на відповідь. Без нього CDN закешує відповідь для одного варіанту і буде видавати її всім.

HTTP/1.1 200 OK
Cache-Control: public, max-age=300
Vary: Accept-Language, Accept-Encoding
Content-Language: ru

Типові випадки застосування Vary:

Сценарій Vary
Багатомовний API Accept-Language
Стиснення gzip/br Accept-Encoding
Версіонування через заголовок Accept (якщо використовується content negotiation)
CORS з різними origin Origin

Проблема: Vary: Authorization — погана ідея для публічних CDN. Кожен унікальний токен створює окремий запис в кеші. Якщо потрібно кешувати авторизовані запити, використовуйте суррогатні ключі або кешуйте тільки на стороні клієнта.

Директиви Cache-Control для API

Cache-Control: public, max-age=300, s-maxage=600
  • public — можна кешувати на CDN/proxy
  • private — тільки в браузері/клієнті
  • max-age=N — TTL в секундах для клієнта
  • s-maxage=N — TTL для shared caches (CDN), переопиняє max-age
  • no-cache — завжди валідувати через умовний запит
  • no-store — ніколи не кешувати (чутливі дані)
  • must-revalidate — не використовувати застарілий кеш
  • stale-while-revalidate=N — видавати застарілу відповідь N секунд поки йде оновлення
  • stale-if-error=N — видавати застарілу відповідь при помилці upstream

Для фінансових даних, особистих профілів: Cache-Control: private, no-store.

Для публічного каталогу: Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=600.

Інвалідація кешу

ETag/Last-Modified не допомагають з інвалідацією—вони тільки підтверджують актуальність. Для примусового скидання кешу на CDN:

# Cloudflare API
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  --data '{"files":["https://api.example.com/v1/products/42"]}'

Суррогатні ключі (Cloudflare Cache-Tag, Fastly Surrogate-Key):

Cache-Tag: product-42, category-electronics
Surrogate-Key: product-42 category-electronics

Після оновлення товару—інвалідація всіх URL з тегом product-42 одним викликом.

Тестування

# Перевірити заголовки відповіді
curl -I https://api.example.com/v1/products/42

# Умовний запит з ETag
curl -H 'If-None-Match: "abc123"' https://api.example.com/v1/products/42

# Перевірити X-Cache від CDN
curl -v https://api.example.com/v1/products/42 2>&1 | grep -i 'x-cache\|age\|etag\|cache-control'

Терміни

Додавання ETag + Last-Modified до існуючого API: 2–4 дні (реалізація + тести + документація). Повна стратегія з Vary, Cache-Control за типами ресурсів, інвалідацією CDN і суррогатними ключами: 1–2 тижні.