Range 要「只要资源的一部分」。三种写法:绝对区间、最后 N 字节、从某处到结尾。
成功回 206,越界回 416 并且必须告诉客户端真实长度。
上一节是认证,这一节是分段。3.5 从报文角度讲过它,这里从请求字段角度讲全。
一、三种写法
| 写法 | 含义 | 结果 |
|---|---|---|
bytes=0-99 |
第 0 到第 99 字节 | 100 字节 |
bytes=-48 |
最后 48 字节 | 48 字节 |
bytes=2000- |
从 2000 到结尾 | 48 字节(总长 2048) |
| 省略的一端表示「到那一头」。 | ||
注意 -48 和 48- 差了十万八千里:前者是「最后 48 字节」,后者是「从第 48 字节到结尾」。 |
||
| 所以数字前的那个减号不是负号,是「省略」的标记。写错一个位置,下载的就完全是另一段内容。 |
二、两种失败:416
越界:bytes=5000-5100 而资源只有 2048 字节,起点就超出了范围,
回 416 Range Not Satisfiable。
倒过来:bytes=100-50,起点大于终点。同样是 416。
416 有一个强制要求:必须带 Content-Range: bytes */总长。
那个星号是字面意思——「起点终点我不知道,但总长是这个数」。客户端据此才能算出合法的区间再试一次。
不带这一行的话,客户端只知道「你这个范围不行」,不知道资源到底多大,也就没法修正。
这是很多手写实现漏掉的细节。
三、它撑起了哪些功能
断点续传:下载中断后从断点继续,不用从头再来。这就是 6.4.12 那个 If-Range 要保护的东西。
多线程下载:把一个文件切成几段,几个连接并行拉,再拼起来。对高延迟链路提升很明显。
视频拖动:播放器跳到 3 分 20 秒,其实就是发一个从那个位置开始的 Range。
没有它,看视频必须先下完整个文件。
所以 Accept-Ranges: bytes 这个响应首部值得注意——服务端用它声明「我支持分段」。
不回它,客户端就该老实下全量。
四、动手 Demo:六种 Range,三种结果
服务端有一份 2048 字节的资源,手工解析 Range 的三种写法,算出合法的区间回 206, 算不出来就回 416 并带上总长。
a, _, b = spec.partition(u'-')
if not a: # bytes=-48 这种,最后 N 字节
n = int(b)
start, end = len(DATA) - n, len(DATA) - 1
elif not b: # bytes=2000- 这种,到结尾
start, end = int(a), len(DATA) - 1
else:
start, end = int(a), int(b)
if start >= len(DATA) or start > end:
return reply(u'HTTP/1.1 416 Range Not Satisfiable',
[(u'Content-Range', u'bytes */%d' % len(DATA))], b'')
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)
实跑输出:
--------------------------------------------------------------------
Range 的四种写法与三种结果
--------------------------------------------------------------------
资源总长 2048 字节
【不要范围】
Range (没发)
结果 HTTP/1.1 200 OK | 正文 2048 字节
Content-Range (没有)
【前 100 字节】
Range bytes=0-99
结果 HTTP/1.1 206 Partial Content | 正文 100 字节
Content-Range bytes 0-99/2048
【最后 48 字节】
Range bytes=-48
结果 HTTP/1.1 206 Partial Content | 正文 48 字节
Content-Range bytes 2000-2047/2048
【从 2000 到结尾】
Range bytes=2000-
结果 HTTP/1.1 206 Partial Content | 正文 48 字节
Content-Range bytes 2000-2047/2048
【越界】
Range bytes=5000-5100
结果 HTTP/1.1 416 Range Not Satisfiable | 正文 0 字节
Content-Range bytes */2048
【倒过来的范围】
Range bytes=100-50
结果 HTTP/1.1 416 Range Not Satisfiable | 正文 0 字节
Content-Range bytes */2048
只取 100 字节时,比全量少传 1948 字节,只花了 4.9%。
三种写法各有含义:`0-99` 是绝对区间,`-48` 是「最后 48 字节」,
`2000-` 是「从 2000 到结尾」。省略的一端表示「到那一头」。
越界和倒过来的范围都回 **416**,并且必须带 `Content-Range: bytes */总长` 告诉客户端真实长度——
否则客户端不知道该怎么改它的请求。
不要范围:200 加 2048 字节全量,没有 Content-Range。
bytes=0-99:206,正文 100 字节,Content-Range: bytes 0-99/2048。
bytes=-48 和 bytes=2000- 结果完全一样:都是 bytes 2000-2047/2048,48 字节。
一个从尾部算,一个从头算,碰巧重合。
bytes=5000-5100:416,正文 0 字节,Content-Range: bytes */2048。
bytes=100-50:同样 416,同样带 bytes */2048。
只取 100 字节时,比全量少传 1948 字节,只花了 4.9%。
30 秒实验
- 发
bytes=0-0,看能不能只取一个字节。 - 发
bytes=2000-9999,终点越界但起点合法,看该回 206 还是 416。 - 想一想:如果一个资源是动态生成的(比如实时报表),还该不该支持 Range?
常见误区
- 「
bytes=-48是从第 48 字节开始」——是「最后 48 字节」。 - 「416 不用带 Content-Range」——必须带,而且要写总长。
- 「Range 一定回 206」——越界回 416,服务端不支持时还可能直接回 200 全量。
自测三问(点开对答案)
1. `bytes=-48` 和 `bytes=48-` 有什么区别? —— 前者是最后 48 字节,后者是从第 48 字节一直到结尾。 2. 416 响应必须带什么? —— `Content-Range: bytes */总长`,让客户端知道资源真实大小。 3. Range 支撑了哪些常见功能? —— 断点续传、多线程下载、视频拖动。分段讲完了。下一个字段记录来源,而且名字里有个拼写错误。
- ☐ (本节完,下一节 6.4.17 Referer)