Conditional GET(条件 GET 请求)— HTTP 机制,允许客户端在完全加载之前检查缓存资源的有效性。客户端发送带有 If-None-Match(包含 ETag)或 If-Modified-Since(包含日期)头的 GET 请求,如果资源未更改,服务器返回 304 Not Modified,不带响应体。根据 MDN Web Docs,2025,条件请求可减少服务器和客户端的网络流量。304 Not Modified — 移动应用高效同步的关键 HTTP 状态码。
要点
Conditional GET — 是一个 GET 请求,包含一个或多个条件头,服务器根据这些条件头决定返回完整响应还是仅返回 304 Not Modified 状态码。主要目的是如果资源自上次请求以来未更改,则避免传输响应体。这是 HTTP 缓存的基本机制,在 RFC 7232 规范中定义。
对于移动应用,Conditional GET 是优化网络流量最有效的方法之一。典型场景:打开应用时,客户端发送一系列条件 GET 请求以加载信息流、个人资料和设置。如果数据未更改,应用收到 304 并使用本地副本。这需要毫秒而非秒,且不消耗移动流量。
根据 Google Web Fundamentals(2025),在移动应用中实施条件 GET 请求可将重复访问的平均加载时间减少 40–60%,并将更新不频繁页面的流量消耗降低 70–90%。在慢速连接(3G、Edge)上效果尤其明显,每个字节都至关重要。
该过程包含三个步骤。第一步 — 客户端发送普通 GET 请求,服务器返回资源及缓存头(ETag、Last-Modified)。第二步 — 客户端将资源及其验证器保存在本地。第三步 — 重复请求时,客户端发送带有 If-None-Match(用于 ETag)和/或 If-Modified-Since(用于 Last-Modified)的 GET 请求。服务器检查验证器,如果资源未更改则响应 304,否则返回 200 及新数据。
当两个头都存在时,服务器使用 ETag 优先于 Last-Modified。这是因为 ETag 提供更精确的验证 — 内容哈希随任何更改而变化,而 Last-Modified 的分辨率为一秒。如果 ETag 匹配,服务器立即返回 304,而不检查 Last-Modified。
请求序列中 Conditional GET 完整周期的示例:
// 第 1 步:首次请求 — 获取数据和 ETag
GET /api/profile
Response: 200 OK
ETag: "33a64df551425fcc55e"
Body: { "name": "Alice" }
// 第 2 步:重复请求 — 使用 If-None-Match
GET /api/profile
If-None-Match: "33a64df551425fcc55e"
Response: 304 Not Modified
// 响应体不存在 — 使用本地副本
在第二个请求中,服务器将 If-None-Match 中的 ETag 与资源的当前哈希进行比较。如果匹配,则返回不带体的 304 — 客户端继续使用缓存数据。这就是 Conditional GET 的本质:最小流量,最大数据新鲜度。
普通 GET 请求始终返回完整的 200 OK 响应及响应体。即使资源未更改,服务器也会重新传输所有数据。这对于小资源或罕见请求可以接受,但对于每次启动发出数百个请求的移动应用,这种方法会导致流量和电池过度消耗。
Conditional GET 以头的形式增加额外开销(通常每个请求 50–200 字节),但在 304 响应中节省千字节和兆字节。资源越大,条件请求越有利。对于 10 KB 及以上的图像、数据列表和 JSON 文档,Conditional GET 从第一次重复请求即可收回成本。
两种方法的对比:
| 参数 | 普通 GET | Conditional GET |
|---|---|---|
| 流量(无更改) | 完整响应 | 仅头(~200 字节) |
| 延迟 | 完全加载 | 毫秒 (304) |
| 服务器负载 | 生成 + 传输 | 仅 ETag 检查 |
| 实现复杂度 | 最低 | 需要存储 ETag |
| 大数据效率 | 低 | 高 |
我们来看一个完整的实现 — 使用 OkHttp 和 Room 存储 ETag 在 Kotlin 中实现 Conditional GET。任务列表应用从服务器加载任务,并使用条件请求最小化流量。ETag 存储在本地数据库中以在会话之间保持。
Kotlin 中带有 Conditional GET 的仓库:
class TaskRepository(
private val api: TaskApi,
private val etagDao: EtagDao
) {
suspend fun getTasks(): List<Task> {
val savedEtag = etagDao.getEtag("tasks")
val response = api.fetchTasks(
ifNoneMatch = savedEtag
)
return when (response.code()) {
304 -> taskDao.getAll() // 从本地缓存
200 -> {
response.header("ETag")?.let {
etagDao.saveEtag("tasks", it)
}
val tasks = response.body() ?: emptyList()
taskDao.replaceAll(tasks)
tasks
}
else -> throw Exception(
"Sync failed: ${response.code()}")
}
}
}
TaskRepository 检查响应码:304 表示无更改,数据从 Room 的本地缓存返回。200 时保存新 ETag 并更新本地数据库中的任务。此模式是通过 REST API 同步的移动应用的标准。
Conditional GET 广泛用于 移动应用中优化数据同步。主要场景:加载新闻信息流(Twitter、Instagram 定期使用 If-None-Match 查询 API)、更新用户资料、加载通知列表和同步任务。在每种情况下,应用都可以检查数据的有效性而无需重新加载。
对于 离线优先应用,Conditional GET 作为同步的第一阶段。应用首先为自上次同步以来所有本地修改过的资源发送条件 GET 请求。304 的资源不需要加载。之后,应用为本地更改发送 PUT/POST。这种两阶段方法确保了最小的流量消耗。
结合 冲突解决,Conditional GET 允许有效检测冲突。如果客户端收到带有新数据的 200(资源已更改),但客户端有未发送的本地更改 — 则记录冲突。客户端可以应用 LWW(本地更改丢失)或启动合并策略以合并本地和远程更改。根据 Meta Engineering Blog(2025),在 Messenger 中实施 Conditional GET 将同步的平均流量消耗降低了 73%。
常见问题解答
Conditional GET — 带有条件头(If-None-Match、If-Modified-Since)的 HTTP GET 请求。如果资源未更改,服务器返回 304 Not Modified;否则返回 200 及新数据。这是一种高效的缓存机制。
普通 GET 始终返回带有体的完整响应。Conditional GET 添加版本检查头(ETag、日期)。如果数据未更改,服务器以 304 无体响应,节省流量和加载时间。
为了高效缓存,将每个服务器响应中的 ETag 和 Last-Modified 保存在本地数据库中。下次请求时,在 If-None-Match 和 If-Modified-Since 头中发送它们。收到 304 时使用本地缓存中的数据。
在 304 响应中,服务器不传输响应体 — 仅头部(约 200 字节)。对于 50 KB 的资源,这意味着节省 99.6% 的流量。对于每天同步 50 次的应用,每月节省可达数十兆字节。
是的,这是增量同步的标准方法。客户端通过 Conditional GET 检查每个资源的有效性,仅加载已更改的资源并发送本地更改。这种方法用于 Twitter、Instagram、Telegram 及大多数现代 API。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。