跳到主要内容

6. 消息体

HTTP/1.1 消息的消息体 (message body, 如有) 用于承载请求或响应的内容 ([HTTP] 的 Section 6.4). 除非已应用传输编码 (transfer coding), 如 Section 6.1 所述, 否则消息体与内容相同.

message-body = *OCTET

确定 HTTP/1.1 消息中何时存在消息体的规则, 对请求和响应有所不同.

请求中是否存在消息体由 Content-Length 或 Transfer-Encoding 头部字段指示. 请求消息成帧独立于方法语义.

如 Section 6.3 所详述, 响应中是否存在消息体同时取决于它所响应的请求方法和响应状态码. 这对应于 HTTP 语义允许响应内容出现的时机 ([HTTP] 的 Section 6.4.1).

6.1. Transfer-Encoding

Transfer-Encoding 头部字段列出传输编码名称, 对应于为了形成消息体而已经 (或将要) 应用于内容的一系列传输编码. 传输编码在 Section 7 中定义.

Transfer-Encoding = #transfer-coding
; defined in [HTTP], Section 10.1.4

Transfer-Encoding 类似于 MIME 的 Content-Transfer-Encoding 字段, 后者旨在让二进制数据能够安全地通过 7 位传输服务传输 ([RFC2045], Section 6). 然而, 对于一个 8bit-clean 的传输协议, 安全传输的侧重点不同. 在 HTTP 中, Transfer-Encoding 主要意在准确界定动态生成的内容. 它还用于区分仅在传输过程中应用的编码, 与所选表示本身特征的编码.

接收方 MUST 能够解析 chunked 传输编码 (Section 7.1), 因为当内容大小事先未知时, 它在消息成帧中起关键作用. 发送方 MUST NOT 对一个消息体多次应用 chunked 传输编码 (即不允许对已经分块的消息再次分块). 如果请求内容应用了 chunked 以外的任何传输编码, 发送方 MUST 将 chunked 作为最终传输编码应用, 以确保消息被正确成帧. 如果响应内容应用了 chunked 以外的任何传输编码, 发送方 MUST 要么将 chunked 作为最终传输编码应用, 要么通过关闭连接来终止消息.

例如:

Transfer-Encoding: gzip, chunked

表示内容先使用 gzip 编码压缩, 然后在形成消息体时使用 chunked 编码分块.

不同于 Content-Encoding ([HTTP] 的 Section 8.4.1), Transfer-Encoding 是消息的属性, 而不是表示的属性. 请求/响应链上的任何接收方 MAY 解码收到的一个或多个传输编码, 或对消息体应用额外的一个或多个传输编码, 前提是对 Transfer-Encoding 字段值做出相应变更. 关于编码参数的附加信息可以由本规范未定义的其他头部字段提供.

Transfer-Encoding MAY 出现在对 HEAD 请求的响应中, 或者出现在对 GET 请求的 304 (Not Modified) 响应 ([HTTP] 的 Section 15.4.5) 中; 这两者都不包含消息体, 但可用于指示如果该请求是无条件 GET, 源服务器本会对消息体应用某种传输编码. 不过, 这种指示并非必需, 因为响应链上的任何接收方 (包括源服务器) 都可以在不需要传输编码时将其移除.

服务器 MUST NOT 在任何状态码为 1xx (Informational) 或 204 (No Content) 的响应中发送 Transfer-Encoding 头部字段. 服务器 MUST NOT 在对 CONNECT 请求 ([HTTP] 的 Section 9.3.6) 的任何 2xx (Successful) 响应中发送 Transfer-Encoding 头部字段.

服务器收到带有其不理解的传输编码的请求消息时, SHOULD 以 501 (Not Implemented) 响应.

Transfer-Encoding 是在 HTTP/1.1 中加入的. 通常假定只宣告支持 HTTP/1.0 的实现不会理解如何处理 transfer-encoded 内容, 并且收到带有 Transfer-Encoding 的 HTTP/1.0 消息很可能是在传输过程中未经正确处理 chunked 传输编码就被转发的.

客户端 MUST NOT 发送包含 Transfer-Encoding 的请求, 除非它知道服务器会处理 HTTP/1.1 请求 (或后续次版本); 这种认知可以来自特定用户配置, 或来自记住所收到的先前响应版本. 服务器 MUST NOT 发送包含 Transfer-Encoding 的响应, 除非对应请求指示 HTTP/1.1 (或后续次版本).

Transfer-Encoding 的早期实现有时会同时发送用于消息成帧的 chunked 传输编码, 以及供进度条使用的估计 Content-Length 头部字段. 这就是 Transfer-Encoding 被定义为覆盖 Content-Length 的原因, 而不是二者互不兼容. 遗憾的是, 如果任何下游接收方未按本规范解析消息, 特别是当某个下游接收方只实现 HTTP/1.0 时, 转发这样的消息可能导致与请求走私 (Section 11.2) 或响应拆分 (Section 11.1) 攻击相关的漏洞.

服务器 MAY 拒绝同时包含 Content-Length 和 Transfer-Encoding 的请求, 或者仅根据 Transfer-Encoding 处理此类请求. 无论如何, 服务器在响应此类请求后 MUST 关闭连接, 以避免潜在攻击.

收到包含 Transfer-Encoding 头部字段的 HTTP/1.0 消息的服务器或客户端 MUST 将该消息视为成帧有误, 即使存在 Content-Length, 并在处理该消息后关闭连接. 消息发送方可能在缓冲区中保留了该消息的一部分, 后续复用连接时可能会误解这部分数据.

6.2. Content-Length

当消息没有 Transfer-Encoding 头部字段时, Content-Length 头部字段 ([HTTP] 的 Section 8.6) 可以用十进制八位组数量提供潜在内容的预期大小. 对于确实包含内容的消息, Content-Length 字段值提供确定数据 (以及消息) 结束位置所需的成帧信息. 对于不包含内容的消息, Content-Length 指示所选表示的大小 ([HTTP] 的 Section 8.6).

发送方 MUST NOT 在任何包含 Transfer-Encoding 头部字段的消息中发送 Content-Length 头部字段.

Note: HTTP 将 Content-Length 用于消息成帧的方式, 与 MIME 中同一字段的用途有显著差异; 在 MIME 中, 它是仅用于 "message/external-body" 媒体类型内的可选字段.

6.3. 消息体长度

消息体长度按以下规则确定 (按优先级顺序):

  1. 对 HEAD 请求的任何响应, 以及状态码为 1xx (Informational), 204 (No Content) 或 304 (Not Modified) 的任何响应, 始终由头部字段之后的第一个空行终止, 无论消息中存在什么头部字段, 因而不能包含消息体或 trailer section.

  2. 对 CONNECT 请求的任何 2xx (Successful) 响应意味着连接会在结束头部字段的空行之后立即成为隧道. 客户端 MUST 忽略此类消息中收到的任何 Content-Length 或 Transfer-Encoding 头部字段.

  3. 如果收到的消息同时带有 Transfer-Encoding 和 Content-Length 头部字段, Transfer-Encoding 会覆盖 Content-Length. 这样的消息可能表明存在请求走私 (Section 11.2) 或响应拆分 (Section 11.1) 尝试, 并且应作为错误处理. 选择转发该消息的中间件 MUST 先移除收到的 Content-Length 字段, 并在向下游转发消息之前处理 Transfer-Encoding (如下所述).

  4. 如果存在 Transfer-Encoding 头部字段, 且 chunked 传输编码 (Section 7.1) 是最终编码, 则消息体长度通过读取并解码 chunked 数据来确定, 直到传输编码指示数据完成.

    如果响应中存在 Transfer-Encoding 头部字段, 但 chunked 传输编码不是最终编码, 则消息体长度通过读取连接直到服务器关闭连接来确定.

    如果请求中存在 Transfer-Encoding 头部字段, 但 chunked 传输编码不是最终编码, 则消息体长度无法可靠确定; 服务器 MUST 以 400 (Bad Request) 状态码响应, 然后关闭连接.

  5. 如果收到的消息没有 Transfer-Encoding, 且带有无效的 Content-Length 头部字段, 则消息成帧无效, 接收方 MUST 将其视为不可恢复错误, 除非该字段值可以成功解析为逗号分隔列表 ([HTTP] 的 Section 5.6.1), 列表中的所有值均有效, 且列表中的所有值都相同 (在这种情况下, 以该单个值作为 Content-Length 字段值处理消息). 如果不可恢复错误出现在请求消息中, 服务器 MUST 以 400 (Bad Request) 状态码响应, 然后关闭连接. 如果它出现在代理收到的响应消息中, 代理 MUST 关闭到服务器的连接, 丢弃收到的响应, 并向客户端发送 502 (Bad Gateway) 响应. 如果它出现在用户代理收到的响应消息中, 用户代理 MUST 关闭到服务器的连接并丢弃收到的响应.

  6. 如果存在有效的 Content-Length 头部字段且没有 Transfer-Encoding, 其十进制值定义预期消息体长度, 单位为八位组. 如果发送方在收到所指示数量的八位组之前关闭连接, 或接收方在此之前超时, 接收方 MUST 将该消息视为不完整并关闭连接.

  7. 如果这是请求消息, 且以上条件均不成立, 则消息体长度为零 (不存在消息体).

  8. 否则, 这是一个没有声明消息体长度的响应消息, 因此消息体长度由服务器关闭连接之前收到的八位组数量确定.

由于无法区分一个成功完成的 close-delimited 响应消息和一个因网络故障而中断的部分接收消息, 服务器 SHOULD 尽可能生成以编码或长度定界的消息. close-delimiting 特性主要是为了向后兼容 HTTP/1.0 而存在.

Note: 请求消息从不以关闭连接定界, 因为它们总是通过长度或传输编码显式成帧; 二者都不存在时, 表示请求在头部区段之后立即结束.

服务器 MAY 拒绝包含消息体但没有 Content-Length 的请求, 方法是以 411 (Length Required) 响应.

除非已应用 chunked 以外的传输编码, 否则发送包含消息体的请求的客户端, 如果事先知道消息体长度, SHOULD 使用有效的 Content-Length 头部字段, 而不是 chunked 传输编码, 因为一些现有服务即使理解 chunked 传输编码, 也会对 chunked 响应 411 (Length Required) 状态码. 这通常是因为此类服务通过网关实现, 而网关要求在调用前预先知道内容长度, 且服务器无法或不愿在处理前缓冲整个请求.

发送包含消息体的请求的用户代理 MUST 发送有效的 Content-Length 头部字段, 或者使用 chunked 传输编码. 客户端 MUST NOT 使用 chunked 传输编码, 除非它知道服务器会处理 HTTP/1.1 (或更高版本) 请求; 这种认知可以来自特定用户配置, 或来自记住所收到的先前响应版本.

如果连接上最后一个请求的最终响应已完整接收, 且仍有额外数据可读, 用户代理 MAY 丢弃剩余数据, 或尝试判断这些数据是否属于先前消息体的一部分; 如果先前消息的 Content-Length 值不正确, 就可能出现这种情况. 客户端 MUST NOT 将此类额外数据作为单独响应进行处理, 缓存或转发, 因为这种行为容易受到缓存投毒攻击.