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)机制 — HTTP协议的基础优化之一。与无条件请求(服务器始终返回完整响应)不同,条件请求允许客户端检查缓存的最新状态而无需重新下载数据。根据HTTP Archive (2025)的数据,由于ETag和Last-Modified的正确配置,约40%的所有HTTP响应都是304 Not 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通过CacheInterceptor自动管理ETag:保存响应的ETag,并在重复请求时添加If-None-Match。接收到304后,OkHttp返回缓存的数据。OkHttp支持ETag无需额外配置 — 只需通过OkHttpClient.Builder.cache()启用缓存即可。
服务器可以通过多种方式计算ETag:通过内容的MD5或SHA散列,通过数据库的修订号(例如MySQL的updated_at),通过静态文件的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(强) | "散列" | 字节完全相同 | 静态文件、二进制资源 |
| Weak(弱) | W/"散列" | 语义等价 | 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根据inode、mtime和大小自动为静态文件生成ETag。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)
})
}
Go中的中间件拦截请求,为所请求的URL生成ETag(例如,计算缓存或数据库中数据的散列),并设置响应头。如果客户端发送了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在origin请求中考虑ETag以检查缓存的最新状态。如果origin上资源的ETag已变更,CDN下载新版本。Cloudflare和Fastly将ETag作为在origin层面无效缓存的标准机制进行支持。
RFC 7232没有限制ETag的长度,但服务器和代理可能截断或忽略过长的值。建议使用长度为20–40个字符的散列,或版本标识符与校验和的组合。
这两者并非互斥机制。Cache-Control确定缓存策略(存储多久、允许谁使用),而ETag是对缓存资源的验证机制。最优配置包含两个头部的联合使用。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。