6. 消息抽象
HTTP 的每个主要版本都定义了自己的消息通信语法. 本节基于这些消息特征, 公共结构和传达语义的能力的泛化, 为 HTTP 消息定义一种抽象数据类型. 该抽象用于定义独立于 HTTP 版本的发送方和接收方要求, 从而使某个版本中的消息可以经由其他版本中继而不改变其含义.
"消息" (message) 由以下部分组成:
-
描述和路由消息的控制数据,
-
用于扩展该控制数据并传达关于发送方, 消息, 内容或上下文的附加信息的名称/值对 headers 查找表,
-
一个可能无界的内容流, 以及
-
用于传达发送内容期间获得的信息的名称/值对 trailers 查找表.
定帧和控制数据最先发送, 随后是包含 headers 表字段的头部区段. 当消息包含内容时, 内容在头部区段之后发送, 其后可能跟随一个尾部区段, 该区段可能包含 trailers 表字段.
消息预期作为流来处理, 在读取过程中会显现该流的目的及其后续处理方式. 因此, 控制数据描述接收方需要立即知道的内容, 头字段描述接收内容之前需要知道的内容, 内容 (如果存在) 大概包含接收方为满足消息语义所想要或需要的内容, 而尾字段提供发送内容之前未知的可选元数据.
消息意图是 "自描述的": 接收方需要了解的关于消息的一切, 都可以通过查看消息本身来确定, 在解码或重构传输中被压缩或省略的部分之后, 无需理解发送方当前的应用状态 (该状态由先前消息建立). 然而, 客户端在解析, 解释或缓存对应响应时, 必须保留对请求的了解. 例如, 对 HEAD 方法的响应看起来与对 GET 的响应开头完全一样, 但不能以相同方式解析.
注意, 此消息抽象是跨 HTTP 多个版本的泛化, 包括某些版本中可能不存在的特性. 例如, trailers 是在 HTTP/1.1 chunked transfer coding 中作为内容之后的 trailer section 引入的. HTTP/2 和 HTTP/3 中在终止每个流的 header block 内存在等价特性.
6.1. 定帧和完整性
消息定帧指明每个消息如何开始和结束, 以便每个消息能与同一连接上的其他消息或噪声区分开. HTTP 的每个主要版本都定义了自己的定帧机制.
HTTP/0.9 和 HTTP/1.0 的早期部署使用关闭底层连接来结束响应. 为保持向后兼容, HTTP/1.1 也允许这种隐式定帧. 然而, 如果连接提前关闭, 隐式定帧可能无法区分不完整响应. 因此, 几乎所有现代实现都使用显式定帧, 其形式是由长度定界的消息数据序列.
当定帧所指示的所有八位字节都可用时, 消息被认为是 "完整的".注意, 当未使用显式定帧时, 由底层连接关闭结束的响应消息被认为是完整的, 即使它可能无法与不完整响应区分, 除非传输层错误指示其不完整.
6.2. 控制数据
消息以描述其主要目的的控制数据开始. 请求消息控制数据包括请求方法 (Section 9), 请求目标 (Section 7.1) 和协议版本 (Section 2.5). 响应消息控制数据包括状态码 (Section 15), 可选原因短语和协议版本.
在 HTTP/1.1 ([HTTP/1.1]) 及更早版本中, 控制数据作为消息第一行发送. 在 HTTP/2 ([HTTP/2]) 和 HTTP/3 ([HTTP/3]) 中, 控制数据作为带有保留名称前缀的伪头字段发送 (例如 ":authority").
每个 HTTP 消息都有协议版本. 根据所用版本, 它可能在消息中显式标识, 也可能从接收消息所经过的连接推断. 接收方使用该版本信息确定与该发送方后续通信的限制或可能性.
当消息由中介转发时, 协议版本会被更新以反映该中介使用的版本. Via 头字段 (Section 7.6.3) 用于在转发消息中传达上游协议信息.
如果已知服务器支持的最高版本, 客户端应发送等于其所符合最高版本的请求版本, 且该版本主版本号不高于服务器支持的最高版本. 客户端不得发送其不符合的版本.
如果已知服务器错误实现 HTTP 规范, 客户端可以发送较低请求版本, 但前提是客户端至少尝试过一次正常请求, 并从响应状态码或头字段 (例如 Server) 判定服务器不当处理较高请求版本.
服务器应发送等于其所符合最高版本的响应版本, 且该版本主版本号小于或等于请求中收到的主版本号. 服务器不得发送其不符合的版本. 服务器如果出于任何原因希望拒绝为客户端的主协议版本服务, 可以发送 505 (HTTP Version Not Supported) 响应.
接收方收到的消息若主版本号是其实现的版本, 但次版本号高于其实现的版本, 则应按其符合的该主版本内最高次版本来处理该消息. 接收方可以假定, 当较高次版本消息被发送给尚未表明支持该较高版本的接收方时, 该消息足够向后兼容, 可由同一主版本的任何实现安全处理.
6.3. 头字段
在内容之前发送或接收的字段 (Section 5) 称为 "头字段" (header fields, 口语中也称 "headers").
消息的 "头部区段" 由一系列头字段行组成. 每个头字段都可能修改或扩展消息语义, 描述发送方, 定义内容或提供附加上下文.
Note: 当具名字段只允许在头部区段中发送时, 我们专门称其为 "header field".
6.4. 内容
HTTP 消息通常以消息 "内容" (content) 的形式传输完整或部分表示: 即在头部区段之后发送, 由消息定帧界定的八位字节流.
内容的这个抽象定义反映的是从消息定帧中提取后的数据. 例如, HTTP/1.1 message body ([HTTP/1.1] Section 6) 可能由使用 chunked transfer coding 编码的数据流组成, 即一系列数据块, 一个零长度块和一个尾部区段; 而同一消息的内容只包括 transfer coding 解码后的数据流, 不包括块长度, chunked 定帧语法或尾字段 (Section 6.5).
Note: 有些字段名带有 "Content-" 前缀. 这是一种非正式约定; 虽然其中有些字段引用上文定义的消息内容, 但其他字段的作用域是选定表示 (Section 3.2). 请参见各字段定义以消除歧义.
6.4.1. 内容语义
请求中内容的目的由方法语义 (Section 9) 定义.
例如, PUT 请求 (Section 9.3.4) 内容中的表示代表请求成功应用后目标资源的期望状态, 而 POST 请求 (Section 9.3.3) 内容中的表示代表要由目标资源处理的信息.
在响应中, 内容的目的由请求方法, 响应状态码 (Section 15), 以及描述该内容的响应字段定义. 例如, 对 GET (Section 9.3.1) 的 200 (OK) 响应内容代表在消息发起日期 (Section 6.6.1) 时观察到的目标资源当前状态, 而对 POST 的相同状态码响应内容可能代表处理结果, 或代表应用该处理后目标资源的新状态.
对 GET 的 206 (Partial Content) 响应内容包含选定表示的单个部分, 或者包含该表示多个部分的 multipart message body, 如 Section 15.3.7 所述.
带有错误状态码的响应消息通常包含代表错误条件的内容, 使该内容描述错误状态以及建议采取哪些步骤来解决它.
对 HEAD 请求方法 (Section 9.3.2) 的响应从不包含内容; 相关响应头字段仅指示如果请求方法是 GET (Section 9.3.1) 时这些字段本应具有的值.
对 CONNECT 请求方法 (Section 9.3.6) 的 2xx (Successful) 响应会把连接切换到隧道模式, 而不是包含内容.
所有 1xx (Informational),204 (No Content) 和 304 (Not Modified) 响应都不包含内容.
所有其他响应都包含内容, 尽管该内容长度可能为零.
6.4.2. 标识内容
当完整或部分表示作为消息内容传输时, 发送方提供或接收方确定与该特定表示对应的资源标识符通常是有用的. 例如, 客户端对 "当前天气报告" 资源发起 GET 请求时, 可能希望获得特定于返回内容的标识符 (例如 "Laguna Beach at 20210720T1711 的天气报告"). 这对于共享或收藏来自预计会随时间改变表示的资源的内容可能很有用.
对于请求消息:
-
如果请求包含 Content-Location 头字段, 则发送方断言内容是由 Content-Location 字段值所标识资源的表示. 然而, 除非可通过其他方式验证 (本规范未定义), 否则不能信任此类断言. 该信息对修订历史链接可能仍然有用.
-
否则, HTTP 不标识该内容, 但内容本身内部可能提供更具体的标识符.
对于响应消息, 按顺序应用以下规则, 直到找到匹配项:
-
如果请求方法是 HEAD, 或响应状态码是 204 (No Content) 或 304 (Not Modified), 则响应中没有内容.
-
如果请求方法是 GET 且响应状态码是 200 (OK), 则内容是目标资源 (Section 7.1) 的表示.
-
如果请求方法是 GET 且响应状态码是 203 (Non-Authoritative Information), 则内容是由中介提供的目标资源的潜在修改或增强表示.
-
如果请求方法是 GET 且响应状态码是 206 (Partial Content), 则内容是目标资源表示的一个或多个部分.
-
如果响应包含 Content-Location 头字段, 且其字段值是对与目标 URI 相同 URI 的引用, 则内容是目标资源的表示.
-
如果响应包含 Content-Location 头字段, 且其字段值是对不同于目标 URI 的 URI 的引用, 则发送方断言内容是由 Content-Location 字段值所标识资源的表示. 然而, 除非可通过其他方式验证 (本规范未定义), 否则不能信任此类断言.
-
否则, HTTP 不标识该内容, 但内容本身内部可能提供更具体的标识符.
6.5. 尾字段
位于 "尾部区段" (trailer section) 内的字段 (Section 5) 称为 "尾字段" (trailer fields, 口语中也称 "trailers"). 尾字段可用于提供消息完整性检查, 数字签名, 交付指标或后处理状态信息.
尾字段应与头部区段中的字段分开处理和存储, 以避免与头部区段完成时已知的消息语义相矛盾. 某些头字段存在与否, 可能会在收到 trailers 之前影响针对整个消息路由或处理所做的选择; 后续发现尾字段无法撤销这些选择.
6.5.1. Trailers 使用限制
只有当所用 HTTP 版本支持并由显式定帧机制启用时, 才可能存在尾部区段. 例如, HTTP/1.1 中的 chunked transfer coding 允许在内容之后发送尾部区段 ([HTTP/1.1] Section 7.1.2).
许多字段不能在头部区段之外处理, 因为接收内容之前必须评估它们, 例如描述消息定帧, 路由, 认证, 请求修饰符, 响应控制或内容格式的字段. 除非发送方知道对应头字段名的定义允许该字段在 trailers 中发送, 否则发送方不得生成尾字段.
对于在不同协议版本之间转发消息的中介而言, 尾字段可能难以处理. 如果整个消息可以在传输途中缓冲, 一些中介可以在转发前将尾字段合并到头部区段 (如果合适). 然而, 在大多数情况下, trailers 会被简单丢弃. 除非接收方理解对应头字段定义, 且该定义明确允许并定义如何安全合并尾字段值, 否则接收方不得把尾字段合并到头部区段.
请求的 TE 头字段 (Section 10.1.4) 中存在关键词 "trailers", 表明客户端代表自身和任何下游客户端愿意接受尾字段. 对于来自中介的请求, 这意味着所有下游客户端都愿意在转发响应中接受尾字段. 注意, 存在 "trailers" 并不意味着客户端会处理响应中的任何特定尾字段; 只意味着 trailer section 不会被任何客户端丢弃.
由于尾字段可能在传输途中被丢弃, 服务器不应生成其认为用户代理必须接收的尾字段.
6.5.2. 处理尾字段
"Trailer" 头字段 (Section 6.6.2) 可以发送, 用于指示可能在尾部区段中发送的字段, 这使接收方能在处理内容之前为接收它们做好准备. 例如, 如果某个字段名表示应在接收内容时动态计算校验和, 并在收到尾字段值后立即检查, 这会很有用.
与头字段一样, 同名尾字段按接收顺序处理; 同名多个尾字段行的语义等价于把多个值追加为成员列表. 可能在一个消息期间生成多次的尾字段必须定义为基于列表的字段, 即使每个成员值在每个收到的字段行中只处理一次.
在消息末尾, 接收方可以把收到的尾字段集合视为名称/值对数据结构, 类似于头字段但与其分离. 对于意图在 trailers 中使用的字段, 如有附加处理预期, 可以在该字段规范中定义.
6.6. 消息元数据
描述消息本身的字段, 如消息何时以及如何生成, 可以同时出现在请求和响应中.
6.6.1. Date
"Date" 头字段表示消息发起的日期和时间, 其语义与 [RFC5322] Section 3.6.1 中定义的 Origination Date Field (orig-date) 相同. 字段值是 Section 5.6.7 中定义的 HTTP-date.
Date = HTTP-date
示例为
Date: Tue, 15 Nov 1994 08:12:31 GMT
生成 Date 头字段的发送方应把其字段值生成为消息生成日期和时间的最佳可用近似值. 理论上, 该日期应表示生成消息内容之前的瞬间. 实践中, 发送方可以在消息发起期间的任何时间生成该日期值.
具有时钟 (如 Section 5.6.7 所定义) 的源服务器必须在所有 2xx (Successful),3xx (Redirection) 和 4xx (Client Error) 响应中生成 Date 头字段, 并且可以在 1xx (Informational) 和 5xx (Server Error) 响应中生成 Date 头字段.
没有时钟的源服务器不得生成 Date 头字段.
具有时钟的接收方收到不含 Date 头字段的响应消息时, 必须记录其接收时间, 并在该消息被缓存或向下游转发时向消息头部区段追加对应的 Date 头字段.
具有时钟的接收方收到带有无效 Date 头字段值的响应时, 可以用该响应收到的时间替换该值.
用户代理可以在请求中发送 Date 头字段, 但通常不会这样做, 除非认为它能向服务器传达有用信息. 例如, HTTP 的定制应用可能会传达 Date, 如果预期服务器会根据用户代理和服务器时钟差异调整其对用户请求的解释.
6.6.2. Trailer
"Trailer" 头字段提供一个字段名列表, 这些字段名是发送方预期在该消息中作为尾字段发送的字段. 这允许接收方在开始处理内容前为接收所指示的元数据做好准备.
Trailer = #field-name
例如, 发送方可能指示将在内容流式传输过程中计算签名, 并把最终签名作为尾字段提供. 这允许接收方在接收内容时即时执行相同检查.
打算在消息中生成一个或多个尾字段的发送方, 应在该消息的头部区段中生成 Trailer 头字段, 以指示 trailers 中可能出现哪些字段.
如果中介在传输途中丢弃尾部区段, Trailer 字段可以提供关于丢失了哪些元数据的提示, 尽管不能保证 Trailer 的发送方总会通过发送所命名字段来兑现该提示.