ETag
Применение
Заголовок ETag присваивает уникальную метку каждой версии ресурса. Когда ресурс изменяется, меняется и значение ETag. Клиент сохраняет ETag из закэшированного ответа и отправляет его обратно в заголовке запроса If-None-Match при последующих обращениях. Если ETag по-прежнему совпадает, сервер возвращает 304 Not Modified без тела, и клиент повторно использует свою закэшированную копию. Если ETag больше не совпадает, сервер отправляет полный обновлённый ответ.
Валидация на основе ETag надёжнее валидации по дате с помощью Last-Modified. Строгие сущностные метки — это непрозрачные строки, привязанные к точному содержимому представления, тогда как отметки времени изменения имеют гранулярность в одну секунду и подвержены расхождению часов. Когда присутствуют и ETag, и Last-Modified, при повторной валидации приоритет имеет ETag, как того требует стандарт HTTP.
ETag также позволяет управлять параллельным доступом для небезопасных методов. Запрос PUT с заголовком If-Match гарантирует, что обновление будет применено только к ожидаемой версии. Если ресурс изменился с момента, когда клиент в последний раз получил ETag, сервер возвращает 412 Precondition Failed, предотвращая проблему потерянного обновления.
Значение ETag генерируется сервером. Распространённые стратегии включают хеширование содержимого, счётчики версий и комбинацию времени изменения файла с его размером. Значение заключается в двойные кавычки и может дополнительно предваряться префиксом W/ для слабого сравнения.
Примечание
Google рекомендует использовать ETag вместо Last-Modified для указания предпочтений кэширования, поскольку ETag избавляет от проблем с форматированием дат. Когда присутствуют и ETag, и Last-Modified, поисковые роботы Google используют значение ETag, как того требует стандарт HTTP. Устанавливать оба заголовка всё же рекомендуется, потому что на них опираются и другие приложения, например CMS. Отдельные поисковые роботы и загрузчики Google используют кэширование в зависимости от потребностей связанного с ними продукта. Googlebot поддерживает кэширование при повторном сканировании URL для Google Search.
Строгий ETag
Строгий ETag гарантирует побайтовую идентичность двух представлений ресурса. Два ресурса с одинаковым строгим ETag взаимозаменяемы на уровне байтов. Строгие ETag подходят для диапазонных запросов и всех методов сравнения.
ETag: "abc123"
Слабый ETag
Слабый ETag, предваряемый префиксом W/, указывает на семантическую эквивалентность, а не на побайтовую идентичность. Два ресурса с одинаковым слабым ETag считаются равнозначными по смыслу, но их содержимое не обязательно совпадает. Слабые ETag проще генерировать, поскольку они допускают незначительные различия, такие как изменения пробельных символов или обновление отметок времени в теле. Слабые ETag недопустимы для запросов байтовых диапазонов.
ETag: W/"v2.6"
Пример
Сервер возвращает ETag вместе с ответом. Клиент сохраняет метку для будущей повторной валидации.
HTTP/1.1 200 OK
ETag: "33a64df5"
Content-Type: text/html
Cache-Control: max-age=3600
Клиент выполняет повторную валидацию с помощью If-None-Match. Сервер сверяет метку с текущей версией ресурса.
GET /page HTTP/1.1
If-None-Match: "33a64df5"
Ресурс не изменился. Сервер возвращает 304 без тела, и клиент повторно использует свой кэш.
HTTP/1.1 304 Not Modified
ETag: "33a64df5"
Cache-Control: max-age=3600
Запрос PUT с If-Match для оптимистичного контроля параллельного доступа. Обновление выполняется только в том случае, если ресурс по-прежнему соответствует ETag, которым располагает клиент.
PUT /api/document/42 HTTP/1.1
If-Match: "33a64df5"
Content-Type: application/json
Слабый ETag на ресурсе, где допустимы незначительные вариации тела (например, встроенные отметки времени). Префикс W/ обозначает семантику слабого сравнения.
ETag: W/"2024-11-22-v3"
Диагностика
Проблемы, связанные с ETag, проявляются как неудачная повторная валидация, неожиданные полные загрузки или конфликты параллельного доступа в распределённой инфраструктуре.
Несовпадение ETag между серверами за балансировщиком нагрузки. Разные серверы за балансировщиком нагрузки генерируют разные ETag для одного и того же ресурса, если алгоритм генерации учитывает номера inode, отметки времени или специфичные для сервера данные. Клиент закэшировал ответ от сервера A, но выполняет повторную валидацию на сервере B, который возвращает другой ETag и вынуждает выполнить полную загрузку. В Apache ETag по умолчанию включает inode, size и mtime. Уберите компонент inode:
FileETag MTime Size
В nginx ETag выводятся из времени последнего изменения и длины содержимого, что даёт одинаковые значения на серверах с одним и тем же содержимым файлов. Убедитесь, что отметки времени развёртывания файлов совпадают на всех узлах.
IIS выдаёт ETag в формате “filetime:changenumber”, например “04c2528da58dc1:0”. Первая половина кодирует в шестнадцатеричном виде Windows FILETIME последнего изменения файла. Суффикс ChangeNumber отслеживает состояние конфигурации IIS. IIS 7 и более поздние версии всегда выдают :0: свойство метабазы, управлявшее этим счётчиком, было упразднено вместе с IIS 6, и ни одна настройка applicationHost.config или web.config не раскрывает ненулевое значение. Поэтому узлы за балансировщиком нагрузки выдают совпадающие ETag для одного и того же файла. IIS 6 и более ранние версии увеличивают ChangeNumber при правках метабазы и перезапусках службы, из-за чего узлы рассогласовываются, и межузловая повторная валидация возвращает 200 вместо 304. Сбросьте значение на каждом узле IIS 6 командой cscript adsutil.vbs set w3svc/etag_changenumber 0 либо уберите суффикс, чтобы ETag отражал только FILETIME. Любой ненулевой суффикс, встреченный на практике, указывает на источник под управлением IIS 6.
Слабый ETag приводит к неожиданному полному ответу вместо 304. Слабые ETag (W/”…”) обозначают семантическую эквивалентность, но не поддерживают запросы байтовых диапазонов. Запрос If-None-Match со слабым ETag по-прежнему пригоден для повторной валидации кэша. Если сервер возвращает полный ответ 200 вместо 304, логика сравнения на стороне сервера может быть некорректной. Проверьте, что сервер сравнивает слабые ETag с помощью функции слабого сравнения, которая при сопоставлении игнорирует префикс W/.
Клиент не отправляет If-None-Match. Браузер отправляет If-None-Match только тогда, когда закэшированный ответ содержит заголовок ETag. Отсутствие ETag в первоначальном ответе блокирует всю последующую повторную валидацию. Проверьте ответ источника командой curl -I https://example.re/resource и убедитесь, что заголовок ETag присутствует. Серверы статических файлов, такие как nginx и Apache, генерируют ETag автоматически. Серверы приложений и API-фреймворки часто требуют явной генерации ETag в коде.
CDN удаляет или изменяет ETag. Некоторые CDN изменяют значения ETag в процессе обработки. Cloudflare преобразует строгие ETag в слабые при применении преобразований вроде сжатия Brotli или оптимизации изображений. AWS CloudFront передаёт ETag от источника без изменений. Проверьте ETag на источнике (curl -I https://origin.example.re) и сравните с граничным узлом CDN (curl -I https://cdn.example.re). Когда CDN изменяет ETag, повторная валидация через CDN всё равно работает, поскольку CDN хранит соответствие. А вот повторная валидация напрямую к источнику с изменённым CDN значением ETag завершается неудачей.
Apache и nginx генерируют ETag по-разному. Apache по умолчанию использует inode-mtime-size, порождая ETag вида “1a2b3c-4d5e-6f7a8b9c”. Nginx использует mtime-content-length в шестнадцатеричном виде, порождая ETag вида “5f4dcc3b-d8”. Миграция между серверами делает недействительными все закэшированные ETag, вызывая полную загрузку для каждого клиента. Заложите период прогрева кэша после миграции серверов. Для ответов API генерируйте ETag из хешей содержимого (MD5 или SHA-256), чтобы получать значения, не зависящие от сервера.
Примечание
Генерация ETag в Apache по умолчанию включает номер inode файла (FileETag INode MTime Size). Номера inode раскрывают сведения о файловой системе сервера, позволяя выполнять снятие отпечатков и сопоставление между серверами. OWASP относит ETag на основе inode к рискам раскрытия информации. Уберите inode из расчёта директивой FileETag MTime Size в конфигурации Apache.
Примечание
За помощью по SEO и кэшированию обращайтесь к бывшим SEO-консультантам Google — Search Brothers.