Кэширование и заголовки безопасности

Workbox — это мощная библиотека для работы с Service Worker в JavaScript, упрощающая реализацию стратегий кэширования и оптимизацию офлайн-доступа. В основе работы лежит предопределённый набор стратегий кэширования, которые позволяют гибко управлять сохранением ресурсов, временем их жизни и обновлением.

Стратегии кэширования

Workbox предоставляет несколько ключевых стратегий кэширования:

  1. CacheFirst Предпочитает данные из кэша. Если ресурс отсутствует, происходит запрос к сети. Используется для статических ресурсов, таких как изображения, CSS и JS.

    workbox.routing.registerRoute(
      ({request}) => request.destination === 'image',
      new workbox.strategies.CacheFirst({
        cacheName: 'images-cache',
        plugins: [
          new workbox.expiration.ExpirationPlugin({
            maxEntries: 50,
            maxAgeSeconds: 30 * 24 * 60 * 60, // 30 дней
          }),
        ],
      })
    );
  2. NetworkFirst Сначала пытается получить ресурс с сети, при неудаче использует кэш. Подходит для динамического контента, например, API-запросов.

    workbox.routing.registerRoute(
      ({url}) => url.pathname.startsWith('/api/'),
      new workbox.strategies.NetworkFirst({
        cacheName: 'api-cache',
        networkTimeoutSeconds: 3,
        plugins: [
          new workbox.expiration.ExpirationPlugin({
            maxEntries: 50,
            maxAgeSeconds: 5 * 60, // 5 минут
          }),
        ],
      })
    );
  3. StaleWhileRevalidate Возвращает ресурс из кэша мгновенно и параллельно обновляет его с сети. Идеален для часто изменяющихся, но не критичных данных.

    workbox.routing.registerRoute(
      ({request}) => request.destination === 'script',
      new workbox.strategies.StaleWhileRevalidate({
        cacheName: 'js-cache',
      })
    );

Управление заголовками кэша

Workbox позволяет устанавливать заголовки для контроля кэширования и безопасности:

  • Cache-Control — ключевой заголовок для управления временем жизни ресурса. Пример для работы с API:

    workbox.routing.registerRoute(
      '/api/data',
      async ({event}) => {
        const response = await fetch(event.request);
        const modifiedResponse = new Response(response.body, {
          ...response,
          headers: {
            ...response.headers,
            'Cache-Control': 'no-store, no-cache, must-revalidate',
          },
        });
        return modifiedResponse;
      }
    );
  • Expires и ETag — используются для валидации кэшированных ресурсов, предотвращая лишние запросы.

Безопасность через заголовки

Workbox поддерживает интеграцию с заголовками безопасности, которые критичны для современных веб-приложений:

  1. Content-Security-Policy (CSP) Ограничивает источники ресурсов, предотвращая внедрение вредоносного кода:

    workbox.routing.registerRoute(
      ({request}) => request.destination === 'script',
      async ({event}) => {
        const response = await fetch(event.request);
        return new Response(response.body, {
          ...response,
          headers: {
            ...response.headers,
            'Content-Security-Policy': "default-src 'self'; script-src 'self'",
          },
        });
      }
    );
  2. Strict-Transport-Security (HSTS) Принуждает браузеры использовать HTTPS:

    workbox.routing.registerRoute(
      ({request}) => request.url.startsWith('https://'),
      async ({event}) => {
        const response = await fetch(event.request);
        return new Response(response.body, {
          ...response,
          headers: {
            ...response.headers,
            'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
          },
        });
      }
    );
  3. X-Content-Type-Options Предотвращает MIME-type sniffing:

    workbox.routing.registerRoute(
      ({request}) => request.destination === 'style',
      async ({event}) => {
        const response = await fetch(event.request);
        return new Response(response.body, {
          ...response,
          headers: {
            ...response.headers,
            'X-Content-Type-Options': 'nosniff',
          },
        });
      }
    );

Использование плагинов Workbox для безопасности и кэширования

Workbox включает ряд плагинов, которые расширяют возможности управления кэшем и безопасности:

  • ExpirationPlugin — ограничивает количество и возраст кэшированных элементов.
  • CacheableResponsePlugin — позволяет кэшировать только определённые ответы (например, успешные HTTP-статусы 200-299).
  • BroadcastUpdatePlugin — уведомляет клиент о новых версиях кэшированных ресурсов.
  • Plugin для настройки заголовков — позволяет автоматически добавлять заголовки безопасности к любым ресурсам, проходящим через Service Worker.

Оптимизация офлайн-доступа

Кэширование с Workbox не только ускоряет загрузку страниц, но и обеспечивает стабильную работу приложения офлайн. Для максимальной эффективности важно:

  • Разделять кэш на статические ресурсы и динамический контент.
  • Использовать правильные стратегии для каждого типа ресурсов.
  • Настраивать время жизни и обновление кэша через ExpirationPlugin.
  • Контролировать безопасность с помощью заголовков CSP, HSTS, X-Content-Type-Options.

Примеры комплексного кэширования и безопасности

// Кэшируем статические файлы с CacheFirst и ExpirationPlugin
workbox.routing.registerRoute(
  ({request}) => ['script', 'style', 'image'].includes(request.destination),
  new workbox.strategies.CacheFirst({
    cacheName: 'static-resources',
    plugins: [
      new workbox.expiration.ExpirationPlugin({
        maxEntries: 100,
        maxAgeSeconds: 7 * 24 * 60 * 60,
      }),
      new workbox.cacheableResponse.CacheableResponsePlugin({
        statuses: [0, 200],
      }),
    ],
  })
);

// Защищаем API-запросы с NetworkFirst и заголовками
workbox.routing.registerRoute(
  ({url}) => url.pathname.startsWith('/api/'),
  new workbox.strategies.NetworkFirst({
    cacheName: 'api-cache',
    networkTimeoutSeconds: 5,
    plugins: [
      new workbox.expiration.ExpirationPlugin({ maxEntries: 50 }),
      {
        fetchDidSucceed: async ({response}) => {
          const modifiedHeaders = new Headers(response.headers);
          modifiedHeaders.set('Cache-Control', 'no-store');
          modifiedHeaders.set('Strict-Transport-Security', 'max-age=31536000');
          return new Response(response.body, { headers: modifiedHeaders });
        },
      },
    ],
  })
);

Эта конфигурация сочетает кэширование с политиками безопасности, обеспечивая высокую производительность и защиту веб-приложения.