ETag
ETag (Entity Tag) — HTTP-заголовок, содержащий идентификатор версии ресурса. Обычно это хеш содержимого или инкрементальный номер. Позволяет клиенту спросить «а версия всё та же?» и не качать заново, а серверу — предотвратить lost update при конкурентном изменении.
Как выглядит
HTTP/1.1 200 OK
ETag: "5d8c72a5edda3d7c6b4b2e6b4a2c8b1e"
Content-Length: 12345
...
Синтаксис — строка в кавычках. Может быть с префиксом W/ для weak-ETag:
ETag: W/"12345-abcdef"
Strong vs Weak
| Strong ETag | Weak ETag (W/"...") | |
|---|---|---|
| Означает | Байт-в-байт одинаковый ресурс | «Смыслово тот же», байты могут отличаться |
| Пример | SHA-256 контента | Хеш только «важных» полей, без timestamps |
| Годен для Range-запросов | Да | Нет |
| Годен для If-Match (защита от race) | Да | Нет (только If-None-Match) |
Кэширование — If-None-Match
Клиент имеет закэшированную копию с ETag "abc". При следующем запросе:
GET /image.png HTTP/1.1
If-None-Match: "abc"
Сервер сравнивает свой текущий ETag с присланным:
- Совпал →
304 Not Modified, без тела. Клиент использует кэш. - Не совпал →
200 OKс новым ETag и телом.
Concurrency control — If-Match
Проблема lost update: два клиента загрузили ресурс с версией "v5", оба меняют, оба PUT. Второй PUT перезапишет первый.
PUT /doc HTTP/1.1
If-Match: "v5"
...
Сервер проверяет: текущий ETag всё ещё "v5"?
- Совпал → применяет PUT, ETag становится
"v6". - Не совпал →
412 Precondition Failed. Клиент должен перезагрузить, помёржить, повторить.
Это оптимистичная блокировка — не блокирует ресурс, но ловит конфликты.
ETag vs Last-Modified
Ещё один способ версионирования — заголовок Last-Modified: Wed, 15 Jul 2026 22:00:00 GMT. Проверяется через If-Modified-Since.
| ETag | Last-Modified | |
|---|---|---|
| Гранулярность | Любая (даже байт) | Секунда |
| Определяет «одинаковость» | По содержимому | По времени |
| Работает если mtime сбросился | Да | Нет |
| Работает для CDN с многими нодами | Да (если детерминированный) | Плохо (mtime разный) |
Современные серверы шлют оба, клиенты могут использовать любой.
Как генерировать
- Nginx — генерирует автоматически по
Last-Modified + Content-Length(weak). - Apache — по
inode-size-mtime. Проблема при мульти-серверном setup (inode разный). ДирективаFileETag MTime Size. - Приложение (Django/Rails/Express) — обычно SHA-256 или MD5 от response body.
- S3 / GCS — MD5 хеш файла как ETag (для multipart-загрузок особый формат).
Проблемы и подводные
- ETag как «cookie». Уникальные ETag на пользователя можно использовать для трекинга. Некоторые адблокеры чистят ETag.
- CDN с двумя нодами. Если каждая генерирует свой ETag — кэш ломается. Нужно чтобы формула была детерминированной.
- gzip. Nginx с включённым gzip меняет ETag на weak — потому что тело сжатое отличается от исходного.
ETag в реальных API
- GitHub API — активно использует If-None-Match, экономит rate-limit.
- Kubernetes API —
resourceVersionвместо ETag, но идея та же. - CouchDB —
_revполе документа выполняет роль ETag. - S3 — ETag на объект для оптимизации.