---
layout: post
title: Предотвращение ненужных сетевых запросов с помощью кэша HTTP
authors:
  - jeffposnick
  - ilyagrigorik
date: 2018-11-05
updated: 2020-04-17
description: Как избежать ненужных сетевых запросов? Первое из доступных средств — кэш HTTP браузера. Это не самый мощный и гибкий подход, и он дает ограниченный контроль над сроком жизни кэшированных ответов. Тем не менее он эффективен, поддерживается во всех браузерах и для его настройки не требуется особых усилий.
codelabs:
  - codelab-http-cache
feedback:
  - api
---

Получать ресурсы по сети медленно и дорого.

- При получении больших ответов требуется выполнять много операций обмена данными между браузером и сервером.
- Страница не загрузится, пока не будут полностью загружены все ее [критически важные ресурсы](https://developers.google.com/web/fundamentals/performance/critical-rendering-path).
- Если кто-то посещает ваш сайт, используя тарифный план мобильной сети с ограниченным количеством трафика, то для него каждый ненужный сетевой запрос — пустая трата денег.

Как избежать ненужных сетевых запросов? Первое из доступных средств — кэш HTTP браузера. Это не самый мощный и гибкий подход, и он дает ограниченный контроль над сроком жизни кэшированных ответов. Тем не менее он эффективен, поддерживается во всех браузерах и для его настройки не требуется особых усилий.

В этом руководстве рассказывается об основных принципах реализации эффективного кэширования HTTP.

## Совместимость браузеров {: #browser-compatibility }

На самом деле не существует единого API, называемого HTTP Cache (кэшем HTTP). Это общее название коллекции API веб-платформы. Эти API поддерживаются во всех браузерах:

- [`Cache-Control;`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control#Browser_compatibility)
- [`ETag;`](https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag#Browser_compatibility)
- [`Last-Modified;`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified#Browser_compatibility)

## Принцип работы кэша HTTP {: #overview }

Все HTTP-запросы, создаваемые браузером, сначала поступают в кэш браузера, чтобы проверить, имеются ли допустимые кэшированные ответы, которые можно использовать для выполнения запроса. Если нужный ответ есть, он будет считан из кэша. Это позволяет уменьшить задержки в сети и затраты на передачу данных.

Порядок работы кэша HTTP определяется сочетанием [заголовков запросов](https://developer.mozilla.org/docs/Glossary/Request_header) и [заголовков ответов](https://developer.mozilla.org/docs/Glossary/Response_header). В идеальном сценарии вы сможете контролировать и код веб-приложения (от которого зависят заголовки запросов), и конфигурацию веб-сервера (от которого зависят заголовки ответов).

Более глубокий концептуальный обзор см. в статье MDN «[HTTP-кеширование](https://developer.mozilla.org/docs/Web/HTTP/Caching)».

## Заголовки запросов: использование параметров, применяемых по умолчанию (в обычных случаях) {: #request-headers }

Несмотря на то что в исходящие запросы веб-приложения следует включать ряд важных заголовков, при создании запросов браузер почти всегда самостоятельно настраивает заголовки от вашего имени. Создавая заголовки запросов, влияющих на проверку актуальности, например [`If-None-Match`](https://developer.mozilla.org/docs/Web/HTTP/Headers/If-None-Match) и [`If-Modified-Since`](https://developer.mozilla.org/docs/Web/HTTP/Headers/If-Modified-Since), браузер руководствуется собственными оценками текущих значений в кэше HTTP.

Это хорошо, потому что вы можете включать нужные теги, например `<img src="мое_изображение.png">`, в HTML-код, а браузер автоматически позаботится о кэшировании HTTP без дополнительных усилий с вашей стороны.

{% Aside %} Разработчикам, которым требуется больший контроль над кэшем HTTP в своих веб-приложениях, доступна альтернатива — вы можете «раскрыть» уровень и вручную использовать [Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API), передавая ему объекты [`Request`](https://developer.mozilla.org/docs/Web/API/Request) с набором переопределений [`cache`](https://developer.mozilla.org/docs/Web/API/Request/cache). Однако эта тема выходит за рамки данного руководства. {% endAside %}

## Заголовки ответов: настройка веб-сервера {: #response-headers }

Самая важная часть настройки кэширования HTTP — это заголовки, которые веб-сервер добавляет к каждому исходящему ответу. Все указанные ниже заголовки влияют на эффективность кэширования.

- [`Cache-Control`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control). Сервер может возвратить директиву `Cache-Control`, чтобы указать, каким образом и на какой срок браузер и другие промежуточные кэши должны кэшировать тот или иной ответ.
- [`ETag`](https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag). Когда браузер находит кэшированный ответ с истекшим сроком действия, он может отправить на сервер небольшой токен (обычно хэш содержимого файла), чтобы проверить, не изменился ли файл. Если сервер возвращает такой же токен, это означает, что файл не изменился, и повторно загружать его не нужно.
- [`Last-Modified`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified). Этот заголовок используется для той же цели, что и заголовок `ETag`, но для него применяется стратегия на основе времени, позволяющая определить, изменился ли ресурс (в отличие от стратегии на основе содержимого, применяемой для заголовка `ETag`).

В некоторых веб-серверах имеются встроенные средства, настраивающие такие заголовки по умолчанию, в то время как другие веб-серверы полностью пропускают заголовки, если явно не настроить их. *Порядок* настройки заголовков сильно зависит от того, какой веб-сервер вы используете. Точные сведения о настройке см. в документации по используемому вами серверу.

Чтобы не тратить время на поиск, воспользуйтесь приведенными ниже ссылками на инструкции по настройке нескольких популярных веб-серверов.

- [Express](https://expressjs.com/en/api.html#express.static)
- [Apache](https://httpd.apache.org/docs/2.4/caching.html)
- [nginx](http://nginx.org/en/docs/http/ngx_http_headers_module.html)
- [Firebase Hosting](https://firebase.google.com/docs/hosting/full-config)
- [Netlify](https://www.netlify.com/blog/2017/02/23/better-living-through-caching/)

Если заголовки ответов `Cache-Control` не обрабатываются, это не значит, что кэширование HTTP отключено. Вместо этого браузер [эффективно прогнозирует](https://www.mnot.net/blog/2017/03/16/browser-caching#heuristic-freshness), какие действия при кэшировании лучше всего подходят для контента того или иного типа. Скорее всего вам потребуется больший контроль, чем обеспечивает эта функция, поэтому вам придется настроить заголовки ответов.

## Какие значения заголовков ответов использовать {: #response-header-strategies }

При настройке заголовков ответов веб-сервера следует учитывать два важных сценария.

### Долгосрочное кэширование для URL-адресов со сведениями о версиях {: #versioned-urls }

{% Details %} {% DetailsSummary 'h4' %} Как URL-адреса со сведениями о версиях могут упростить стратегию кэширования Использовать URL-адреса со сведениями о версиях — хороший подход, так как он упрощает аннулирование кэшированных ответов. {% endDetailsSummary %} Предположим, ваш сервер сообщает браузерам, что нужно кэшировать файл CSS на 1 год (<code>Cache-Control: max-age=31536000</code>), но ваш дизайнер только что создал экстренное обновление, которое нужно немедленно развернуть. Как уведомить браузеры о том, что необходимо обновить «устаревший» кэшированный файл? Вы не сможете сделать это (не изменив, по крайней мере, URL-адрес ресурса). После того, как браузер кэширует ответ, кэшированная версия используется до тех пор, пока она не перестанет быть актуальной согласно значениям параметров <code>max-age</code> или <code>expires</code> либо пока она не будет удалена из кэша по какой-либо другой причине, например если пользователь очистит кэш браузера. В результате может получиться так, что при создании страницы для разных пользователей будут использоваться разные версии файла: для пользователей, которые только что получили ресурс, будет использоваться новая версия, а для пользователей, у которых кэширована более ранняя (но все еще действующая) копия, будет использоваться старая версия ответа. Как использовать лучшее из обоих «миров» — кэширование на стороне клиента и быстрые обновления? Нужно изменить URL-адрес ресурса и сделать так, чтобы пользователь загрузил новый ответ при изменении содержимого этого ответа. Обычно для этого внедряют отпечаток файла или номер версии в имя файла, например так: <code>style.x234dff.css</code>. {% endDetails %}

При ответах на запросы, связанные с URL-адресами, которые содержат «[отпечаток](https://en.wikipedia.org/wiki/Fingerprint_(computing))» или информацию о версии и для которых не предполагается изменять содержимое, добавляйте к ответам заголовок `Cache-Control: max-age=31536000`.

После настройки этого значения сервер сообщит браузеру, что если последнему потребуется загрузить ресурс по тому же URL-адресу в течение следующего года (31 536 000 секунд, максимальное поддерживаемое значение), он может использовать значение, которое хранится в кэше HTTP и не отправлять сетевой запрос на ваш веб-сервер. Это здорово — вы повысили надежность и скорость, исключив ряд сетевых операций.

С помощью средств сборки, например webpack, можно [автоматизировать процесс](https://webpack.js.org/guides/caching/#output-filenames) назначения хэш-отпечатков URL-адресам ваших ресурсов.

{% Aside %} Кроме того, для дополнительной оптимизации можно добавить [свойство `immutable`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control#Revalidation_and_reloading) в заголовок `Cache-Control`, хотя некоторые браузеры [будут игнорировать его](https://www.keycdn.com/blog/cache-control-immutable#browser-support). {% endAside %}

### Повторная URL-адресов без сведений о версиях на сервере {: #unversioned-urls }

К сожалению, не во всех URL-адресах, используемых для загрузки ресурсов, имеется информация о версии. Например, если у вас нет возможности добавить этап сборки перед развертыванием веб-приложения, вам не удается включать хэши в URL-адреса ресурсов. Каждое веб-приложение работает с HTML-файлами, а в этих файлах никогда (или почти никогда) не содержатся сведения о версиях, так как никто не будет использовать ваше приложение, если для этого нужно будет переходить примерно по такому URL-адресу: `https://example.com/index.34def12.html`. Так что же можно сделать при использовании этих URL-адресов?

Это один из сценариев, в которых нужно признать поражение. Только кэширования HTTP недостаточно, чтобы полностью отказаться от передачи данных по сети. (Но не беспокойтесь, скоро вы узнаете о [служебных сценариях](/service-workers-cache-storage/), с помощью которых можно снова будет взять ситуацию под контроль). Тем не менее вы можете выполнить несколько действий и убедиться, что сетевые запросы выполняются максимально быстро и эффективно.

С помощью указанных ниже значений заголовка `Cache-Control` можно точно указать, где и каким образом следует кэшировать URL-адреса без сведений о версиях.

- `no-cache`. Это значение указывает браузеру, что он должен выполнять проверку на сервере каждый раз перед использованием кэшированного контента для соответствующего URL-адреса.
- `no-store`. Это значение указывает браузеру и другим промежуточным кэшам (например, CDN) никогда не сохранять никакие версии файла.
- `private`. Браузерам разрешено кэшировать файл, а промежуточным кэшам — нет.
- `public`. Ответ можно хранить в любом кэше.

Изучите раздел «[Приложение: блок-схема заголовка `Cache-Control`](#flowchart)», в котором наглядно представлен процесс принятия решения об использовании тех или иных значений заголовка `Cache-Control`. Кроме того, обратите внимание на то, что для заголовка `Cache-Control` можно использовать список директив, разделенных запятыми. См. раздел «<a href="#examples" data-md-type="link">Приложение: примеры заголовка `Cache-Control`</a>».

Наряду с этим может быть полезно настроить один из двух дополнительных заголовков ответов — [`ETag`](https://developer.mozilla.org/docs/Web/HTTP/Headers/ETag) или [`Last-Modified`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Last-Modified). Как упоминалось в разделе «[Заголовки ответов](#response-headers)», заголовки `ETag` и `Last-Modified` используются для одной цели: определить, нужно ли браузеру повторно загружать кэшированный файл с истекшим сроком действия. Рекомендуется использовать заголовок `ETag`, так как он позволяет получать более точные результаты.

{% Details %} {% DetailsSummary 'h4' %} Пример заголовка ETag {% endDetailsSummary %} Предположим, что с момента первоначального получения ресурса прошло 120 секунд, и браузер инициировал новый запрос для того же ресурса. Сначала браузер проверяет кэш HTTP и находит предыдущий ответ. К сожалению, браузер не может использовать предыдущий ответ, потому что срок действия последнего истек. На этом этапе браузер может отправить новый запрос и получить новый полный ответ. Однако это неэффективно, потому что если ресурс не изменился, нет причин загружать информацию, которая уже находится в кэше. Для решения этой проблемы предназначены токены проверки, которые указывают в заголовке <code>ETag</code>. Сервер генерирует и возвращает произвольный токен, который обычно представляет собой хэш или другой отпечаток содержимого файла. Браузеру не нужна информация о том, каким образом был создан отпечаток; ему достаточно отправить этот отпечаток на сервер при следующем запросе. Если отпечаток остался прежним, это значит, что ресурс не изменился, и браузер может не загружать его. {% endDetails %}

После настройки заголовка `ETag` или `Last-Modified` запросы на повторную проверку будут выполняться намного эффективнее. В конечном итоге они будут запускать заголовок запроса [`If-Modified-Since`](https://developer.mozilla.org/docs/Web/HTTP/Headers/If-Modified-Since) или [`If-None-Match`](https://developer.mozilla.org/docs/Web/HTTP/Headers/If-None-Match), о которых мы говорили в разделе «[Заголовки запросов](#request-headers)».

Когда на правильно настроенный веб-сервер поступают запросы с этими заголовками, он может проверить, соответствует ли версия ресурса, хранящегося в кэше HTTP браузера, последней версии на веб-сервере. Если версии совпадают, сервер может отправить HTTP-ответ [`304 Not Modified`](https://developer.mozilla.org/docs/Web/HTTP/Status/304), что эквивалентно фразе «Продолжайте использовать контент, который у вас уже есть». При отправке ответов такого типа требуется передавать очень мало данных, поэтому такие операции обычно выполняются намного быстрее, чем передача запрошенного ресурса.

<figure>{% Img src="image/tcFciHGuF3MxnTr1y5ue01OGLBn2/e2bN6glWoVbWIcwUF1uh.png", alt="Схема, на которой показаны клиент, запрашивающий ресурс, и сервер, отвечающий с использованием заголовка 304.", width="474", height="215" %} <figcaption> Браузер запрашивает файл <code>/file</code> с сервера и включает в запрос заголовок <code>If-None-Match</code>, тем самым сообщая серверу, что полный файл нужно возвращать, только если заголовок <code>ETag</code> файла на сервере не соответствует значению <code>If-None-Match</code> в браузере. В данном случае два значения совпали, поэтому сервер возвращает ответ <code>304 Not Modified</code> с инструкциями о том, сколько еще следует хранить файл в кэше (<code>Cache-Control: max-age=120</code>). </figcaption></figure>

## Сводка {: #summary }

Кэш HTTP — эффективный способ повысить производительность при загрузке данных, так как он позволяет уменьшить количество ненужных сетевых запросов. Он поддерживается во всех браузерах и для его настройки не требуются особые усилия.

Для начала можно использовать указанные ниже конфигурации заголовка `Cache-Control`.

- `Cache-Control: no-cache` — для ресурсов, которые следует повторно проверять на сервере перед каждым использованием.
- `Cache-Control: no-store` — для ресурсов, которые никогда не нужно кэшировать.
- `Cache-Control: max-age=31536000` — для ресурсов со сведениями о версиях.

С помощью заголовков `ETag` и `Last-Modified` можно эффективнее проверять ресурсы с истекшим сроком действия в кэше.

{% Aside 'codelab' %} Воспользуйтесь [практическим занятием по кэшу HTTP](/codelab-http-cache), чтобы потренироваться работать с заголовками `Cache-Control` и `ETag` на веб-сервере Express. {% endAside %}

## Дополнительные сведения {: #learn-more }

Если вам недостаточно основных сведений по использованию заголовка `Cache-Control`, прочитайте руководство Джейка Арчибальда (Jake Archibald) «[Рекомендации по кэшированию и соображения по максимальному возрасту данных](https://jakearchibald.com/2016/caching-best-practices/)» (Caching best practices &amp; max-age gotchas).

Рекомендации о том, как оптимизировать использование кэша для повторных посетителей см. в статье «[Любите кэш](/love-your-cache)» (Love your cache).

## Приложение: дополнительные советы {: #tips }

Если у вас есть время, можно оптимизировать использование кэша HTTP указанными ниже способами.

- Использование согласованных URL-адресов. Если вы передаете один и тот же контент по разным URL-адресам, клиенты будут получать и сохранять этот контент несколько раз.
- Разделение данных. Если часть ресурса (например, файл CSS) обновляется часто, а остальная часть файла (например, код библиотеки) — нет, попробуйте отделить часто обновляемый код и разместить его в отдельном файле. Для часто обновляемого кода используйте стратегию краткосрочного кэширования, а для кода, который изменяется редко, — стратегию долгосрочного кэширования.
- Если в используемой вами политике заголовка `Cache-Control` допускается некоторая степень устаревания данных, попробуйте использовать новую директиву [`stale-while-revalidate`](/stale-while-revalidate/).

## Приложение: блок-схема заголовка `Cache-Control` {: #flowchart }

{% Img src="image/admin/htXr84PI8YR0lhgLPiqZ.png", alt="Блок-схема", width="595", height="600" %}

## Приложение: примеры заголовка `Cache-Control` {: #examples }

<div class="table-wrapper scrollbar">
  <table>
    <thead>
      <tr>
        <th>Значение заголовка <code>Cache-Control</code>
</th>
        <th>Пояснение</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><code>max-age=86400</code></td>
        <td>Ответ разрешено кэшировать в браузерах и промежуточных кэшах на срок до 1 дня (60 секунд x 60 минут x 24 часа).</td>
      </tr>
      <tr>
        <td><code>private, max-age=600</code></td>
        <td>Ответ разрешено кэшировать в браузерах (но не в промежуточных кэшах) на срок до 10 минут (60 секунд x 10 минут).</td>
      </tr>
      <tr>
        <td><code>public, max-age=31536000</code></td>
        <td>Ответ разрешено хранить в любых кэшах в течение 1 года.</td>
      </tr>
      <tr>
        <td><code>no-store</code></td>
        <td>Ответ запрещено кэшировать; следует получать его полностью при каждом запросе.</td>
      </tr>
    </tbody>
  </table>
</div>
