📘 第四章 返回结果的HTTP状态码
📄 一句话概括

206 是范围请求的专用响应:只给资源的一段,靠 Content-Range 说明这段在原文件里的位置。

📝 本节和前面几节的关系

上一节的 204 是「没有内容」,这一节的 206 是「只有一部分内容」。 3.5 节讲请求怎么写,这一节讲响应怎么用。

一、为什么必须是 206 而不是 200

如果服务端回了 200 加一段内容, 客户端就无法区分「这是整个文件」还是「这是其中一段」——拼接逻辑立刻失效。 所以标准强制:只要按 Range 返回了部分内容,状态码必须是 206,并且必须带 Content-Range。 Content-Range: bytes 0-1023/4096 这三个数缺一不可:起、止、总长。 少了总长,客户端不知道还要下几次。

二、断点续传的完整流程

① 先查总长:HEAD 或 GET 第一个字节,读 Content-Length;② 记住已下多少字节; ③ 断线后从那个偏移量发 Range: bytes=偏移-;④ 收到 206 就追加到文件末尾; ⑤ 直到收到的总字节等于总长。 Demo 里 4096 字节切成四个 1024 的块,四块拼回去逐字节比对一致。这就是所有下载器的骨架。

三、代价与陷阱

代价是每块多一个往返。块越小,往返越多。 所以下载器通常越下越大块(从 1 KB 涨到几 MB),因为大块能把往返摊薄。 陷阱一:压缩与范围的冲突。 3.5 节说过,偏移量是对压缩流算的, 所以带 Content-Encoding 的资源做范围请求会非常别扭——下载大文件时通常直接禁用压缩。 陷阱二:资源在下载过程中变了。 要带 If-Range 或 ETag 校验,否则拼出来的是两个版本的混合体,文件损坏还查不出原因。

四、动手 Demo:四块下完再逐字节比对

服务端准备 4096 字节可预测内容,客户端按 1024 一片发 Range,把每片追加进缓冲区, 最后和服务端原文逐字节比对。

step = 1024
acc = bytearray()
for a in range(0, len(content), step):
    b = min(a + step - 1, len(content) - 1)
    s.sendall(_req(extra=[(u'Range', u'bytes=%d-%d' % (a, b))]))
    got, buf = H.recv_response(s, buf)
    first, hd, part = H.parse_headers(got)
    acc.extend(part)
print(bytes(acc) == content)

实跑输出:

--------------------------------------------------------------------
206 Partial Content:把大文件切块下完再拼
--------------------------------------------------------------------
  资源总长                   4096 字节
  分块大小                   1024 字节
  请求次数                   4 次
  收到的字节合计             4096
  逐块字节比对               一致

  每块的状态                 206 Partial Content
  每块的 Content-Range       bytes 起-止/总长(缺了就没法拼)
  断点续传的本质             记住已下多少,下次从那个位置发 Range
  代价                       每块多一个往返;块越小往返越多
  这就是所有下载器的骨架:先 HEAD 或 GET 第一条拿总长,
  再按片发 Range,最后按偏移量拼起来。

请求次数 4 次、收到的字节合计 4096、逐块字节比对 一致——分四块下完拼回去,一个字节不差。 注意这里复用了同一条连接(keepalive=True),所以「每块一个往返」指的是请求响应的往返,不含握手。 每块的 Content-Range bytes 起-止/总长(缺了就没法拼)——这不是建议,是标准要求。 少写它,客户端只能靠猜。

30 秒实验

  1. 把 step 改成 256,重跑,看请求次数变成几次、总耗时怎么变。
  2. 故意让某一块返回 200 而不是 206,看拼接结果会怎样。
  3. 给资源加 Content-Encoding: gzip,再发 Range,想想客户端拿到的偏移量对应哪一层。

常见误区

  1. 「206 是错误」——它是正常响应,专用于范围请求。
  2. 「有 Content-Range 就够了,状态码无所谓」——必须是 206,否则无法区分整份与部分。
  3. 「断点续传只要 Range」——还要校验资源没变(If-Range/ETag),否则拼出损坏文件。
自测三问(点开对答案) 1. 206 响应必须带哪个首部? —— `Content-Range: bytes 起-止/总长`,三个数都要有。 2. 为什么不能用 200 返回部分内容? —— 客户端无法区分「整份」和「一部分」,拼接会错。 3. 范围请求与内容压缩为什么冲突? —— 偏移量是对压缩流算的,客户端没法直接映射到解压后的内容。

2XX 看完了。下一节进 3XX:那些「还要再走一步」的码。

相关笔记