ETag (Entity Tag) — HTTP-заголовок, который присваивает уникальный идентификатор версии ресурса на сервере, позволяя клиенту эффективно проверять актуальность кэшированных данных. При повторном запросе браузер или приложение отправляет сохранённый ETag, и сервер сравнивает его с текущим: при совпадении возвращается статус 304 Not Modified без тела ответа. По данным RFC 7232 (IETF, 2014), условные запросы с ETag сокращают объём передаваемых данных до 95% для часто запрашиваемых ресурсов. Это делает заголовок критически важным для производительности мобильных приложений.
Главное
ETag (Entity Tag) — это HTTP-заголовок ответа, содержащий уникальный идентификатор конкретной версии ресурса. Сервер вычисляет ETag на основе содержимого файла, его метаданных или номера ревизии и передаёт клиенту в ответе на GET-запрос. Клиент сохраняет этот идентификатор и при последующих запросах к тому же ресурсу отправляет его в заголовке If-None-Match. Если ресурс не изменился, сервер отвечает 304 Not Modified, и клиент использует свою кэшированную копию.
Формат ETag определён в RFC 7232 как строка в кавычках: "33a64df551425fcc55e4d42a148795d9f25f89d4". Значение может быть хешем SHA-1 содержимого файла, номером версии инкрементальным, комбинацией inode-номер-время для статических файлов или произвольным токеном, который генерирует сервер. Единственное требование — значение должно меняться при любом изменении ресурса и не меняться, если ресурс остался прежним.
ETag относится к механизмам условных запросов (conditional requests) — одной из базовых optimisations HTTP-протокола. В отличие от безусловных запросов, где сервер всегда возвращает полный ответ, условный запрос позволяет клиенту проверить актуальность кэша без повторной загрузки данных. По данным HTTP Archive (2025), около 40% всех HTTP-ответов — это 304 Not Modified благодаря корректной настройке ETag и Last-Modified.
ETag используется в REST API для оптимизации загрузки коллекций данных — если список объектов не изменился, клиент получает 304 без пересылки всего JSON. В статических файлах (CSS, JS, изображения) ETag позволяет CDN и браузерам эффективно проверять актуальность кэша. В мобильных приложениях ETag критичен для фоновой синхронизации: приложение проверяет, изменились ли данные на сервере, и загружает обновления только при необходимости. Это экономит трафик и батарею устройства.
Полный цикл работы ETag состоит из четырёх шагов. Сервер генерирует ETag при первом запросе и возвращает его в заголовке ответа. Клиент сохраняет ETag вместе с кэшированным ресурсом. При повторном запросе клиент отправляет заголовок If-None-Match со значением сохранённого ETag. Сервер сравнивает полученное значение с текущим ETag ресурса: при совпадении возвращает 304 Not Modified с пустым телом, при несовпадении — 200 OK с новым ресурсом и новым ETag.
// Запрос клиента с If-None-Match
GET /api/users HTTP/1.1
Host: example.com
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
// Ответ сервера — ресурс не изменился
HTTP/1.1 304 Not Modified
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
В мобильном приложении этот цикл может быть реализован через HTTP-клиент с поддержкой кэширования. OkHttp, например, автоматически управляет ETag через CacheInterceptor: он сохраняет ETag ответа и при повторном запросе добавляет If-None-Match. При получении 304 OkHttp возвращает закэшированные данные. OkHttp поддерживает ETag без дополнительной настройки — достаточно включить кэш через OkHttpClient.Builder.cache().
Сервер может вычислять ETag разными способами: через MD5- или SHA-хеш содержимого, через номер ревизии из базы данных (например, updated_at из MySQL), через комбинацию inode + mtime + size для статических файлов (Nginx генерирует ETag именно так). Для динамических API наиболее надёжен хеш контента: если JSON-ответ изменил хотя бы одно поле, ETag изменится. Однако вычисление хеша при каждом запросе нагружает CPU — для высоконагруженных систем лучше использовать инкрементальный номер версии.
RFC 7232 определяет два типа ETag: сильные (strong) и слабые (weak). Сильный ETag означает, что два представления ресурса идентичны байт-в-байт — ни один бит не отличается. Слабый ETag (префикс W/) гарантирует только семантическую эквивалентность: содержимое может различаться на уровне сериализации (пробелы, порядок полей JSON), но данные для клиента считаются одинаковыми. Слабые ETag помечаются префиксом W/, например W/"1a2b3c".
Выбор типа ETag зависит от требований к точности сравнения. Для статических файлов (CSS, JS, изображения) предпочтительны сильные ETag — если файл изменился, клиент должен получить новую версию. Для динамических API, где один и тот же JSON может быть сериализован с разным порядком полей или форматированием, слабые ETag дают больше гибкости: сервер генерирует ETag на основе бизнес-данных, а не строкового представления.
| Тип ETag | Формат | Гарантия | Применение |
|---|---|---|---|
| Strong (сильный) | "хeш" | Байт-в-байт идентичность | Статические файлы, бинарные ресурсы |
| Weak (слабый) | W/"хeш" | Семантическая эквивалентность | JSON API, динамические страницы |
Ограничение слабых ETag: они не могут использоваться с диапазонными запросами (Range requests). Если клиент запрашивает часть файла, сервер должен вернуть сильный ETag, чтобы гарантировать, что фрагмент соответствует полному ресурсу. Слабые ETag не обеспечивают такой гарантии. В остальных сценариях слабые ETag безопасны и рекомендованы для API.
ETag и Last-Modified — два HTTP-заголовка для условных запросов, которые часто используются вместе. Last-Modified указывает дату последнего изменения ресурса и работает с заголовком If-Modified-Since. ETag предоставляет уникальный идентификатор версии и работает с If-None-Match. Каждый имеет свои преимущества и ограничения, а комбинация даёт максимальную эффективность кэширования.
Last-Modified проще в реализации — сервер автоматически получает дату из файловой системы или обновляет поле updated_at в базе. Однако дата имеет точность до секунды, что недостаточно для ресурсов, изменяющихся несколько раз в секунду. Кроме того, Last-Modified не различает разные состояния: если файл перезаписан той же версией, дата изменилась, а контент — нет, клиент перезагрузит идентичные данные.
ETag точнее: он меняется только при реальном изменении контента. Если сервер восстановил предыдущую версию из бекапа, ETag изменится. Если файл перезаписан с теми же данными — ETag останется прежним, и клиент не будет перезагружать. Совместное использование рекомендовано HTTP-спецификацией: сервер возвращает оба заголовка, клиент отправляет If-None-Match и If-Modified-Since одновременно. Если хотя бы один заголовок показывает изменение — сервер возвращает новый ресурс.
По спецификации, ETag имеет приоритет над Last-Modified. Если сервер получил If-None-Match, он должен проверять только ETag, игнорируя If-Modified-Since. Это предотвращает race condition: если ресурс изменился между отправкой клиентом Last-Modified и проверкой на сервере, ETag будет более свежим индикатором. На практике серверы обычно проверяют оба заголовка, но при несовпадении результатов побеждает ETag.
Настройка ETag зависит от типа сервера. Nginx генерирует ETag для статических файлов автоматически на основе inode, mtime и размера. Apache использует механизм FileETag. Для динамических приложений на Node.js, PHP, Python, Ruby ETag нужно генерировать программно — через хеш ответа, номер версии данных или комбинацию параметров запроса.
func etagMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter,
r *http.Request) {
// Генерация ETag на основе данных
etag := generateETag(r.URL.Path)
w.Header().Set("ETag", etag)
// Проверка If-None-Match
if r.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return
}
next.ServeHTTP(w, r)
})
}
Middleware на Go перехватывает запрос, генерирует ETag для запрошенного URL (например, вычисляет хеш данных из кэша или БД) и устанавливает заголовок ответа. Если клиент отправил If-None-Match и он совпадает с текущим ETag, сервер возвращает 304 Not Modified сразу, без вызова основного обработчика. В продакшене стоит добавить кэширование вычисленных ETag по URL и параметрам для снижения нагрузки на сервер.
В многосерверной конфигурации (round-robin или anycast) ETag должен быть одинаковым на всех нодах для одного и того же ресурса. Если ETag генерируется на основе inode файла, а сайт развёрнут на нескольких серверах, значения будут различаться. Решение — использовать хеш содержимого или централизованное хранение версий (Redis, etcd). Вторая проблема — gzip-сжатие: Nginx меняет ETag при включении сжатия, что может вызвать избыточные 304. Требуется настройка gzip_vary on для синхронизации ETag со сжатым контентом.
Часто задаваемые вопросы
Да, если сервер явно этого не предотвратил. ETag не обязан быть глобально уникальным — он уникален в пределах конкретного URL. Для статических файлов коллизии маловероятны при использовании SHA-хеша, но для самодельных генераторов возможны дубликаты.
ETag наиболее эффективен для ресурсов, которые запрашиваются многократно и редко меняются: статика, API-списки, конфигурации. Для уникальных страниц, которые загружаются один раз (например, страница подтверждения заказа), ETag не даёт преимущества.
CDN учитывает ETag в origin-запросах для проверки актуальности кэша. Если ETag ресурса на origin изменился, CDN загружает новую версию. Cloudflare и Fastly поддерживают ETag как стандартный механизм инвалидации кэша на уровне origin.
RFC 7232 не ограничивает длину ETag, но серверы и прокси могут урезать или игнорировать слишком длинные значения. Рекомендуется использовать хеш длиной 20–40 символов или комбинацию идентификатора версии с контрольной суммой.
Это не взаимоисключающие механизмы. Cache-Control определяет политику кэширования (сколько хранить, кому разрешено), а ETag — механизм валидации закэшированного ресурса. Оптимальная конфигурация включает оба заголовка вместе.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также