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

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

  1. 发 bytes=0-0,看是不是只给 1 个字节。
  2. 发 bytes=-50,看服务端会不会理解成「最后 50 字节」。
  3. 想一想:为什么压缩过的资源一般不该支持范围请求?

常见误区

  1. 「bytes=0-99 是 99 个字节」——闭区间,是 100 个。
  2. 「斜杠后面是剩余长度」——是总长。
  3. 「倒序范围按取前 N 处理」——是非法,回 416。
自测三问(点开对答案) 1. `bytes 1200-1233/1234` 里的三个数字各是什么? —— 起点 1200、终点 1233(闭区间)、总长 1234,一共 34 字节。 2. 416 的 `Content-Range` 该怎么写? —— `bytes */总长`,星号占住起点位置。 3. 为什么 206 必须带 `Content-Range`? —— 客户端要靠它把这段贴回文件的正确位置。

位置说清楚了,下一个字段是实体首部里用途最广的:Content-Type。

相关笔记