📘 第三章 HTTP报文内的HTTP信息
📄 一句话概括

Range: bytes=起点-终点 只要资源的一段。 响应必须回 206 并带 Content-Range,否则拼接必错。

📝 本节和前面几节的关系

上一节讲一次发多样东西,这一节讲反过来:一次只要一样东西的一部分。 断点续传、视频拖进度条、大文件分片下载全靠它。

一、一来一回两个首部

请求:Range: bytes=0-99,表示要前 100 字节(下标从 0 开始,两端都含)。 响应:206 Partial Content 加 Content-Range: bytes 0-99/2048,最后那个数是资源总长。 Content-Range 里的总长不能省。 客户端要靠它判断「我下到第几块了、总共还有多少」,没有这个数就没法算进度,也拼不对。 另外服务端要声明自己支持:Accept-Ranges: bytes。 不支持的话应当忽略 Range 直接回 200 整个资源——客户端必须能处理这种「你请求了但我给全部」的情况。

二、三种写法

写法 含义 实测结果
bytes=0-99 前 100 字节 206,100 字节
bytes=2000- 从 2000 到末尾 206,48 字节
bytes=-100 最后 100 字节 206,100 字节
bytes=0-9,100-109 两段 multipart/byteranges
Demo 里试了前两种:bytes=0-99 回 100 字节,
bytes=2000-2047 回 48 字节(因为资源只有 2048 字节)。
越界要小心:请求 bytes=3000- 在标准里应当回 416 Range Not Satisfiable,
并且带 Content-Range: bytes */2048。很多实现直接回整个文件,这会让分片下载逻辑出错。

三、多段范围为什么几乎没人用

理论上可以一次要好几段,服务端用 multipart/byteranges 回。 但每段都要加 boundary 和 Content-Range 首部,开销可能超过省下的字节。 所以真实的断点续传都是「一次要一段」,多段请求只在极少数流媒体场景出现,而且常常被服务端直接忽略。 再补一个现实约束:范围请求是对压缩后的字节算的。 如果响应带 Content-Encoding: gzip,Range 的偏移量指的是压缩流的偏移, 客户端不能对解压后的内容算——这也是为什么下载大文件时常常禁用压缩。

四、动手 Demo:三次请求:不带 Range、前 100 字节、末尾 48 字节

服务端准备 2048 字节的可预测内容(0~255 循环八次),按 Range 决定回 200 还是 206。

content = bytes(range(256)) * 8
rng = hd.get(u'range')
if not rng:
    return (u'HTTP/1.1 200 OK', [(u'Accept-Ranges', u'bytes')], content)
a, b = rng.split(u'=')[1].split(u'-')
a = int(a)
b = int(b) if b else len(content) - 1
return (u'HTTP/1.1 206 Partial Content',
        [(u'Content-Range', u'bytes %d-%d/%d' % (a, b, len(content)))],
        content[a:b + 1])

实跑输出:

--------------------------------------------------------------------
范围请求:只要一段,不要整份
--------------------------------------------------------------------
  Range                  (没发)
      状态               HTTP/1.1 200 OK
      Content-Range      (没有)
      正文               2048 字节
  Range                  bytes=0-99
      状态               HTTP/1.1 206 Partial Content
      Content-Range      bytes 0-99/2048
      正文               100 字节
  Range                  bytes=2000-2047
      状态               HTTP/1.1 206 Partial Content
      Content-Range      bytes 2000-2047/2048
      正文               48 字节

  整份资源                   2048 字节
  只要前 100 字节能省        95.1%

  三个关键点:请求用 `Range: bytes=起点-终点`,响应必须是 **206**,
  并且要带 `Content-Range: bytes 起-止/总长`。
  少了 `Content-Range`,客户端不知道这段在原文件里的位置,拼接就会错。
  多段范围(`bytes=0-9,100-109`)要用 multipart/byteranges 回,
  开销比整份还大,实际几乎只用单段。
  断点续传、视频拖动进度条、大文件分片下载,全都靠这一个首部。

三行的状态码依次是 200 OK、206 Partial Content、206 Partial Content; Content-Range 只有后两行有,格式是 bytes 起-止/总长。 只要前 100 字节能省 95.1%——这就是断点续传的收益:已经下过 95%,只需要补 5%。 注意第二行请求的是 bytes=2000-2047,正好到末尾,回了 48 字节。 如果请求 bytes=2000-(不写终点),结果也是 48 字节。

30 秒实验

  1. 请求 bytes=-100(不写起点),看服务端该回什么。
  2. 请求 bytes=5000-(超出总长),看你的实现回什么,再查一下标准要求回什么(416)。
  3. 给响应加 Content-Encoding: gzip,再发 Range,想想客户端拿到的偏移量对应的是哪一层字节。

常见误区

  1. 「206 就是成功」——它表示「这是部分内容」,客户端必须知道怎么拼。
  2. 「Range 端点不含」——标准里两端都含,0-99 是 100 个字节。
  3. 「范围请求总能用」——服务端可以忽略它回 200,客户端必须兼容这种情况。
自测三问(点开对答案) 1. `bytes=0-99` 要几个字节? —— 100 个,因为区间两端都包含。 2. 响应里哪个首部告诉客户端「这段在原文件的位置」? —— `Content-Range: bytes 起-止/总长`。 3. 范围请求对有压缩的响应有什么坑? —— 偏移量是对压缩流算的,不能拿解压后的内容算。

要一段能要了。最后一节看服务端怎么在多个版本里挑一个给客户端。

相关笔记