📘 第六章 HTTP首部
📄 一句话概括

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 秒实验

  1. 把甲的 ETag 改成 W/"v1"(弱标签),看会不会匹配失败。
  2. 删掉资源再用 If-Match: * 试,看是不是回 412 而不是 404。
  3. 想一想:如果两个客户端几乎同时提交,服务端该怎么保证只有一个成功?

常见误区

  1. 「If-Match 不匹配回 304」——是 412,304 是读场景用的。
  2. 「If-Match: * 表示不管有没有都改」——资源不存在时回 412。
  3. 「弱 ETag 也能用在 If-Match 里」——必须强比较,弱标签会被拒。
自测三问(点开对答案) 1. `If-Match` 解决什么问题? —— 丢失更新:防止后提交的人覆盖掉别人已经保存的修改。 2. 不匹配时回什么状态码? —— 412 Precondition Failed。 3. 为什么弱 ETag 不能用在 `If-Match` 里? —— 因为它是强比较,要求字节完全一致;弱标签只保证语义等价。

写场景的条件字段说完了,下一个是同族的读场景字段。

相关笔记