Content-Range 说明「这段正文在整份资源的哪个位置」,格式是 bytes 起点-终点/总长。
206 必须带它;416 里写成 bytes */总长。
上一节给整份正文配校验和,这一节处理「只给一段」的情况。
6.5.1 讲了 Accept-Ranges,6.4.16 讲了 Range,这里补上第三个角色。
一、三个数字的位置
Content-Range: bytes 0-99/1234 读作「给你第 0 到 99 字节,总共 1234 字节」。
起点、终点是闭区间,斜杠后面是总长。
总长写成 * 表示不知道。边生成边发的场景会这样写,代价是客户端算不出进度条。
Demo 里整份 1234 字节,五种要法的结果:不要范围给 200 加 1234 字节;
要前 100 给 206 加 bytes 0-99/1234 和 100 字节;要尾巴给 bytes 1200-1233/1234 和 34 字节。
二、416 里的写法不一样
范围不合法时回 416 Range Not Satisfiable,
这时的 Content-Range 写成 bytes */1234——星号占住起点位置,只说总长。
为什么这么写?因为此时没有「给了哪一段」这回事,只有一个总长有意义。
客户端看到星号就知道「我这次要的范围整个不成立」。
Demo 里两种不合法都被判 416:越界(9999-10099)和倒序(500-100)。
注意倒序不是「取前 500」,是彻底非法。
三、几个容易错的地方
闭区间。bytes=0-99 是 100 字节,不是 99。这个差一错误在实现里极常见。
总长不是「剩余」。bytes 0-99/1234 里的 1234 一直是 1234,不会随着请求变。
压缩过的资源,范围指的是编码后字节流的位置。
压缩流不能随便切,
所以带压缩的资源通常干脆不支持范围请求——这也是断点续传在开 gzip 的站点上常失败的原因。
四、动手 Demo:整份 1234 字节,五种要法
服务端拿一份 1234 字节的资源,按 Range 切段;
分别试不带范围、要前 100、要尾巴、越界、倒序五种情况,看状态码和 Content-Range 的写法怎么变。
TOTAL = 1234
rng = hd.get(u'range', u'')
if not rng.startswith(u'bytes='):
return X.reply(u'HTTP/1.1 200 OK', [(u'Accept-Ranges', u'bytes')], BODY)
a, _, b = rng[6:].partition(u'-')
a, b = int(a), int(b)
if a < 0 or b >= TOTAL or a > b:
return X.reply(u'HTTP/1.1 416 Range Not Satisfiable',
[(u'Content-Range', u'bytes */%d' % TOTAL)], b'')
return X.reply(u'HTTP/1.1 206 Partial Content',
[(u'Content-Range', u'bytes %d-%d/%d' % (a, b, TOTAL))],
BODY[a:b + 1]) # 闭区间,切片要 +1
实跑输出:
--------------------------------------------------------------------
Content-Range:这一段在整份里的位置
--------------------------------------------------------------------
整份 1234 字节,四种要法:
不要范围 HTTP/1.1 200 OK (没有) 正文 1234 字节
要前 100 HTTP/1.1 206 Partial Content bytes 0-99/1234 正文 100 字节
要尾巴 HTTP/1.1 206 Partial Content bytes 1200-1233/1234 正文 34 字节
越界 HTTP/1.1 416 Range Not Satisfiable bytes */1234 正文 0 字节
倒序 HTTP/1.1 416 Range Not Satisfiable bytes */1234 正文 0 字节
五种结果里有三点值得记住:
一、206 必须带 Content-Range,没有它客户端不知道这段该贴在文件的哪个位置;
二、416 里的写法是 `bytes */1234`——星号占住起点位置,只说总长;
三、倒序(500-100)和越界一样按 416 处理,不是按「取前 500」处理。
还有一条容易漏:Content-Range 也可能出现在 200 里,
那意思是「这次给的整份,本身只是某个更大东西的一部分」——用得很少。
五行结果:200 加 1234 字节、206 加 100 字节、206 加 34 字节、416 加 0 字节、416 加 0 字节。
注意最后两行一模一样:越界和倒序在协议上是同一类错误,都回 416,都只报总长 bytes */1234。
要尾巴那组最值得看:bytes 1200-1233/1234 说明给的是最后 34 字节,34 = 1234 − 1200。
这个换算很容易错一位。
整份 1234 字节里只取 100 字节,省下的 92% 就是范围请求的价值;代价是服务端得能随机访问这份资源。
30 秒实验
- 发
bytes=0-0,看是不是只给 1 个字节。 - 发
bytes=-50,看服务端会不会理解成「最后 50 字节」。 - 想一想:为什么压缩过的资源一般不该支持范围请求?
常见误区
- 「
bytes=0-99是 99 个字节」——闭区间,是 100 个。 - 「斜杠后面是剩余长度」——是总长。
- 「倒序范围按取前 N 处理」——是非法,回 416。
自测三问(点开对答案)
1. `bytes 1200-1233/1234` 里的三个数字各是什么? —— 起点 1200、终点 1233(闭区间)、总长 1234,一共 34 字节。 2. 416 的 `Content-Range` 该怎么写? —— `bytes */总长`,星号占住起点位置。 3. 为什么 206 必须带 `Content-Range`? —— 客户端要靠它把这段贴回文件的正确位置。位置说清楚了,下一个字段是实体首部里用途最广的:Content-Type。
- ☐ (本节完,下一节 6.6.8 Content-Type)