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