14. 范围请求 (Range Requests)
客户端经常会因为请求被取消或连接中断而遇到数据传输中断. 当客户端已经存储了部分表示时, 它希望在后续请求中只请求该表示的剩余部分, 而不是传输整个表示. 同样, 本地存储受限的设备也可能受益于只请求较大表示的一个子集, 例如非常大型文档中的单个页面, 或嵌入图像的尺寸信息.
范围请求 (range request) 是 HTTP 的一个可选特性. 该特性的设计目标是, 即使接收方没有实现此特性, 或者不支持目标资源的此特性, 也可以像处理普通 GET 请求一样响应, 而不会影响互操作性. 部分响应通过不同的状态码来标识, 以免被未实现该特性的缓存误认为完整响应.
14.1. 范围单位 (Range Units)
当表示数据的内容编码或媒体类型本身具有可寻址的结构单位时, 表示数据可以被划分为子范围. 例如, 八位字节 (octet, 也称为 byte) 边界是所有表示数据共有的结构单位, 因而可以把数据分区标识为从该数据起始处或末尾处某个偏移量开始的一段字节范围.
这种通用的"范围单位 (range unit)"概念用于 Accept-Ranges (第 14.3 节) 响应头字段, 以声明对范围请求的支持; 用于 Range (第 14.2 节) 请求头字段, 以界定所请求的表示部分; 也用于 Content-Range (第 14.4 节) 头字段, 以描述正在传输的是表示的哪个部分.
range-unit = token
所有范围单位名称都不区分大小写, 并且应当注册到第 16.5.1 节定义的 "HTTP Range Unit Registry" 中.
范围单位旨在可扩展, 如第 16.5 节所述.
14.1.1. 范围说明符 (Range Specifiers)
范围用一个范围单位和一组范围说明符配对来表达. 范围单位名称决定了哪些类型的 range-spec 适用于它自己的说明符. 因此, 下面的语法是通用语法: 每个范围单位都应规定何时允许 int-range, suffix-range 和 other-range.
一个范围请求可以在单个表示内指定单个范围或一组范围.
ranges-specifier = range-unit "=" range-set
range-set = 1#range-spec
range-spec = int-range
/ suffix-range
/ other-range
int-range 是用两个非负整数表达的范围, 或者用一个非负整数表达的从该位置直到表示数据末尾的范围. 范围单位规定这些整数的含义, 例如它们可以表示从起始处开始的单位偏移量, 包含端点的编号部分, 等等.
int-range = first-pos "-" [ last-pos ]
first-pos = 1*DIGIT
last-pos = 1*DIGIT
如果存在 last-pos 值且它小于 first-pos, 则该 int-range 无效.
suffix-range 是一个表示数据后缀范围, 使用给定的非负整数最大长度来表达, 该长度以范围单位计. 换言之, 它表示表示数据的最后 N 个单位.
suffix-range = "-" suffix-length
suffix-length = 1*DIGIT
为了支持扩展性, other-range 规则是一个大体不受约束的语法, 允许特定应用或未来的范围单位定义额外的范围说明符.
other-range = 1*( %x21-2B / %x2D-7E )
; 1*(VCHAR excluding comma)
如果 ranges-specifier 包含任何对所指示 range-unit 无效或未定义的 range-spec, 则该 ranges-specifier 无效.
如果一个有效的 ranges-specifier 至少包含一个按所指示 range-unit 定义可满足的 range-spec, 则该 ranges-specifier 是"可满足的 (satisfiable)". 否则, 该 ranges-specifier 是"不可满足的 (unsatisfiable)".
14.1.2. 字节范围 (Byte Ranges)
"bytes" 范围单位用于表达表示数据的八位字节序列的子范围. 每个字节范围都表示为某个偏移量处的整数范围, 该偏移量相对于表示数据的起始处 (int-range) 或末尾处 (suffix-range). 字节范围不使用 other-range 说明符.
bytes int-range 中的 first-pos 值给出范围内第一个字节的偏移量. last-pos 值给出范围内最后一个字节的偏移量; 也就是说, 所指定的字节位置包含端点. 字节偏移量从零开始.
如果表示数据应用了内容编码 (content coding), 则每个字节范围都相对于编码后的字节序列计算, 而不是相对于解码后得到的底层字节序列计算.
bytes 范围说明符示例:
- 前 500 个字节 (字节偏移量 0-499, 包含端点):
bytes=0-499
- 第二个 500 字节 (字节偏移量 500-999, 包含端点):
bytes=500-999
客户端可以在不知道所选表示大小的情况下限制所请求的字节数. 如果 last-pos 值不存在, 或该值大于或等于表示数据的当前长度, 则该字节范围被解释为表示的剩余部分. 也就是说, 服务器会把 last-pos 的值替换为比所选表示当前长度小 1 的值.
客户端可以使用 suffix-range 引用所选表示的最后 N 个字节 (N > 0). 如果所选表示短于指定的 suffix-length, 则使用整个表示.
其他示例, 假设表示长度为 10000:
- 最后 500 个字节 (字节偏移量 9500-9999, 包含端点):
bytes=-500
或:
bytes=9500-
- 仅第一个字节和最后一个字节 (字节 0 和 9999):
bytes=0-0,-1
- 前部, 中部和最后的 1000 个字节:
bytes= 0-999, 4500-5499, -1000
- 第二个 500 字节 (字节偏移量 500-999, 包含端点) 的其他有效但非规范写法:
bytes=500-600,601-999
bytes=500-700,601-999
对于 GET 请求, 有效的 bytes range-spec 在以下任一情况下是可满足的:
-
它是 int-range, 且 first-pos 小于所选表示的当前长度; 或
-
它是 suffix-range, 且 suffix-length 非零.
当所选表示长度为零时, GET 请求中唯一可满足的 range-spec 形式是 suffix-length 非零的 suffix-range.
在 byte-range 语法中, first-pos, last-pos 和 suffix-length 表示为八位字节数量的十进制数. 由于内容长度没有预定义限制, 接收方必须预期可能很大的十进制数字, 并防止因整数转换溢出而导致解析错误.
14.2. Range
GET 请求上的 "Range" 头字段会修改方法语义, 请求只传输所选表示数据 (第 8.1 节) 的一个或多个子范围, 而不是传输整个所选表示.
Range = ranges-specifier
服务器可以忽略 Range 头字段. 不过, 源服务器和中间缓存应当在可能时支持字节范围, 因为它们支持从部分失败的传输中高效恢复, 也支持对大型表示进行部分获取.
服务器必须忽略与未识别的请求方法一起接收的 Range 头字段, 也必须忽略与未定义范围处理的请求方法一起接收的 Range 头字段. 对于本规范, GET 是唯一已定义范围处理的方法.
源服务器必须忽略包含其不理解的范围单位的 Range 头字段. 代理可以丢弃包含其不理解的范围单位的 Range 头字段.
支持范围请求的服务器可以忽略或拒绝包含以下内容的 Range 头字段: 无效的 ranges-specifier (第 14.1.1 节), 包含两个以上重叠范围的 ranges-specifier, 或大量未按升序列出的小范围集合. 这些情况表明客户端可能存在缺陷, 或者存在有意的拒绝服务攻击 (第 17.15 节). 客户端不应请求多个本质上比单个涵盖相同数据的范围处理和传输效率更低的范围.
当所选表示没有内容时, 即所选表示的数据长度为零时, 支持范围请求的服务器可以忽略 Range 头字段.
请求多个范围的客户端应当按升序列出这些范围, 即它们在完整表示中通常会被接收的顺序, 除非存在先请求较后部分的特定需要. 例如, 用户代理在处理带有内部部件目录的大型表示时, 可能需要先请求较后的部分, 尤其是在该表示由按逆序存储的页面组成, 且用户代理希望一次传输一个页面的情况下.
Range 头字段在第 13.1 节定义的前置条件头字段求值之后求值, 且只有在没有 Range 头字段时结果会是 200 (OK) 响应的情况下才求值. 换言之, 当条件 GET 会得到 304 (Not Modified) 响应时, Range 会被忽略.
If-Range 头字段 (第 13.1.5 节) 可以作为应用 Range 头字段的前置条件使用.
如果所有前置条件都为真, 服务器支持目标资源的 Range 头字段, 接收到的 Range 字段值包含有效的 ranges-specifier 且其 range-unit 受该目标资源支持, 并且该 ranges-specifier 相对于所选表示是可满足的, 则服务器应当发送 206 (Partial Content) 响应, 其内容包含与所请求的可满足 range-spec 对应的一个或多个部分表示.
上述要求并不意味着服务器会发送所有被请求的范围. 在某些情况下, 服务器可能只能或更高效地先发送所请求范围的一部分, 并期望客户端稍后在仍然需要剩余部分时重新请求它们 (参见第 15.3.7 节).
如果所有前置条件都为真, 服务器支持目标资源的 Range 头字段, 接收到的 Range 字段值包含有效的 ranges-specifier, 且 range-unit 不受该目标资源支持, 或该 ranges-specifier 相对于所选表示不可满足, 则服务器应当发送 416 (Range Not Satisfiable) 响应.
14.3. Accept-Ranges
响应中的 "Accept-Ranges" 字段指示上游服务器是否支持目标资源的范围请求.
Accept-Ranges = acceptable-ranges
acceptable-ranges = 1#range-unit
例如, 支持字节范围请求 (第 14.1.2 节) 的服务器可以发送字段:
Accept-Ranges: bytes
以指示它支持该目标资源的字节范围请求, 从而鼓励客户端在同一请求路径上未来的部分请求中使用该能力. 范围单位在第 14.1 节中定义.
客户端可以在未接收 Accept-Ranges 字段的情况下生成范围请求. 该信息只是为了提升性能并减少不必要的网络传输而提供的建议.
反过来, 客户端禁止假定收到 Accept-Ranges 字段就意味着未来的范围请求会返回部分响应. 内容可能发生变化, 服务器可能只在某些时间或某些条件下支持范围请求, 或下一次请求可能由不同的中间方处理.
不支持目标资源任何类型范围请求的服务器可以发送:
Accept-Ranges: none
以建议客户端不要在同一请求路径上尝试范围请求. 范围单位 "none" 为此目的保留.
Accept-Ranges 字段可以在 trailer section 中发送, 但最好作为头字段发送, 因为该信息对于重启在内容中途失败的大型信息传输特别有用, 而此时 trailer section 尚未被接收.
14.4. Content-Range
"Content-Range" 头字段在单个部分的 206 (Partial Content) 响应中发送, 用于指示作为消息内容封装的所选表示的部分范围; 在 multipart 206 响应的每个部分中发送, 用于指示每个主体部分内封装的范围 (第 14.6 节); 并在 416 (Range Not Satisfiable) 响应中发送, 用于提供关于所选表示的信息.
Content-Range = range-unit SP
( range-resp / unsatisfied-range )
range-resp = incl-range "/" ( complete-length / "*" )
incl-range = first-pos "-" last-pos
unsatisfied-range = "*/" complete-length
complete-length = 1*DIGIT
如果 206 (Partial Content) 响应包含 Content-Range 头字段, 且该字段中的范围单位 (第 14.1 节) 接收方不理解, 则接收方禁止尝试将其与已存储的表示重新组合. 收到此类消息的代理应当将其向下游转发.
Content-Range 也可以作为请求修饰符发送, 用于基于客户端和源服务器之间的私有约定请求 partial PUT, 如第 14.5 节所述. 如果服务器在请求中接收到 Content-Range 头字段, 且该请求使用的方法没有定义 Content-Range 支持, 则服务器必须忽略该头字段.
对于字节范围, 发送方应当指示被提取范围所属表示的完整长度, 除非该完整长度未知或难以确定. 用星号字符 ("*") 代替 complete-length 表示生成该头字段时表示长度未知.
以下示例说明发送方知道所选表示完整长度为 1234 字节的情况:
Content-Range: bytes 42-1233/1234
第二个示例说明完整长度未知的情况:
Content-Range: bytes 42-1233/*
如果 Content-Range 字段值包含的 range-resp 具有小于 first-pos 值的 last-pos 值, 或 complete-length 值小于或等于 last-pos 值, 则该字段值无效. 无效 Content-Range 的接收方禁止尝试将收到的内容与已存储的表示重新组合.
为字节范围请求生成 416 (Range Not Satisfiable) 响应的服务器应当发送带有 unsatisfied-range 值的 Content-Range 头字段, 如以下示例:
Content-Range: bytes */1234
416 响应中的 complete-length 指示所选表示的当前长度.
Content-Range 头字段对于未显式描述其语义的状态码没有意义. 对于本规范, 只有 206 (Partial Content) 和 416 (Range Not Satisfiable) 状态码描述了 Content-Range 的含义.
以下是 Content-Range 值示例, 其中所选表示总共包含 1234 字节:
- 前 500 个字节:
Content-Range: bytes 0-499/1234
- 第二个 500 字节:
Content-Range: bytes 500-999/1234
- 除前 500 个字节之外的全部内容:
Content-Range: bytes 500-1233/1234
- 最后 500 个字节:
Content-Range: bytes 734-1233/1234
14.5. Partial PUT
当用户代理在请求中发送 Content-Range 头字段 (第 14.4 节) 时, 一些源服务器支持对部分表示执行 PUT, 尽管这种支持并不一致, 并且依赖于与用户代理之间的私有约定. 一般而言, 它请求将目标资源的状态部分替换为所封装的内容, 替换位置和长度由 Content-Range 值指示, 其中偏移量相对于当前所选表示.
如果源服务器在不支持 partial PUT 请求的目标资源的 PUT 上收到 Content-Range, 则它应当以 400 (Bad Request) 状态码响应.
Partial PUT 不向后兼容 PUT 的原始定义. 它可能导致内容被写入为当前表示的完整替换.
部分资源更新也可以通过以下方式实现: 以一个单独标识的资源为目标, 该资源的状态与较大资源的一部分重叠或扩展该部分; 或者使用专门为部分更新定义的不同方法, 例如 [RFC5789] 中定义的 PATCH 方法.
14.6. 媒体类型 multipart/byteranges (Media Type multipart/byteranges)
当 206 (Partial Content) 响应消息包含多个范围的内容时, 它们会作为 multipart 消息体 ([RFC2046], 第 5.1 节) 中的主体部分传输, 该消息体的媒体类型为 "multipart/byteranges".
"multipart/byteranges" 媒体类型包含一个或多个主体部分, 每个主体部分都有自己的 Content-Type 和 Content-Range 字段. 必需的 boundary 参数指定用于分隔各主体部分的边界字符串.
实现说明:
-
第一个边界字符串之前可能会有额外的 CRLF.
-
虽然 [RFC2046] 允许边界字符串加引号, 但一些现有实现会错误地处理加引号的边界字符串.
-
一些客户端和服务器是按照 byteranges 规范的早期草案编写的, 该草案使用 "multipart/x-byteranges" 媒体类型, 它与此类型几乎兼容, 但并不完全兼容.
尽管名称如此, "multipart/byteranges" 媒体类型并不限于字节范围. 以下示例使用 "exampleunit" 范围单位:
HTTP/1.1 206 Partial Content
Date: Tue, 14 Nov 1995 06:25:24 GMT
Last-Modified: Tue, 14 July 04:58:08 GMT
Content-Length: 2331785
Content-Type: multipart/byteranges; boundary=THIS_STRING_SEPARATES
--THIS_STRING_SEPARATES
Content-Type: video/example
Content-Range: exampleunit 1.2-4.3/25
...the first range...
--THIS_STRING_SEPARATES
Content-Type: video/example
Content-Range: exampleunit 11.2-14.3/25
...the second range
--THIS_STRING_SEPARATES--
以下信息作为 "multipart/byteranges" 媒体类型的注册表单.
Type name: multipart
Subtype name: byteranges
Required parameters: boundary
Optional parameters: N/A
Encoding considerations: only "7bit", "8bit", or "binary" are permitted
Security considerations: see Section 17
Interoperability considerations: N/A
Published specification: RFC 9110 (see Section 14.6)
Applications that use this media type: HTTP components supporting multiple ranges in a single request
Fragment identifier considerations: N/A
Additional information: Deprecated alias names for this type: N/A
Magic number(s): N/A
File extension(s): N/A
Macintosh file type code(s): N/A
Person and email address to contact for further information: See Authors' Addresses section.
Intended usage: COMMON
Restrictions on usage: N/A
Author: See Authors' Addresses section.
Change controller: IESG