If-Range 是续传前的最后一道检查:我手上这份还是那一版吗。
匹配就给片段(206),不匹配就给全量(200)——不是报错。
上一节是缓存的条件,这一节是下载的条件。它要解决的竞态很具体:续传时文件被换了。
一、竞态长什么样
下载一个 2 GB 的文件,下到一半断了。
客户端记着「我已经有前 1000 字节」,于是发 Range: bytes=1000- 续传。
可如果这期间服务器上的文件被替换了(发了新版本),客户端拿到的后半段对不上前半段,
拼起来是一份彻底损坏的文件。而且从字节上看不出问题——长度对得上,只是内容是两份不同的文件。
If-Range 就是让客户端先说一句「我记得那版是 v7」。服务端比对:还是 v7,就给片段;
不是 v7,就从头给一份完整的。
二、降级而不是报错
这一点是它和另外四个条件字段最大的不同。
If-Match 不匹配回 412,If-None-Match 不匹配回 200(读场景),
而 If-Range 不匹配直接退回 200 全量。
为什么这么设计?因为对下载来说,能拿到完整文件比报错有用。
报 412 的话客户端还得再来一次,不如这一次就把全量给出去。
Demo 第三组用过期 ETag,结果就是 200 加 2048 字节全量,
Content-Range 那一栏是空的——因为它不是片段响应了。
三、它能用两种凭证
If-Range 的值可以是 ETag,也可以是 HTTP 日期。
用日期时它是强比较:必须与 Last-Modified 完全相等,
不是「不晚于」——这一点和 If-Modified-Since 完全不同。
为什么这么严?因为续传要求字节级一致,「不晚于」只说明没改过,不能说明是同一次生成的内容。
顺带一句:这也是为什么 If-Range 不能和弱 ETag 一起用——弱标签只保证语义等价,不保证字节一致,
而续传恰恰要求字节一致。
四、动手 Demo:三种组合:只有 Range、配对 If-Range、过期 If-Range
服务端有一份 2048 字节的资源,当前 ETag 是 v7。 客户端分别只给 Range、给 Range 加正确 If-Range、给 Range 加过期 If-Range。
if rng and ir:
if ir != ETAG:
# 副本变了,不能给片段,改成给全量
return reply(u'HTTP/1.1 200 OK', [(u'ETag', ETAG)], DATA)
seg = DATA[start:end + 1]
return reply(u'HTTP/1.1 206 Partial Content',
[(u'Content-Range',
u'bytes %d-%d/%d' % (start, end, len(DATA)))], seg)
实跑输出:
--------------------------------------------------------------------
If-Range:续传前先确认「还是那一份吗」
--------------------------------------------------------------------
资源总长 2048 字节
当前 ETag "v7"
【只给 Range】
If-Range (没发)
结果 HTTP/1.1 206 Partial Content | 正文 100 字节
Content-Range bytes 1000-1099/2048
首尾字节 232 … 75
【Range 加正确的 If-Range】
If-Range "v7"
结果 HTTP/1.1 206 Partial Content | 正文 100 字节
Content-Range bytes 1000-1099/2048
首尾字节 232 … 75
【Range 加过期的 If-Range】
If-Range "v3"
结果 HTTP/1.1 200 OK | 正文 2048 字节
Content-Range (没有)
首尾字节 0 … 255
这个字段解决的是续传时的一个竞态:下载到一半断了,
客户端拿着「我要第 1000 到 1099 字节」来续,
可如果这期间文件被换过了,拼起来就是一份乱掉的文件。
`If-Range` 就是让客户端先说「我记得那版是 v7」,
服务端一比对:还是 v7 就给片段(206),不是 v7 就给全量(200)。
注意它**不是**条件请求那种 412,而是降级成一次完整下载——
因为对下载来说,能拿到完整文件比报错有用。
只给 Range: bytes=1000-1099:206,正文 100 字节,Content-Range: bytes 1000-1099/2048,
首尾字节是 232 和 75。
加上正确的 If-Range: "v7":还是 206,100 字节,首尾一样。说明凭证匹配,正常续传。
加上过期的 If-Range: "v3":200,2048 字节全量,Content-Range 那一栏是「没有」。
服务端判断副本变了,直接放弃续传,给一份完整的。首尾字节变成 0 和 255。
注意首尾那对数字:bytes(range(256)) 重复八遍,所以第 1000 字节的值是 232(1000 除 256 余 232),
第 1099 字节是 75。这两个数能验证片段确实是从 1000 开始切的。
30 秒实验
- 把
If-Range换成日期,并且填一个比Last-Modified晚但不是同一个值,看会不会 206。 - 把
If-Range换成W/"v7",看服务端该不该接受。 - 想一想:如果客户端拿到 200 全量,它该不该覆盖已经下了一半的临时文件?
常见误区
- 「
If-Range不匹配回 412」——回 200 全量,是降级不是报错。 - 「用日期时是「不晚于」比较」——是强比较,必须完全相等。
- 「
If-Range和If-None-Match可以一起用」——规范只允许带一个条件,If-Range单独成立。
自测三问(点开对答案)
1. `If-Range` 解决什么竞态? —— 续传期间资源被替换,导致拼起来的文件前后不是同一份。 2. 条件不满足时回什么? —— 回 200 加全量,而不是 412 之类的错误。 3. 为什么它不能用弱 ETag? —— 续传要求字节级一致,弱标签只保证语义等价。续传说完了。还有一个条件字段,管的是写场景的另一面。
- ☐ (本节完,下一节 6.4.13 If-Unmodified-Since)