Настройка 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
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Настройка HTTP-кэширования (ETag, Last-Modified, Vary) для API

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

Модели кэширования

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

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

Для 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 недели.