3. Message Format (消息格式)
所有 HTTP/1.1 消息由起始行、一系列八位字节组成, 这些八位字节的结构类似于互联网消息格式 [RFC5322]: 零个或多个头部字段 (统称为"headers" 或 "header section", 头部区段), 一个空行指示头部区段的结束, 以及一个可选的消息正文。
HTTP-message = start-line
*( header-field CRLF )
CRLF
[ message-body ]
通常的解析过程是按顺序读取起始行到缓冲区, 然后将每个头部字段行读取到哈希表中, 通过字段名进行键入, 直到读取空行。一旦接收到头部区段, 消息解析器可以使用该 Content-Length 来确定消息正文的长度, 该长度可以作为流读取, 或者一次性读取到缓冲区。
HTTP 消息可以通过多种方式进行解析, 具体取决于可用的库函数和实现选择。虽然本规范将其定义为行导向的文本, 但解析器不需要将接收到的消息处理为字符串。许多实现将消息解析为一系列八位字节, 增量地, 并在每个协议元素被识别时将其传递给更高级的应用程序, 以便可以在接收整个消息之前做出决策和响应。
发送方绝对不能在起始行和第一个头部字段之间发送空格。接收方如果在起始行之后立即收到空格, 必须要么拒绝该消息为无效, 要么消耗每个空行 (即, 忽略它们), 直到接收到有效的头部字段或头部区段已终止。
发送方绝对不能发送包含八位字节 0x00 (NUL) 或 0x0D (CR) 或 0x0A (LF) 的头部字段名称或值, 除非在字段内容的 ABNF 中明确允许 (例如, 在引用字符串中)。
接收方如果收到包含这些字符的消息, 可以拒绝该消息, 或者可以删除这些字符并继续处理该消息。
3.1. Start Line (起始行)
HTTP 消息可以是从客户端到服务器的请求, 也可以是从服务器到客户端的响应。从语法上讲, 两种消息类型仅在起始行方面有所不同, 起始行对于请求是请求行 (request-line), 对于响应是状态行 (status-line), 以及算法用于确定消息长度 (Section 3.3)。
理论上, 客户端可以接收请求, 服务器可以接收响应, 从而区分它们是语法无关的。然而, 在实践中, 服务器被实现为仅期望请求 (响应被解释为未知或无效的请求方法), 而客户端仅期望响应。
start-line = request-line / status-line
3.1.1. Request Line (请求行)
请求行以方法标记开始, 后跟一个空格 (SP), 请求目标, 另一个空格 (SP), 协议版本, 并以 CRLF 结束。
request-line = method SP request-target SP HTTP-version CRLF
method 标记指示要在目标资源上执行的请求方法。请求方法是区分大小写的。
method = token
请求方法在 [RFC7231] 的 [Section 4] 中定义, 以及其他规范中定义的扩展。
请求目标标识应用请求的目标资源。客户端从所需的目标 URI 派生请求目标。请求目标有四种不同的格式, 具体取决于请求方法和请求是否发送到代理。
request-target = origin-form
/ absolute-form
/ authority-form
/ asterisk-form
请求目标的详细信息在 Section 5.3 中定义。
接收到无效的 request-line 的接收方应该以 400 (Bad Request, 错误请求) 错误进行响应。
3.1.2. Status Line (状态行)
响应消息的第一行是状态行, 由协议版本、空格 (SP)、状态码、另一个空格、可能为空的文本短语 (描述状态码) 以及 CRLF 组成。
status-line = HTTP-version SP status-code SP reason-phrase CRLF
状态码元素是一个 3 位数的整数代码, 描述服务器尝试理解和满足客户端相应请求的结果。reason-phrase 元素的存在仅用于为人类用户提供文本描述。客户端应该忽略 reason-phrase 内容。
status-code = 3DIGIT
reason-phrase = *( HTAB / SP / VCHAR / obs-text )
状态码在 [RFC7231] 的 [Section 6] 中定义, 以及其他规范中定义的扩展, 如 Section 8.1 所指示的那样。
3.2. Header Fields (头部字段)
每个头部字段由不区分大小写的字段名称、冒号 (":"), 可选的前导空格、字段值和可选的尾随空格组成。
header-field = field-name ":" OWS field-value OWS
field-name = token
field-value = *( field-content / obs-fold )
field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
field-vchar = VCHAR / obs-text
obs-fold = CRLF 1*( SP / HTAB )
; obsolete line folding
; see Section 3.2.4
字段名称标记标识头部字段的语义。例如, Date 头部字段被定义为在 [RFC7231] 的 [Section 7.1.1.2] 中定义。
3.2.1. Field Extensibility (字段可扩展性)
头部字段是完全可扩展的: 关于字段名称的注册表或任何特定消息中可能出现的字段集没有预定义的限制。现有字段在 [RFC7231] 的附录中定义, 并在 "Message Headers" 注册表中维护 ([RFC3864])。
新字段名称可以定义, 只要将其定义为单个令牌, 字段值语法与本规范的通用字段内容规则相兼容, 并且其语义不会产生所需头部字段的约束 (见 Section 3.2.2)。
由于注册头部字段名称的过程 ([RFC3864]) 不要求对字段值语法的标准化, 并且不会阻止在已注册字段名称上发生的值语法的变化, 在实现者在协议元素中定义新字段时, 某些谨慎是明智的: 可能存在现有字段名称的使用, 这可能与新定义的语法发生冲突, 或者新定义的字段值本身可能具有在以前独立、不兼容定义中使用的语法。
推荐的做法是新头部字段具有稀有或特定于应用程序的语法, 而不是通用语法, 以最小化与现有字段的冲突风险。
3.2.2. Field Order (字段顺序)
接收方可以以任何顺序组合具有相同字段名称的多个头部字段, 方法是按顺序将每个后续字段值附加到组合字段值, 用逗号分隔。因此, 接收到的头部字段的顺序对于组合字段值的解释很重要, 因此代理绝对不能在转发消息时更改这些字段值的顺序。
注意: 实际上, "Set-Cookie" 头部字段 ([RFC6265]) 通常在实践中出现多次, 并且不使用列表语法, 违反了上述要求对于组合头部字段的期望 (见 [RFC6265] 的 [Appendix A.2.3])。由于它不能被组合成单个字段值, 多个 "Set-Cookie" 头部字段的接收方应该按收到的顺序处理其字段值。
除非字段定义另有指定, 否则相同头部字段名称的多个字段值的顺序对于该字段的解释是显著的, 因此发送方绝对不能为任何给定字段名称生成多次头部字段名称, 除非该字段的值被定义为逗号分隔的列表 (即, #(values)) 或该字段是已知的例外 (如上所述)。
注意: 本文档中定义的字段规则, 以及 [RFC7231] 定义的字段规则中, 没有字段定义依赖于字段在头部区段中出现的位置; 唯一重要的是组合字段值。然而, 某些现有的缓存实现可能对这些字段的接收顺序做出假设, 因此建议发送方按照对解释最有可能有益的顺序生成字段: 条件头部字段优先于认证头部字段优先于内容协商头部字段优先于其余字段。
发送方绝对不能在没有先从该头部区段中移除现有实例或将现有字段值组合到新的字段值中的情况下, 生成该头部字段名称的多个实例。
3.2.3. Whitespace (空白符)
本规范使用三个规则来表示线性空白的使用: OWS (optional whitespace, 可选空白)、RWS (required whitespace, 必需空白) 和 BWS ("bad" whitespace, "坏"空白)。
OWS 规则在发送方可能更喜欢使用空白以提高可读性 (例如, 在分隔符或操作符之后), 或者仅用于消费固定宽度字段的空白以完成行解析算法的地方使用; 在这种情况下, 发送方应该生成可选的空白作为单个 SP。
OWS = *( SP / HTAB )
; optional whitespace
RWS 规则用于至少需要一个线性空白以分隔字段标记的地方。发送方应该生成 RWS 作为单个 SP。
RWS = 1*( SP / HTAB )
; required whitespace
BWS 规则用于出于历史原因允许可选空白但仅用于不改变协议元素语义的目的, 并且发送方绝对不能生成它的地方。
BWS = OWS
; "bad" whitespace
对这些规则的揭露是, 发送方绝对不能生成除 SP 或 HTAB 之外的 OWS 或 RWS。
3.2.4. Field Parsing (字段解析)
消息可以使用行折叠 (即, 一个头部字段可以拆分为多行) 通过前面的每个额外行与至少一个 SP 或 HTAB 传输。行折叠在 [RFC2616] 中定义并被允许, 但现在已被弃用。
发送方绝对不能在 HTTP/1.1 (或更高版本) 消息中生成使用行折叠的消息 (即, 在字段内容之外具有后跟空白的 CRLF 的任何头部字段)。
服务器如果在请求消息中接收到 obs-fold, 必须要么拒绝该消息, 方法是以适当的 4xx (Client Error) 状态码进行响应, 最好是 400 (Bad Request), 要么将每个接收到的 obs-fold 替换为一个或多个 SP 八位字节, 然后处理结果消息。
代理或网关如果在响应消息中接收到 obs-fold, 必须要么丢弃该消息并用 502 (Bad Gateway) 响应替换它, 最好是带有导致错误的原因短语的表示, 要么将每个接收到的 obs-fold 替换为一个或多个 SP 八位字节, 然后处理结果消息。
用户代理如果在响应消息中接收到 obs-fold, 必须将每个接收到的 obs-fold 替换为一个或多个 SP 八位字节, 然后处理结果消息。
历史上, HTTP 头部字段值可以使用空白或续行跨越多行扩展 (见 [RFC2616] 的 [Section 2.2])。本规范将这种行折叠形式作为过时的消息构造 (obs-fold) 弃用, 不允许在 HTTP/1.1 (或更高版本) 中使用, 除非在协议特定字段值语法中明确定义为行折叠; 这类定义在本规范或其扩展中不存在。
3.2.5. Field Limits (字段限制)
HTTP 对头部字段名称或值的长度, 或头部区段的总长度都没有预定义的限制, 如 Section 2.5 中所述。通常, 服务器都是根据处理性能和拒绝服务攻击预防的实际考虑因素进行配置的。
接收到头部字段、字段行或字段集比它希望处理的更大的服务器必须以适当的 4xx (Client Error) 状态码进行响应。忽略此类头部字段会增加请求走私攻击的响应拆分攻击的漏洞 (Section 9.5)。
客户端可以丢弃或截断无法处理的接收到的头部字段。
3.2.6. Field Value Components (字段值组件)
大多数 HTTP 头部字段值使用由空白或特定分隔字符分隔的常见语法组件 (标记、引用字符串和注释) 定义。这些分隔符被选择是因为它们超出了 US-ASCII 可打印文本 (大多数头部字段值中需要允许的编码字符集), 并且易于实现, 因为需要在大多数解析场景中分析过去。
token:
标记是用于许多头部字段值中的短文本字符串。
token = 1*tchar
tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
/ "+" / "-" / "." / "0-9" / "A-Z" / "^" / "_"
/ "`" / "a-z" / "|" / "~"
; any VCHAR, except delimiters
许多 HTTP/1.1 头部字段值由单词 (标记或引用字符串) 以及使用空格或特殊字符的某种组合组成。这些特殊字符必须在引用字符串中才能用作字段值的一部分。
quoted-string:
引用字符串表示法 (quoted-string) 是为单个值提供的, 该值由双引号 (DQUOTE) 字符包围。
quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
引用字符串内允许使用反斜杠八位字节 ("") 作为单个八位字节引用机制。接收方如果处理 quoted-string 的值, 必须处理反斜杠八位字节作为引用机制, 无论接下来的八位字节是什么。
发送方应该仅在引用八位字节的情况下生成 quoted-pair: DQUOTE 和反斜杠 (在引用字符串中会引起歧义)。发送方应该不引用或使用反斜杠八位字节对 qdtext 定义中允许的八位字节进行引用。
历史上, HTTP 允许在引用字符串中的任何地方使用反斜杠八位字节, 包括在引用字符串本身外部引号字符的使用; 然而, 这种用法已被弃用。
comment:
注释 (comment) 可以包含在某些 HTTP 头部字段中, 这些字段由解释允许这种注释的字段值定义的 ABNF 的接收方根据该字段值定义之外进行解释。
comment = "(" *( ctext / quoted-pair / comment ) ")"
ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
注释的反斜杠八位字节 ("") 可以作为单个八位字节引用机制使用, 使用与 quoted-string 相同的规则。
发送方应该仅在引用八位字节的情况下生成 quoted-pair: "(" 、 ")" 和反斜杠 (在注释中会引起歧义)。
parameter:
参数 (parameter) 通常采用名称=值对的形式。
parameter = token "=" ( token / quoted-string )
注意: [RFC2616] 允许引用字符串中的文本使用 ISO-8859-1 字符集 ([ISO-8859-1], 也称为 Latin-1)。本规范不对接收 Latin-1 字符设置任何要求。一些协议元素允许以百分比编码的形式进行任意八位字节序列, 如 [RFC3986] 的 [Section 2.1] 中定义的那样, 因为根据规则它们由 URI 引用使用。某些协议元素允许使用 [RFC2231] 中定义的编码。实现者应参阅相应的协议元素规范, 以获取有关字符集映射的详细信息。
3.3. Message Body (消息正文)
HTTP/1.1 消息的消息正文 (如果有的话) 用于承载目标资源的表示数据 ([RFC7231]) 或请求的有效载荷。消息正文与请求或响应的有效载荷相同, 除非应用了传输编码, 如 Section 3.3.1 中所述。
message-body = *OCTET
识别消息正文存在的规则因请求和响应而异。
请求中消息正文的存在由 Content-Length 或 Transfer-Encoding 头部字段发出信号。请求消息框架独立于请求方法语义, 即使方法没有定义对消息正文的任何使用。
对于响应消息, 消息正文的存在或不存在取决于接收响应的请求方法和响应状态码 (Section 3.1.2)。对 HEAD 请求方法 ([RFC7231], [Section 4.3.2]) 的响应绝对不能包含消息正文, 因为相关的响应头部字段 (例如, Transfer-Encoding、Content-Length 等) 仅指示如果请求方法是 GET ([RFC7231], [Section 4.3.1]), 则会发送什么。所有 1xx (Informational, 信息性), 204 (No Content, 无内容) 和 304 (Not Modified, 未修改) 响应绝对不能包含消息正文。所有其他响应都包含消息正文, 尽管该正文可能长度为零。
3.3.1. Transfer-Encoding (Transfer-Encoding)
Transfer-Encoding 头部字段列出了传输编码名称, 这些名称对应于应用于有效载荷正文的编码序列, 以形成消息正文。传输编码在 Section 4 中定义。
Transfer-Encoding = 1#transfer-coding
; defined in Section 4
Transfer-Encoding 类似于 MIME 的 Content-Transfer-Encoding 字段, 该字段设计用于在 7 位传输服务上启用安全传输二进制数据 ([RFC2045], [Section 6])。然而, 安全传输在 8 位传输协议的上下文中具有不同的焦点。在 HTTP 的情况下, Transfer-Encoding 主要用于准确地描述有效载荷正文的成帧, 以便接收方可以知道消息何时完整。
接收到包含 Transfer-Encoding 的请求消息的服务器必须理解 "chunked" 传输编码, 并且必须解码其他任何传输编码以获得消息表示。如果服务器无法处理或根本不支持任何传输编码, 则服务器可以拒绝请求并以 501 (Not Implemented, 未实现) 响应。
发送方绝对不能应用分块传输编码多次到消息正文 (即, 不允许分块已经分块的消息)。如果除分块以外的任何传输编码应用于请求有效载荷正文, 则发送方必须应用分块作为最终传输编码以确保消息被正确成帧。如果除分块以外的任何传输编码应用于响应有效载荷正文, 则发送方必须应用分块作为最终传输编码, 或者通过关闭连接来终止消息。例如:
Transfer-Encoding: gzip, chunked
发送方绝对不能发送既包含 Content-Length 头部字段又包含 Transfer-Encoding 头部字段的消息。如果消息在转发之前被下游中间方修改以添加或删除传输编码, 则中间方必须更新 Content-Length 字段以反映消息正文的新长度, 或者删除 Content-Length 字段。
Transfer-Encoding 可以发送在响应到 HEAD 请求或 304 (Not Modified) 响应 ([RFC7232], [Section 4.1]) 中, 以指示源服务器会发送哪些传输编码如果请求是无条件的 GET。然而, 这个指示并不要求源服务器发送相同的编码, 因为那些资源特性可能在响应之间改变。注意服务器绝对不能在这种响应中发送任何传输编码。
服务器绝对不能发送包含 Transfer-Encoding 的响应到 HTTP/1.0 客户端。
3.3.2. Content-Length (Content-Length)
当在消息中没有 Transfer-Encoding 头部字段时, Content-Length 头部字段 ([RFC7231], [Section 3.3.2]) 可以提供消息正文的预期大小 (以八位字节计) 给接收方。对于接收到的消息, Content-Length 指示应该从连接中读取多少八位字节以获取消息正文; 对于生成的消息, Content-Length 指示接收方应该从发送方接收多少八位字节。
Content-Length = 1*DIGIT
Content-Length 头部字段值的示例是:
Content-Length: 3495
用户代理应该在请求消息中发送 Content-Length, 当请求方法定义了对封闭的有效载荷正文的语义时。例如, 当请求方法是 POST 或 PUT 时, Content-Length 头部字段通常被发送。用户代理不应该在请求消息正文为空时发送 Content-Length 头部字段。
服务器可以在响应到 HEAD 请求时发送 Content-Length 头部字段 ([RFC7231], [Section 4.3.2]); 服务器绝对不能在这种响应中发送 Content-Length, 除非其字段值等于如果请求方法是 GET 时将在同一请求的响应中发送的消息正文的八位字节大小。
服务器可以在响应到 CONNECT 请求时发送 Content-Length 头部字段 ([RFC7231], [Section 4.3.6]); 服务器绝对不能在状态码为 2xx (Successful, 成功) 的这种响应中发送 Content-Length 头部字段。
除上述情况外, 如果存在消息正文, 源服务器应该在所有消息中发送 Content-Length 头部字段, 并且消息正文的大小在响应生成之前就已知。这使得接收方能够确定消息何时完整, 即使服务器遇到错误并过早关闭连接。
发送方绝对不能在任何包含 Transfer-Encoding 头部字段的消息中发送 Content-Length 头部字段。
注意: HTTP 的旧实现没有理解分块传输编码, 并且忽略了 Transfer-Encoding 头部字段。虽然这可能导致请求走私漏洞, 但如果接收方在 HTTP/1.1 上接收到请求, 它需要理解分块编码, 并且这可以通过验证 Host 头部字段的存在来确定。这种混淆由于发送方绝对不能在消息中同时发送 Content-Length 和 Transfer-Encoding 的要求而进一步减少, 如果接收到的消息包含这两者, 接收方必须关闭连接或丢弃该消息 (见 Section 3.3.3)。
3.3.3. Message Body Length (消息正文长度)
消息正文的长度通过以下方式之一确定 (按优先级顺序):
-
对 HEAD 请求方法的任何响应和所有 1xx (Informational), 204 (No Content) 和 304 (Not Modified) 响应总是由头部区段后的第一个空行终止, 不管消息中存在哪些头部字段, 并且因此不能包含消息正文。
-
对 CONNECT 请求方法的任何 2xx (Successful) 响应暗示连接将在响应头部区段后立即变成隧道。客户端必须忽略在这种成功的 CONNECT 响应中接收到的
Content-Length或Transfer-Encoding头部字段。 -
如果
Transfer-Encoding头部字段存在, 并且分块传输编码 (chunked) 是最终编码 (如 Section 4 中定义的), 则通过读取和解码分块数据直到传输编码指示数据完整来确定消息正文长度。如果
Transfer-Encoding头部字段存在于响应中, 并且分块传输编码不是最终编码, 则消息正文长度由读取连接直到服务器关闭连接来确定。如果Transfer-Encoding头部字段存在于请求中, 并且分块传输编码不是最终编码, 则服务器可以拒绝该请求, 方法是以 400 (Bad Request) 状态码关闭连接, 或者可以通过丢弃该请求, 关闭连接, 并继续监听子序列请求来继续。如果消息在 HTTP/1.1 (或更高版本) 中接收, 并且
Transfer-Encoding头部字段存在, 则接收方必须理解 "chunked" 传输编码 (Section 4.1), 并且必须将消息作为错误处理, 如果接收方是服务器, 则以 400 (Bad Request) 状态码响应, 或者如果接收方是代理, 则通过切换到隧道行为在当前连接的剩余部分。如果消息在 HTTP/1.0 中接收, 或者没有识别到 HTTP 版本, 并且包含
Transfer-Encoding头部字段, 则必须忽略该头部字段。发送方绝对不能在任何包含
Transfer-Encoding头部字段的消息中发送Content-Length头部字段。如果接收到的消息具有两者, 则接收方必须从消息中删除Content-Length字段, 然后处理该消息。 -
如果消息包含
Content-Length头部字段, 则其字段值, 一个以十进制数字序列表示的非负整数, 定义以八位字节为单位的预期消息正文长度。如果发送方关闭连接或接收方在指示的八位字节数之前超时, 则接收方必须将消息视为不完整并关闭连接。 -
如果这是一个请求消息, 并且上述规则都不适用, 则消息正文长度为零 (没有消息正文存在)。
-
否则, 这是一个没有声明消息正文长度的响应消息, 因此消息正文长度由服务器关闭连接之前接收到的八位字节数确定。
由于没有明确的方法来区分成功完成的、关闭传输的响应消息和部分失败的响应消息 (即, 在接收方接收整个预期表示之前被中断), 客户端应该始终为没有显式消息正文长度的响应重试失败的请求, 除非方法定义或状态码明确禁止这样做。
服务器可以通过立即关闭连接来拒绝请求消息, 如果该请求未显式包含 Content-Length, 或者在请求中的 Transfer-Encoding 不被理解, 或者如果服务器选择立即关闭连接以响应可能的请求走私攻击 (Section 9.5)。
3.4. Handling Incomplete Messages (处理不完整的消息)
服务器在发送完整的响应之前关闭连接通常表示服务器端发生了错误或超时。客户端可以决定请求是否可以安全地重试。对于具有幂等请求方法 ([RFC7231], [Section 4.2.2]) 的完整请求, 客户端可以在接收到任何响应之前自动重试该请求。
客户端不应该自动重试非幂等请求, 因为自动重试可能导致服务器端的不期望的副作用。然而, 用户代理可以提供用户界面来手动请求重试。
3.5. Message Parsing Robustness (消息解析健壮性)
尽管本规范对于发送方的行为定义了严格的要求, 但并非所有 HTTP 实现都符合规范, 并且一些用户代理故意违反这些要求以与实际部署的实现进行互操作。虽然健壮的接收方对于健壮的 HTTP 系统至关重要, 但当发送方和接收方都生成和接受无效或歧义的消息时, 通过多个这样的代理的消息可能导致可利用的安全漏洞。
当接收到的消息头部字段值中包含一个或多个在该字段定义的 ABNF 之外的八位字节 (包括头部字段行开始或结束处的空格), 或者包含在该 ABNF 之外观察到的折叠的八位字节, 则消息可以被解析为无效。发送方绝对不能有意生成无效的消息。
接收方绝对不能将分隔符八位字节 (例如 CR、LF 或 NUL) 解释为头部字段值的一部分。
接收方如果在解析头部字段后能够确定接收到的字段是无效的, 必须拒绝该消息或将该字段替换为安全的本地定义值, 然后处理该消息。接收方绝对不能尝试修复或更改接收到的头部字段以匹配其定义的语法; 这样的更改可能导致消息被解释为与发送方的意图不同。
📍 翻译进度: Section 3 完成
下一章节: Section 4. Transfer Codings (传输编码) - 这是分块传输编码的详细章节
请回复 "继续" 以继续翻译下一章节。