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

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

  1. 发 bytes=0-0,看能不能只取一个字节。
  2. 发 bytes=2000-9999,终点越界但起点合法,看该回 206 还是 416。
  3. 想一想:如果一个资源是动态生成的(比如实时报表),还该不该支持 Range?

常见误区

  1. 「bytes=-48 是从第 48 字节开始」——是「最后 48 字节」。
  2. 「416 不用带 Content-Range」——必须带,而且要写总长。
  3. 「Range 一定回 206」——越界回 416,服务端不支持时还可能直接回 200 全量。
自测三问(点开对答案) 1. `bytes=-48` 和 `bytes=48-` 有什么区别? —— 前者是最后 48 字节,后者是从第 48 字节一直到结尾。 2. 416 响应必须带什么? —— `Content-Range: bytes */总长`,让客户端知道资源真实大小。 3. Range 支撑了哪些常见功能? —— 断点续传、多线程下载、视频拖动。

分段讲完了。下一个字段记录来源,而且名字里有个拼写错误。

相关笔记