先记住这个答案
客户端先GET获取资源的ETag或最后修改时间,更新请求带上If-Match或If-Unmodified-Since,服务器在写入前校验该标识是否仍匹配;若不匹配说明资源已被他人修改,返回412。这样后提交的写请求不会覆盖先提交的结果,客户端需重取数据再重试。
- 写前校验失败返回412,阻断旧版本覆盖。
If-Match要求强ETag,秒级时间戳可能漏判。- GET时携带验证器,PUT时带回,机制需前后一致。
条件头在写请求中的判定过程
If-Match携带一个或多个ETag值,用强比较;当前资源的ETag与任一值相等才放行。If-Unmodified-Since携带HTTP日期,要求资源最后修改时间不晚于该时间。若同时携带两者,服务器仅校验If-Match,不匹配即返回412;若仅携带If-Unmodified-Since,当最后修改时间不晚于该时间时才继续,否则中止并返回412。
客户端A、B先后GET同一文档,各自获得相同验证器。A先提交PUT,请求带If-Match,服务器校验匹配并更新资源与ETag;B后提交同样条件,服务器比对发现已不匹配,返回412拒绝。于是只有A的更新生效,B获知冲突后可重新读取最新版再决策。
编辑文档的并发冲突
文档/doc/1当前ETag为v2。两个客户端同时读取并编辑。客户端1提交PUT,带If-Match: v2,服务器确认当前仍是v2,执行更新并设新ETag为v3。客户端2随后提交相同If-Match: v2,服务器发现当前已经是v3,返回412并附最新ETag。
客户端2收到412后不立即重试,而是提示用户有他人修改,可选择重新加载或合并。服务器在写入前做条件检查,保证同一版本只能成功提交一次,彻底杜绝后写覆盖先写。若不使用条件头,两个请求都会成功,后者的数据会覆盖前者。
适用边界与易失效条件
If-Unmodified-Since依赖服务器Last-Modified时间,精度仅到秒。两次修改若发生在同一秒,后一次不会触发412,造成静默覆盖。若服务器未提供该时间或客户端缓存了旧值,条件也失效,需依赖ETag才能可靠检测。
If-Match要求强验证器,弱ETag(W/...)不可用。若服务器只提供弱ETag或验证器不唯一,If-Match将无法区分两次修改。此时应调整ETag生成策略,确保内容变化必然产生新强ETag,或在响应头显式声明允许的验证方式。
容易答错的地方
- 认为If-Match可无条件代替If-Unmodified-Since
- 两者机制不同:If-Match基于ETag精确判断内容变没变,If-Unmodified-Since只比较秒级时间,同秒内第二次修改无法察觉。高并发写必须用If-Match,不能盲目替换。
- 在创建资源时误用If-Match
- 新建资源没有现存的ETag,If-Match要求资源已存在,否则会失败。创建应使用If-None-Match: *确保不存在才创建,避免重复创建或覆盖已有数据。
面试官还会怎么问?
服务器收到If-Match和If-Unmodified-Since都返回412时,客户端应该怎么做?
412表示预条件失败,说明资源已被他人修改。客户端应放弃本次提交,重新GET获取最新版本,合并改动后再带新ETag重试,避免盲目覆盖。
If-Match允许使用*通配符吗?
允许。If-Match: *要求资源已存在,否则条件失败;常用于更新已存在资源,不用于新建。防止新建覆盖(即资源已存在时创建)应使用If-None-Match: *。具体见RFC9110。
多主复制或分布式系统中,HTTP条件请求能否解决跨节点并发?
不能。HTTP条件请求依赖单个服务器的一致状态,分布式需借助版本向量或事务机制。若节点间状态不同步,条件校验可能在本地通过却全局冲突。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。