If-Match 是「只有内容还是那一版,你才能改」。不匹配就回 412。
它解决的是两个人同时改同一份东西的问题。
从这一节开始是 5 个条件请求字段。
它们分成两组:If-Match/If-Unmodified-Since 管写,
If-None-Match/If-Modified-Since 管读,If-Range 管续传。
一、丢失更新:一个必须解决的问题
甲和乙同时打开了同一份文档。甲先保存,服务端内容变成 A。
乙后保存,他手里还是打开时那份,一提交,A 就被覆盖了。甲改的东西凭空消失,而且没有任何人收到提示。
这叫丢失更新。
If-Match 就是解法:客户端提交时带上「我记得这版是 v1」,服务端一比对发现已经是 v2,
就回 412 Precondition Failed,让乙先刷新再说。
Demo 里甲成功(199 到 200,版本从 v1 到 v2),乙带着过期的 v1 来,收到 412,服务端当前版本是 v2。
二、If-Match: * 的特殊含义
星号表示「不管哪一版,只要资源存在就允许改」。
它对应的语义是「这个操作要求资源必须存在」——如果资源不存在,回 412(而不是 404)。
所以它常被用来表达「这是更新,不是创建」。
反过来,If-None-Match: * 表达的是「这是创建,别覆盖已有的」。
Demo 第三组用星号,成功把版本推到 v3。注意这时当前版本已经是 v2,但因为用了星号,不做版本比对。
三、必须是强比较
If-Match 用的是强比较:字节必须完全一致。所以弱 ETag(带 W/ 前缀的)不能用在这个字段里。
为什么?因为弱 ETag 只保证「语义等价」,允许字节有细微差异(比如压缩过的版本)。
而 If-Match 要保护的是「我基于哪一份字节改的」,字节不同就不能算同一份。
这一点很容易踩:服务端回一个 ETag: W/"v1",客户端拿去放在 If-Match 里,结果永远匹配不上,
于是永远 412。要么服务端别发弱标签,要么客户端用 If-Unmodified-Since 退而求其次。
四、动手 Demo:三个人改同一份文档
服务端维护一个文档和它的 ETag。 先读一次拿到当前版本,然后分别让甲带正确 ETag、乙带过期 ETag、丙带星号来 PUT,看各自的结果。
if method != u'PUT':
return reply(u'HTTP/1.1 200 OK', [(u'ETag', st[u'etag'])],
st[u'doc'].encode(u'utf-8'))
im = hd.get(u'if-match', u'')
if im and im != u'*' and im != st[u'etag']:
return reply(u'HTTP/1.1 412 Precondition Failed',
[(u'ETag', st[u'etag'])], b'version mismatch')
st[u'doc'] = body.decode(u'utf-8', u'replace')
n = int(st[u'etag'][2:-1]) + 1
st[u'etag'] = u'"v%d"' % n
实跑输出:
--------------------------------------------------------------------
If-Match:只有内容还是那一版,我才改
--------------------------------------------------------------------
先读一次 HTTP/1.1 200 OK | ETag="v1" | 正文 第一版内容
【甲拿着正确的 ETag 来改】
发出去的 If-Match "v1"
结果 HTTP/1.1 200 OK | 已更新为第 2 版
【乙拿着过期的 ETag 来改】
发出去的 If-Match "v1"
结果 HTTP/1.1 412 Precondition Failed | 版本不匹配,没改
服务端当前版本 "v2"
【丙用 If-Match: * 表示「不管哪版都改」】
结果 HTTP/1.1 200 OK | 已更新为第 3 版
这个字段解决的是「丢失更新」问题:两个人同时打开同一份文档,
甲先保存,乙后保存,如果不管版本,乙的保存会把甲的覆盖掉。
加上 If-Match,乙就会收到 412,前端可以提示「有人改过了,请刷新」。
`If-Match: *` 是特例:只要资源存在就允许改,不存在回 412。
注意它是**强比较**——字节必须完全一致,弱 ETag(带 `W/`)不能用在 If-Match 里。
先读一次:200,ETag="v1",正文「第一版内容」。
甲带 If-Match: "v1":200,正文「已更新为第 2 版」。版本真的推进了。
乙带 If-Match: "v1":412,正文「版本不匹配,没改」,服务端当前版本是 "v2"。
甲改的东西被保住了。
丙带 If-Match: *:200,正文「已更新为第 3 版」。星号不做版本比对。
30 秒实验
- 把甲的 ETag 改成
W/"v1"(弱标签),看会不会匹配失败。 - 删掉资源再用
If-Match: *试,看是不是回 412 而不是 404。 - 想一想:如果两个客户端几乎同时提交,服务端该怎么保证只有一个成功?
常见误区
- 「
If-Match不匹配回 304」——是 412,304 是读场景用的。 - 「
If-Match: *表示不管有没有都改」——资源不存在时回 412。 - 「弱 ETag 也能用在 If-Match 里」——必须强比较,弱标签会被拒。
自测三问(点开对答案)
1. `If-Match` 解决什么问题? —— 丢失更新:防止后提交的人覆盖掉别人已经保存的修改。 2. 不匹配时回什么状态码? —— 412 Precondition Failed。 3. 为什么弱 ETag 不能用在 `If-Match` 里? —— 因为它是强比较,要求字节完全一致;弱标签只保证语义等价。写场景的条件字段说完了,下一个是同族的读场景字段。
- ☐ (本节完,下一节 6.4.10 If-Modified-Since)