3. 消息格式
所有 HTTP/1.1 消息都由一个起始行 (start-line) 以及随后的一个八位字节序列组成, 其格式与 Internet 消息格式 [RFC5322] 类似: 零个或多个头部字段 (统称为 "头部" 或 "头部部分")、一个表示头部部分结束的空行, 以及可选的消息主体.
HTTP-message = start-line
*( header-field CRLF )
CRLF
[ message-body ]
解析 HTTP 消息的常规过程是: 把起始行读入某个结构, 按字段名把每个头部字段读入一张哈希表, 直到遇到空行, 然后使用解析所得的数据判断是否应当存在消息主体. 如果已表明存在消息主体, 则以流的方式读取它, 直到读到的八位字节数等于消息主体长度, 或者连接被关闭.
接收方 MUST 按照作为 US-ASCII [USASCII] 超集的某种编码, 把 HTTP 消息解析为一个八位字节序列. 在不考虑具体编码的情况下把 HTTP 消息当作 Unicode 字符流来解析, 会带来安全漏洞, 原因是字符串处理库对包含八位组 LF (%x0A) 的非法多字节字符序列的处理方式各不相同. 基于字符串的解析器只有在协议元素已从消息中提取出来之后 (例如在消息解析已经划分出各个独立字段之后, 在某个头部字段值内部) 才能被安全地使用.
HTTP 消息可以被解析为流, 以便增量处理或向下游转发. 然而, 接收方不能依赖对部分消息的增量投递, 因为有些实现会出于网络效率、安全检查或载荷变换的考虑而对消息转发进行缓冲或延迟.
发送方 MUST NOT 在起始行与第一个头部字段之间发送空白. 在起始行与第一个头部字段之间收到空白的接收方 MUST 要么以消息无效为由予以拒绝, 要么在不再进一步处理的情况下消费每一行以空白开头的行 (即忽略整行, 以及随后所有以空白开头的行, 直到收到一个格式正确的头部字段或头部部分结束).
请求中出现此类空白, 可能是企图诱使服务器忽略该字段, 或者把其后的行当作一个新请求来处理; 如果请求链上的其他实现对同一条消息作出不同解释, 这两种情况都可能导致安全漏洞. 同样, 响应中出现此类空白可能被某些客户端忽略, 或者导致另一些客户端停止解析.
3.1. 起始行
HTTP 消息既可以是从客户端到服务器的请求, 也可以是从服务器到客户端的响应. 在语法上, 这两类消息只在起始行 (对请求是 request-line, 对响应是 status-line) 以及判定消息主体长度的算法 (第 3.3 节) 上有所不同.
理论上, 客户端可以接收请求, 服务器可以接收响应, 并通过二者起始行格式的不同来区分它们; 但在实践中, 服务器的实现只期望接收请求 (响应会被解释为一个未知或无效的请求方法), 客户端的实现只期望接收响应.
start-line = request-line / status-line
3.1.1. 请求行
请求行以方法 token 开头, 后跟一个空格 (SP)、请求目标 (request-target)、再一个空格 (SP)、协议版本, 并以 CRLF 结束.
request-line = method SP request-target SP HTTP-version CRLF
方法 token 指明要在目标资源上执行的请求方法. 请求方法区分大小写.
method = token
本规范定义的请求方法见 [RFC7231] 第 4 节, 其中还包括关于 HTTP 方法注册表以及定义新方法时的考量的信息.
请求目标标识请求所施加的目标资源, 如第 5.3 节所定义.
接收方通常按空白切分, 把请求行解析为其组成部分 (见第 3.5 节), 因为这三个组成部分中都不允许出现空白. 遗憾的是, 有些用户代理未能正确编码或排除超文本引用中出现的空白, 导致这些不被允许的字符被发送到请求目标中.
收到无效请求行的接收方 SHOULD 返回 400 (Bad Request) 错误, 或者在把请求目标正确编码后返回 301 (Moved Permanently) 重定向. 接收方 SHOULD NOT 尝试自动纠正然后在没有重定向的情况下处理该请求, 因为该无效请求行可能是被刻意构造出来以绕过请求链上安全检查的.
如第 2.5 节所述, HTTP 没有对请求行的长度施加预定义限制. 如果服务器收到的方法比它实现的任何方法都长, SHOULD 返回 501 (Not Implemented) 状态码. 如果服务器收到的请求目标比它愿意解析的任何 URI 都长, MUST 返回 414 (URI Too Long) 状态码 (见 [RFC7231] 第 6.5.12 节).
实践中存在各种针对请求行长度的临时性限制. RECOMMENDED 的做法是: 所有 HTTP 发送方和接收方至少支持 8000 个八位字节的请求行长度.
3.1.2. 状态行
响应消息的第一行是 status-line, 它由协议版本、一个空格 (SP)、状态码、又一个空格、描述该状态码的可能为空的文本短语组成, 并以 CRLF 结束.
status-line = HTTP-version SP status-code SP reason-phrase CRLF
status-code 元素是一个 3 位十进制整数码, 用于描述服务器尝试理解和满足客户端相应请求的结果. 响应消息的其余部分应结合为该状态码所定义的语义来解释. 关于状态码语义的信息, 包括状态码的类别 (由第一位数字表示)、本规范定义的状态码、定义新状态码时的考量以及 IANA 注册表, 见 [RFC7231] 第 6 节.
status-code = 3DIGIT
reason-phrase 元素存在的唯一目的是提供一个与数字状态码相关联的文本描述, 这主要是为了照顾那些更常与交互式文本客户端一起使用的早期 Internet 应用协议. 客户端 SHOULD 忽略 reason-phrase 的内容.
reason-phrase = *( HTAB / SP / VCHAR / obs-text )
3.2. 头部字段
每个头部字段由一个不区分大小写的字段名, 后跟一个冒号 (":"), 可选的前导空白、字段值, 以及可选的尾随空白组成.
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
field-name token 为该 field-value 打上标签, 表明它具有该头部字段所定义的语义. 例如, Date 头部字段在 [RFC7231] 第 7.1.1.2 节中被定义为包含其所在消息的起始时间戳.
3.2.1. 字段可扩展性
头部字段是完全可扩展的: 对引入新的字段名 (每个字段名想必都定义了新的语义) 没有限制, 对给定消息中使用的头部字段数量也没有限制. 现有字段在本规范的各个部分以及本文档集之外的许多其他规范中定义.
新定义的头部字段可以在被接收方理解时覆盖或增强对先前已定义头部字段的解释、定义请求求值的前置条件, 或者细化响应的含义.
代理 MUST 转发无法识别的头部字段, 除非该字段名被列在 Connection 头部字段中 (第 6.1 节), 或者代理被专门配置为阻断或以其他方式变换这类字段. 其他接收方 SHOULD 忽略无法识别的头部字段. 这些要求使得 HTTP 的功能可以在无需事先更新已部署中间方的情况下得到增强.
所有已定义的头部字段都应当在 IANA 的 "Message Headers" 注册表中登记, 如 [RFC7231] 第 8.3 节所述.
3.2.2. 字段顺序
字段名不同的头部字段被接收的顺序并不重要. 不过, 良好的做法是先发送包含控制数据的头部字段, 例如请求中的 Host 和响应中的 Date, 以便实现能够尽可能早地决定何时不该处理某条消息. 服务器 MUST NOT 在收到完整的请求头部部分之前就把请求施加于目标资源, 因为靠后的头部字段可能包含会影响请求处理的条件、认证凭证, 或者刻意误导性的重复头部字段.
除非该头部字段的整个字段值被定义为以逗号分隔的列表 [即 #(values)], 或者该头部字段是众所周知的例外 (如下文所述), 否则发送方 MUST NOT 在一条消息中生成多个字段名相同的头部字段.
接收方 MAY 把多个字段名相同的头部字段合并为一个 "field-name: field-value" 对, 做法是按顺序把后续每个字段值追加到合并后的字段值上并以逗号分隔, 从而不改变消息的语义. 因此, 字段名相同的头部字段被接收的顺序对于合并后字段值的解释是重要的; 代理在转发消息时 MUST NOT 改变这些字段值的顺序.
注意: 在实践中, "Set-Cookie" 头部字段 ([RFC6265]) 经常在一条响应消息中多次出现, 且不使用列表语法, 从而违反了上文关于同名多头部字段的要求. 由于它无法被合并为单个 field-value, 接收方在处理头部字段时应当把 "Set-Cookie" 作为特例来处理. (详情见 [Kri2001] 附录 A.2.3.)
3.2.3. 空白
本规范使用三条规则来表示线性空白的使用: OWS (可选空白, optional whitespace)、RWS (必需空白, required whitespace) 和 BWS ("坏" 空白, "bad" whitespace).
OWS 规则用于可能出现零个或多个线性空白八位组的地方. 对于为提升可读性而倾向于使用可选空白的协议元素, 发送方 SHOULD 以单个 SP 生成该可选空白; 否则, 发送方 SHOULD NOT 生成可选空白, 除非在就地过滤消息时需要用空白抹去无效或不需要的协议元素.
RWS 规则用于至少需要一个线性空白八位组来分隔字段 token 的地方. 发送方 SHOULD 以单个 SP 生成 RWS.
BWS 规则用于文法仅出于历史原因而允许可选空白的地方. 发送方 MUST NOT 在消息中生成 BWS. 接收方 MUST 解析出这类坏空白, 并在解释协议元素之前将其移除.
OWS = *( SP / HTAB )
; optional whitespace
RWS = 1*( SP / HTAB )
; required whitespace
BWS = OWS
; "bad" whitespace
3.2.4. 字段解析
消息使用一种与各个头部字段名无关的通用算法来解析. 给定字段值内部的内容在消息解释的后续阶段才会被解析 (通常是在整条消息的头部部分处理完之后). 因此, 本规范不像以前的版本那样使用 ABNF 规则来定义每个 "Field-Name: Field Value" 对. 相反, 本规范使用的 ABNF 规则按每个已登记的字段名来命名, 规则中定义的是相应字段值的合法文法 (即在通用字段解析器已把该 field-value 从头部部分中提取出来之后).
在头部字段名与冒号之间不允许出现空白. 过去, 对此类空白处理方式的差异曾在请求路由和响应处理中导致安全漏洞. 若收到的请求消息在某个头部字段名与冒号之间含有空白, 服务器 MUST 以 400 (Bad Request) 响应码拒绝该消息. 若响应消息中存在此类空白, 代理 MUST 在向下游转发该消息之前将其移除.
字段值之前和/或之后可能存在可选空白 (OWS); 为了让人阅读时更一致, 倾向于在 field-value 之前使用单个 SP. 字段值不包含任何前导或尾随空白: 从头部字段中提取字段值时, 解析器应当排除出现在字段值第一个非空白八位组之前, 或字段值最后一个非空白八位组之后的 OWS.
历史上, HTTP 头部字段值可以通过让每个额外行以至少一个空格或水平制表符开头而扩展到多行 (obs-fold). 本规范弃用这种行折叠, message/http 媒体类型 (第 8.3.1 节) 内部除外. 发送方 MUST NOT 生成包含行折叠的消息 (即任何 field-value 中匹配 obs-fold 规则的消息), 除非该消息是准备封装在 message/http 媒体类型中的.
如果服务器在非 message/http 容器的请求消息中收到 obs-fold, 它 MUST 要么通过发送 400 (Bad Request) 来拒绝该消息 (最好附带一段表示 "过时的行折叠不可接受" 的表示), 要么在解释字段值或向下游转发该消息之前, 把收到的每个 obs-fold 替换为一个或多个 SP 八位组.
如果代理或网关在非 message/http 容器的响应消息中收到 obs-fold, 它 MUST 要么丢弃该消息并以 502 (Bad Gateway) 响应替换之 (最好附带一段表示 "收到了不可接受的行折叠" 的表示), 要么在解释字段值或向下游转发该消息之前, 把收到的每个 obs-fold 替换为一个或多个 SP 八位组.
如果用户代理在非 message/http 容器的响应消息中收到 obs-fold, 它 MUST 在解释字段值之前把收到的每个 obs-fold 替换为一个或多个 SP 八位组.
历史上, HTTP 允许字段内容使用 ISO-8859-1 字符集 [ISO-8859-1] 中的文本, 只通过使用 [RFC2047] 编码来支持其他字符集. 在实践中, 大多数 HTTP 头部字段值只使用 US-ASCII 字符集 [USASCII] 的一个子集. 新定义的头部字段 SHOULD 把其字段值限制为 US-ASCII 八位组. 接收方 SHOULD 把字段内容中的其他八位组 (obs-text) 当作不透明数据处理.
3.2.5. 字段长度限制
如第 2.5 节所述, HTTP 没有对每个头部字段的长度或整个头部部分的长度施加预定义限制. 实践中存在各种针对单个头部字段长度的临时性限制, 往往取决于具体的字段语义.
如果服务器收到的某个请求头部字段或某一组字段大于它愿意处理的大小, MUST 返回相应的 4xx (Client Error) 状态码. 忽略这类头部字段会增加服务器受到请求走私攻击 (第 9.5 节) 的脆弱性.
如果字段语义允许在安全忽略被丢弃值的情况下不改变消息分帧或响应语义, 客户端 MAY 丢弃或截断所收到的大于它愿意处理的头部字段.
3.2.6. 字段值组成部分
大多数 HTTP 头部字段值都用一些通用的语法组成部分 (token、quoted-string 和 comment) 来定义, 它们之间以空白或特定定界字符分隔. 定界符选自 token 中不允许出现的 US-ASCII 可见字符集合 (DQUOTE 以及 "(),/:;<=>?@[]{}").
token = 1*tchar
tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*"
/ "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
/ DIGIT / ALPHA
; any VCHAR, except delimiters
如果用双引号括起来, 一段文本会被解析为单个值.
quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE
qdtext = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
obs-text = %x80-FF
在某些 HTTP 头部字段中, 可以把注释文本用圆括号括起来以包含注释. 只有在字段值定义中包含 "comment" 的字段里才允许出现注释.
comment = "(" *( ctext / quoted-pair / comment ) ")"
ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
反斜杠八位组 ("") 可以在 quoted-string 和 comment 构造中用作单八位组引用机制. 处理 quoted-string 值的接收方 MUST 把 quoted-pair 当作已被反斜杠之后的八位组替换掉来处理.
quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )
除为引用该字符串中出现的 DQUOTE 和反斜杠八位组所必需之外, 发送方 SHOULD NOT 在 quoted-string 中生成 quoted-pair. 除为引用该注释中出现的圆括号 ["(" 和 ")"] 和反斜杠八位组所必需之外, 发送方 SHOULD NOT 在注释中生成 quoted-pair.
3.3. 消息主体
HTTP 消息的消息主体 (如果有的话) 用于承载该请求或响应的载荷主体. 除非施加了传输编码 (如第 3.3.1 节所述), 消息主体与载荷主体是相同的.
message-body = *OCTET
关于何时允许消息出现在消息主体中的规则, 请求和响应是不同的.
请求中存在消息主体由 Content-Length 或 Transfer-Encoding 头部字段来表明. 请求消息的分帧与方法语义无关, 即使该方法没有为消息主体定义任何用途.
响应中存在消息主体既取决于它所响应的请求方法, 也取决于响应状态码 (第 3.1.2 节). 对 HEAD 请求方法 ([RFC7231] 第 4.3.2 节) 的响应从不包含消息主体, 因为相关的响应头部字段 (例如 Transfer-Encoding、Content-Length 等) 如果有的话, 只表明 "假如请求方法是 GET ([RFC7231] 第 4.3.1 节) 时它们的值会是什么". 对 CONNECT 请求方法 ([RFC7231] 第 4.3.6 节) 的 2xx (Successful) 响应会切换到隧道模式, 而不是带有消息主体. 所有 1xx (Informational)、204 (No Content) 和 304 (Not Modified) 响应都不包含消息主体. 所有其他响应都包含消息主体, 尽管主体长度可能为零.
3.3.1. Transfer-Encoding
Transfer-Encoding 头部字段列出与 "为构成消息主体而已施加 (或将施加) 于载荷主体之上的一序列传输编码" 相对应的传输编码名称. 传输编码定义在第 4 节.
Transfer-Encoding = 1#transfer-coding
Transfer-Encoding 类似于 MIME 的 Content-Transfer-Encoding 字段, 后者被设计用来在 7 位传输服务上安全传输二进制数据 ([RFC2045] 第 6 节). 然而, 对于一种 8 位洁净 (8bit-clean) 的传输协议而言, 安全传输的关注点有所不同. 在 HTTP 的情况下, Transfer-Encoding 的主要意图是准确界定动态生成的载荷, 并把仅为传输效率或安全而施加的载荷编码与作为所选资源特征的载荷编码区分开来.
接收方 MUST 能够解析分块传输编码 (第 4.1 节), 因为当载荷主体大小事先未知时, 它在消息分帧中起着关键作用. 发送方 MUST NOT 对消息主体多次施加分块编码 (即不允许对已经分块的消息再次分块). 如果对请求载荷主体施加了分块之外的任何传输编码, 发送方 MUST 把分块编码作为最后一个传输编码施加, 以确保消息被正确分帧. 如果对响应载荷主体施加了分块之外的任何传输编码, 发送方 MUST 要么把分块编码作为最后一个传输编码施加, 要么以关闭连接来终止该消息.
例如,
Transfer-Encoding: gzip, chunked
表明载荷主体在构成消息主体的过程中先用 gzip 编码压缩, 再用分块编码分块.
与 Content-Encoding ([RFC7231] 第 3.1.2.1 节) 不同, Transfer-Encoding 是消息的属性而非表示的属性; 请求/响应链上的任何接收方 MAY 解码收到的传输编码, 或者对消息主体施加额外的传输编码, 前提是对 Transfer-Encoding 字段值做出相应修改. 关于编码参数的附加信息可以由本规范未定义的其他头部字段提供.
Transfer-Encoding MAY 在响应 HEAD 请求时发送, 或者在针对 GET 请求的 304 (Not Modified) 响应 ([RFC7232] 第 4.1 节) 中发送; 这两者都不包含消息主体, 这样发送是为了表明: 如果该请求是一次无条件的 GET, 源服务器本会对消息主体施加某种传输编码. 不过这种表明并非必需, 因为响应链上的任何接收方 (包括源服务器) 都可以在不需要时移除传输编码.
服务器 MUST NOT 在任何状态码为 1xx (Informational) 或 204 (No Content) 的响应中发送 Transfer-Encoding 头部字段. 服务器 MUST NOT 在对 CONNECT 请求 ([RFC7231] 第 4.3.6 节) 的任何 2xx (Successful) 响应中发送 Transfer-Encoding 头部字段.
Transfer-Encoding 是在 HTTP/1.1 中加入的. 通常认为, 只声明支持 HTTP/1.0 的实现不会理解如何处理经过传输编码的载荷. 除非知道服务器会处理 HTTP/1.1 (或更晚) 请求, 否则客户端 MUST NOT 发送包含 Transfer-Encoding 的请求; 这种知识可能来自特定的用户配置, 或者来自对先前收到的响应版本的记忆. 除非相应请求表明使用 HTTP/1.1 (或更晚), 否则服务器 MUST NOT 发送包含 Transfer-Encoding 的响应.
如果服务器收到的请求消息中带有它不理解的传输编码, SHOULD 返回 501 (Not Implemented).
3.3.2. Content-Length
当消息没有 Transfer-Encoding 头部字段时, Content-Length 头部字段可以用十进制数给出潜在载荷主体的预期大小 (以八位字节计). 对于确实包含载荷主体的消息, Content-Length 字段值提供必要分帧信息, 以确定主体 (以及消息) 在哪里结束. 对于不包含载荷主体的消息, Content-Length 表明所选表示的大小 ([RFC7231] 第 3 节).
Content-Length = 1*DIGIT
一个例子是
Content-Length: 3495
发送方 MUST NOT 在任何包含 Transfer-Encoding 头部字段的消息中发送 Content-Length 头部字段.
当没有发送 Transfer-Encoding, 且请求方法为所附载荷主体定义了含义时, 用户代理 SHOULD 在请求消息中发送 Content-Length. 例如, POST 请求中通常会发送 Content-Length 头部字段, 即使其值为 0 (表示载荷主体为空). 当请求消息不包含载荷主体, 且方法语义不预期存在此类主体时, 用户代理 SHOULD NOT 发送 Content-Length 头部字段.
服务器 MAY 在对 HEAD 请求 ([RFC7231] 第 4.3.2 节) 的响应中发送 Content-Length 头部字段; 但除非其字段值等于 "假如同一请求使用 GET 方法时该响应的载荷主体本会发送的八位字节十进制数", 否则服务器 MUST NOT 在此类响应中发送 Content-Length.
服务器 MAY 在对条件 GET 请求的 304 (Not Modified) 响应 ([RFC7232] 第 4.1 节) 中发送 Content-Length 头部字段; 但除非其字段值等于 "对同一请求的 200 (OK) 响应的载荷主体本会发送的八位字节十进制数", 否则服务器 MUST NOT 在此类响应中发送 Content-Length.
服务器 MUST NOT 在任何状态码为 1xx (Informational) 或 204 (No Content) 的响应中发送 Content-Length 头部字段. 服务器 MUST NOT 在对 CONNECT 请求 ([RFC7231] 第 4.3.6 节) 的任何 2xx (Successful) 响应中发送 Content-Length 头部字段.
除上述情况之外, 在没有 Transfer-Encoding 的情况下, 如果载荷主体大小在发送完整头部部分之前已知, 源服务器 SHOULD 发送 Content-Length 头部字段. 这能让下游接收方度量传输进度、知道何时已收到完整消息, 并有可能复用连接以发起更多请求.
任何大于或等于零的 Content-Length 字段值都是合法的. 由于对载荷长度没有预定义限制, 接收方 MUST 预料到可能出现很大的十进制数字, 并防止因整数转换溢出而导致的解析错误 (第 9.3 节).
如果收到的消息带有多个 Content-Length 头部字段且其字段值由同一个十进制数组成, 或者带有单个 Content-Length 头部字段且其字段值包含一个由相同十进制值构成的列表 (例如 "Content-Length: 42, 42"), 这表明重复的 Content-Length 头部字段已由上游消息处理器生成或合并; 此时接收方 MUST 要么以消息无效为由予以拒绝, 要么在确定消息主体长度或转发该消息之前, 用包含该十进制值的单个合法 Content-Length 字段替换那些重复的字段值.
注意: HTTP 使用 Content-Length 进行消息分帧的方式, 与该字段在 MIME 中的用法有显著差异; 在 MIME 中它是一个可选字段, 只在 "message/external-body" 媒体类型内使用.
3.3.3. 消息主体长度
消息主体的长度由下列之一确定 (按优先级顺序):
-
对 HEAD 请求的任何响应, 以及任何带有 1xx (Informational)、204 (No Content) 或 304 (Not Modified) 状态码的响应, 总是以头部字段之后的第一个空行终止, 与消息中存在的头部字段无关, 因此不能包含消息主体.
-
对 CONNECT 请求的任何 2xx (Successful) 响应意味着: 在结束头部字段的那个空行之后, 连接将立即变为隧道. 客户端 MUST 忽略在此类消息中收到的任何 Content-Length 或 Transfer-Encoding 头部字段.
-
如果存在 Transfer-Encoding 头部字段, 且分块传输编码 (第 4.1 节) 是最后一个编码, 则消息主体长度通过读取并解码分块数据来确定, 直到该传输编码表明数据已完整.
如果响应中存在 Transfer-Encoding 头部字段, 且分块传输编码不是最后一个编码, 则消息主体长度通过读取连接直到服务器关闭它来确定. 如果请求中存在 Transfer-Encoding 头部字段, 且分块传输编码不是最后一个编码, 则消息主体长度无法可靠确定; 服务器 MUST 返回 400 (Bad Request) 状态码, 然后关闭连接.
如果收到的消息同时带有 Transfer-Encoding 和 Content-Length 头部字段, 则 Transfer-Encoding 覆盖 Content-Length. 此类消息可能表明存在实施请求走私 (第 9.5 节) 或响应分裂 (第 9.4 节) 的企图, 应当作为错误处理. 发送方 MUST 在把此类消息向下游转发之前移除收到的 Content-Length 字段.
-
如果收到的消息没有 Transfer-Encoding, 但带有多个字段值不同的 Content-Length 头部字段, 或者带有单个字段值非法的 Content-Length 头部字段, 则消息分帧无效, 接收方 MUST 视其为不可恢复的错误. 如果这是一条请求消息, 服务器 MUST 返回 400 (Bad Request) 状态码, 然后关闭连接. 如果这是代理收到的一条响应消息, 代理 MUST 关闭到服务器的连接、丢弃收到的响应, 并向客户端发送 502 (Bad Gateway) 响应. 如果这是用户代理收到的一条响应消息, 用户代理 MUST 关闭到服务器的连接并丢弃收到的响应.
-
如果存在合法的 Content-Length 头部字段且没有 Transfer-Encoding, 则其十进制值以八位字节为单位定义了预期的消息主体长度. 如果发送方在收到所表明数量的八位字节之前关闭了连接, 或者接收方超时, 则接收方 MUST 把该消息视为不完整并关闭连接.
-
如果这是一条请求消息且上述情况均不成立, 则消息主体长度为零 (不存在消息主体).
-
否则, 这是一条未声明消息主体长度的响应消息, 因此消息主体长度由服务器关闭连接之前收到的八位字节数确定.
由于无法把一条成功完成的、以关闭连接界定末尾的消息, 与一条因网络故障而中断的部分接收消息区分开来, 服务器 SHOULD 尽可能生成以编码或长度界定末尾的消息. 以关闭连接界定末尾这一特性主要是为了与 HTTP/1.0 保持向后兼容.
服务器 MAY 通过返回 411 (Length Required) 来拒绝一条包含消息主体但没有 Content-Length 的请求.
除非施加了分块之外的传输编码, 否则发送包含消息主体请求的客户端, 如果事先已知消息主体长度, SHOULD 使用合法的 Content-Length 头部字段, 而不是分块传输编码; 因为有些现有服务即使理解分块传输编码, 也会对分块返回 411 (Length Required) 状态码. 这通常是因为这类服务是通过网关实现的, 而该网关在调用之前就需要 content-length, 并且服务器无法或不愿意在处理之前缓冲整个请求.
如果用户代理不知道服务器会处理 HTTP/1.1 (或更晚) 请求, 那么它发送包含消息主体的请求时 MUST 发送合法的 Content-Length 头部字段; 这种知识可能来自特定的用户配置, 或者来自对先前收到的响应版本的记忆.
如果连接上最后一个请求的最终响应已被完整接收, 但仍有多余数据可读, 用户代理 MAY 丢弃这些剩余数据, 或者尝试判定这些数据是否属于先前响应主体的一部分 (当先前消息的 Content-Length 值不正确时可能如此). 客户端 MUST NOT 把这些额外数据处理、缓存或转发为一条独立响应, 因为这种行为容易受到缓存投毒 (cache poisoning) 的攻击.
3.4. 处理不完整消息
收到不完整请求消息的服务器 (通常是由于请求被取消或触发了超时异常) MAY 在关闭连接之前发送一条错误响应.
收到不完整响应消息的客户端 (可能发生在连接被过早关闭时, 或者在解码本应为分块传输编码的数据失败时) MUST 把该消息记录为不完整. 对不完整响应的缓存要求定义在 [RFC7234] 第 3 节中.
如果响应在头部部分中途终止 (在收到空行之前), 而状态码可能依赖头部字段来传达响应的完整含义, 那么客户端不能假定该含义已被传达; 客户端可能需要重复该请求, 以确定下一步应采取什么动作.
如果尚未收到终止分块编码的零长度块, 则使用分块传输编码的消息主体是不完整的. 如果收到的消息主体大小 (以八位字节计) 小于 Content-Length 给出的值, 则使用合法 Content-Length 的消息是不完整的. 既没有分块传输编码也没有 Content-Length 的响应, 以关闭连接来界定末尾; 因此, 只要头部部分是被完整收到的, 无论收到多少个消息主体八位字节, 该响应都视为完整.
3.5. 消息解析健壮性
较早的 HTTP/1.0 用户代理实现可能会在 POST 请求之后发送一个额外的 CRLF, 以此规避某些早期服务器应用无法读取未经行结束符终止的消息主体内容的问题. HTTP/1.1 用户代理 MUST NOT 在请求之前或之后附加额外的 CRLF. 如果希望用行结束符终止请求消息主体, 那么用户代理 MUST 把终止用的 CRLF 八位字节计入消息主体长度.
为了健壮性, 正在等待接收并解析 request-line 的服务器 SHOULD 忽略在 request-line 之前收到的至少一个空行 (CRLF).
尽管起始行和头部字段的行终止符是序列 CRLF, 接收方 MAY 把单个 LF 识别为行终止符并忽略其前面的任何 CR.
尽管 request-line 和 status-line 文法规则要求各个组成元素之间以单个 SP 八位组分隔, 接收方 MAY 改为按以空白分隔的词边界解析, 并且除 CRLF 终止符之外, 把任何形式的空白都当作 SP 分隔符处理, 同时忽略前导或尾随空白; 这类空白包括下列八位组中的一个或多个: SP、HTAB、VT (%x0B)、FF (%x0C) 或裸 CR. 然而, 如果一条消息有多个接收方, 且它们各自对健壮性有自己独特的解释, 那么宽松解析可能导致安全漏洞 (见第 9.5 节).
当一台只监听 HTTP 请求消息的服务器, 或者正在处理 "从起始行看来像是 HTTP 请求消息" 的服务器, 收到一个除上文列出的健壮性例外之外不符合 HTTP-message 文法的八位字节序列时, 该服务器 SHOULD 返回 400 (Bad Request) 响应.