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 秒实验
- 把
step改成 256,重跑,看请求次数变成几次、总耗时怎么变。 - 故意让某一块返回 200 而不是 206,看拼接结果会怎样。
- 给资源加
Content-Encoding: gzip,再发Range,想想客户端拿到的偏移量对应哪一层。
常见误区
- 「206 是错误」——它是正常响应,专用于范围请求。
- 「有 Content-Range 就够了,状态码无所谓」——必须是 206,否则无法区分整份与部分。
- 「断点续传只要 Range」——还要校验资源没变(If-Range/ETag),否则拼出损坏文件。
自测三问(点开对答案)
1. 206 响应必须带哪个首部? —— `Content-Range: bytes 起-止/总长`,三个数都要有。 2. 为什么不能用 200 返回部分内容? —— 客户端无法区分「整份」和「一部分」,拼接会错。 3. 范围请求与内容压缩为什么冲突? —— 偏移量是对压缩流算的,客户端没法直接映射到解压后的内容。2XX 看完了。下一节进 3XX:那些「还要再走一步」的码。
- ☐ (本节完,下一节 4.3.1 301 Moved Permanently)