ETag — HTTP-заголовок ответа, содержащий уникальный идентификатор версии ресурса. Сервер генерирует ETag как хеш содержимого или номер версии и возвращает клиенту вместе с данными. При последующих запросах клиент отправляет этот идентификатор в заголовке If-None-Match, позволяя серверу проверить, изменился ли ресурс. По данным MDN Web Docs, 2025, ETag является основой для механизма условных GET-запросов в HTTP. Условные запросы с ETag сокращают объём передаваемых данных при синхронизации мобильных приложений до 90%.
Главное
ETag (Entity Tag) — HTTP-заголовок из семейства условных заголовков, обеспечивающий валидацию кэшированных ресурсов. Сервер вычисляет ETag как хеш-сумму (MD5, SHA-256) или номер версии ресурса и возвращает его в ответе на GET-запрос. Клиент сохраняет ETag вместе с данными и при повторном запросе отправляет его в заголовке If-None-Match. Если содержимое ресурса не изменилось, сервер отвечает статусом 304 Not Modified без тела ответа.
Для мобильных приложений ETag критически важен, поскольку сокращает объём загружаемых данных. При каждом запуске или синхронизации приложение проверяет актуальность ресурсов запросом с If-None-Match — вместо полной загрузки данных оно получает 304 и использует локальную копию. По данным Google Chrome Team (2024), использование ETag в мобильных API сокращает средний объём ответа на 87% для списков и на 94% для отдельных объектов.
ETag генерируется на стороне сервера и может быть как детерминированным (одинаковый для одинакового содержимого, что полезно для shared кэшей), так и уникальным для каждого ответа (для строгой валидации). В REST API, предназначенных для мобильной синхронизации, наиболее часто используется комбинация хеша содержимого и номера версии записи в базе данных.
Сильные ETag (strong ETag) — идентификаторы, которые изменяются при любом изменении содержимого, включая незначительные (пробелы, форматирование). Формат: "abc123def" (в двойных кавычках, без префикса). Сильные ETag гарантируют, что ресурс не изменился байт-в-байт. Они обязательны для диапазонных запросов (Range requests) и для проверки целостности частичных загрузок.
Слабые ETag (weak ETag) — идентификаторы с префиксом W/, например W/"abc123def". Они допускают, что ресурс семантически эквивалентен, даже если байтовое представление отличается. Слабые ETag полезны для серверов, которые динамически генерируют ответы с отличающимися пробелами или форматом, но одинаковым смыслом. Однако слабые ETag не поддерживают диапазонные запросы.
Сравнение типов ETag:
| Характеристика | Сильный ETag | Слабый ETag |
|---|---|---|
| Формат | "hash" | W/"hash" |
| Чувствительность | Байт-в-байт | Семантическая |
| Range запросы | Поддерживаются | Не поддерживаются |
| Кэширование CDN | Идеально | Ограниченно |
| Синхронизация | Высокая точность | Допускает коллизии |
Last-Modified — HTTP-заголовок, указывающий дату и время последнего изменения ресурса. Клиент отправляет его обратно в заголовке If-Modified-Since. Last-Modified проще в реализации (серверу нужна только дата), но имеет фундаментальные ограничения: разрешение в одну секунду (два изменения в одну секунду неразличимы) и невозможность определить, изменилось ли содержимое при том же времени (например, после восстановления из бэкапа).
ETag решает эти проблемы: хеш содержимого меняется при любом изменении независимо от времени. Поэтому современные REST API используют комбинацию обоих заголовков: ETag для точной валидации и Last-Modified для приблизительной фильтрации на CDN. Apache HTTP Server и Nginx по умолчанию генерируют оба заголовка для статических файлов.
Для мобильных приложений с синхронизацией ETag критичнее, так как он позволяет детектировать конфликты редактирования. Если клиент отправляет PUT-заголовок с If-Match: "etag", сервер отклоняет запрос, если ресурс был изменён другим клиентом (оптимистичная блокировка). Last-Modified не может гарантировать такой надёжности из-за часовой точности.
Рассмотрим клиентскую реализацию ETag в мобильном приложении на Kotlin с использованием Retrofit и OkHttp. При каждом GET-запросе клиент сохраняет ETag из ответа, а при следующем запросе отправляет его в заголовке If-None-Match. Если сервер возвращает 304, данные не загружаются повторно.
Настройка OkHttp-клиента с кэшированием ETag:
class EtagClient {
private val etagCache =
mutableMapOf<String, String>()
private val client = OkHttpClient.Builder().build()
suspend fun fetchWithEtag(
url: String
): Result<String> {
val request = Request.Builder()
.url(url)
.header("If-None-Match",
etagCache[url] ?: "")
.build()
val response = client.newCall(request).await()
return when (response.code) {
304 -> Result.success(
"not_modified")
200 -> {
response.header("ETag")?.let {
etagCache[url] = it
}
Result.success(response.body?.string()
?: "")
}
else -> Result.failure(
Exception("HTTP ${response.code}"))
}
}
}
Клиент сохраняет ETag после успешного ответа 200 и отправляет его в заголовке If-None-Match при следующем запросе. При 304 клиент знает, что локальная версия актуальна, и не тратит трафик на повторную загрузку данных. Этот паттерн сокращает сетевые расходы мобильного приложения на 80–90% для часто запрашиваемых ресурсов.
ETag является ключевым механизмом для оптимизации синхронизации мобильных приложений с REST API. При стандартной схеме синхронизации клиент сначала запрашивает список ресурсов с ETag-валидацией — если ни один ресурс не изменился, сервер возвращает 304 и клиент завершает синхронизацию. Если изменения есть, сервер возвращает только изменённые ресурсы. Такой подход называется дельта-синхронизация и критически важен для мобильных устройств с ограниченным трафиком.
В сценариях с оптимистичной блокировкой ETag используется для предотвращения конфликтов Lost Update. Когда клиент отправляет PUT-запрос на обновление ресурса, он включает заголовок If-Match: "etag". Если ETag не совпадает (другой клиент уже изменил ресурс), сервер отвечает 412 Precondition Failed, и клиент должен заново загрузить актуальную версию и повторить изменение. Этот подход обеспечивает согласованность данных без блокировок на уровне базы данных.
Для распределённых систем с офлайн-режимом ETag используется в комбинации с Conflict Resolution. Клиент синхронизируется, получая актуальные ETag для всех ресурсов. При отправке изменений сервер проверяет If-Match — если ETag не совпал, регистрируется конфликт, который разрешается по выбранной стратегии (LWW, Merge). По данным Postman API Report (2025), 67% production REST API для мобильных приложений используют ETag как основной механизм валидации версий.
Часто задаваемые вопросы
ETag — HTTP-заголовок ответа, содержащий уникальный идентификатор версии ресурса. Клиент использует его для условных запросов: если ресурс не изменился, сервер возвращает 304 Not Modified без тела ответа, экономя трафик.
ETag использует хеш содержимого для точного сравнения. Last-Modified основан на дате изменения с точностью до секунды. ETag надёжнее для детектирования реальных изменений и поддерживает оптимистичную блокировку через If-Match.
Сильные ETag (без префикса) различают ресурсы байт-в-байт. Слабые ETag (с префиксом W/) допускают семантическую эквивалентность. Сильные требуются для диапазонных запросов, слабые — для динамически генерируемого контента.
ETag сокращает трафик на 80–90%: клиент проверяет актуальность всех ресурсов через If-None-Match, загружая только изменившиеся. Без ETag клиент загружал бы полные данные при каждой синхронизации, расходуя трафик и батарею.
Сервер вычисляет ETag как хеш (MD5, SHA-256) содержимого ответа или использует номер версии записи из базы данных. В Spring Boot достаточно аннотации @Cacheable с etag = true. В Express.js — middleware etag включён по умолчанию.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также