Skip to main content

2. Architecture (架构)

HTTP 最初设计是作为一种面向文档的信息系统的应用层协议。HTTP 的架构反映了这种设计, 将其定义为一个简单的请求/响应协议: 客户端向服务器发送包含请求方法、URI 和协议版本的请求消息, 随后是包含请求修饰符、客户端信息和可能的表示内容的类似 MIME 的消息; 服务器则以包含消息协议版本、成功或错误代码的状态行响应, 随后是包含服务器信息、实体元信息和可能的实体内容的类似 MIME 的消息。

HTTP 独立于底层传输层或会话层连接协议。HTTP 只假设存在一个可靠的传输层, 能够保证请求和响应的顺序传递 (即, 请求在相应的响应之前被完全接收)。HTTP/1.1 消息到底层传输数据单元的映射超出了本规范的范围。

HTTP 已经在各种环境中使用, 从封闭的企业网络到开放的互联网, 从嵌入式设备到大型多处理器系统, 从自动化代理到普通用户的网页浏览器, 从物理上靠近服务器的客户端到跨越多个中间方和网络的客户端。在所有这些环境中, 实现必须仔细设计以避免或缓解与 HTTP 语义相关的攻击。

2.1. Client/Server Messaging (客户端/服务器消息传递)

HTTP 是一个无状态的请求/响应协议, 通过在客户端和服务器之间交换消息来运行。

HTTP-message   = start-line
*( header-field CRLF )
CRLF
[ message-body ]

客户端 (client) 发送一个 HTTP 请求 (request) 到服务器, 其形式是一个请求方法、URI 和协议版本, 随后是一个包含请求修饰符、客户端信息和可能的表示内容的类似 MIME 的消息。服务器 (server) 以一个或多个 HTTP 响应 (response) 回复, 其形式是一个状态行, 包括消息协议版本和成功或错误代码, 随后是一个包含服务器信息、实体元信息和可能的表示内容的类似 MIME 的消息。

客户端与服务器之间的连接可能由任何一方关闭。例如, 客户端在接收到完整响应后可能会关闭连接, 或者服务器可能在超时期限到期时关闭连接。

通常, HTTP 通信由客户端与特定服务器上的特定端口建立 TCP/IP 连接来初始化。在 HTTP/1.1 中, 连接的建立由 URI 方案隐含。"http" 方案 (Section 2.7.1) 用于通过 TCP/IP 连接定位网络资源, 其中默认使用 TCP 端口 80, 但也可以使用其他端口。这与其他需要固定端口号的协议不同。

一旦建立了连接, 客户端就会向服务器发送一个 HTTP 请求消息:

request-line   = method SP request-target SP HTTP-version CRLF

方法标记 (method token) 指示要在目标资源上执行的请求方法。请求方法是区分大小写的, 并在 [RFC7231] 的 [Section 4] 中定义。

method         = token

请求目标 (request-target) 标识应用请求的目标资源。更多细节在 Section 5.3 中描述。

request-target = origin-form
/ absolute-form
/ authority-form
/ asterisk-form

HTTP-version 表示发送方符合的协议版本, 其规则在 Section 2.6 中定义。

在接收并解释请求消息后, 服务器以一个或多个 HTTP 响应消息进行响应:

status-line    = HTTP-version SP status-code SP reason-phrase CRLF

HTTP 消息的第一行称为"起始行" (start-line)。虽然请求消息和响应消息之间存在差异, 但总体上消息格式是相同的 (Section 3)。

2.2. Implementation Diversity (实现多样性)

当我们谈论 HTTP 的"客户端"或"服务器"时, 我们指的是该程序在特定连接中扮演的角色, 而不是程序一般能力方面的标签。一个给定的程序可能在某些连接中充当客户端, 而在其他连接中充当服务器。术语"用户代理" (user agent) 指的是初始化请求的任何程序, 包括 (但不限于) 浏览器、爬虫 (web-traversing robots)、命令行工具、定制应用程序和移动应用程序。术语"源服务器" (origin server) 指的是能够为给定目标资源提供权威响应的程序。术语"发送方" (sender) 和"接收方" (recipient) 指的是给定消息传输的任何实现: 发送方发送消息, 接收方接收消息。

HTTP 依赖于统一资源标识符 (Uniform Resource Identifiers, URI) 标准 [RFC3986] 来指示目标资源 (Section 5.1) 和资源之间的关系。

消息通过类似于互联网邮件 [RFC5322] 和多用途互联网邮件扩展 (Multipurpose Internet Mail Extensions, MIME) [RFC2045] 中使用的格式传递。

大多数 HTTP 通信由用户代理发起请求以获取或修改源服务器上的资源组成。最简单的情况下, 这可以通过用户代理 (UA) 和源服务器 (O) 之间的单个双向连接来完成:

request   >
UA ======================================= O
< response

客户端向服务器发送请求, 其形式是一个起始行, 包含方法、请求 URI 和协议版本, 随后是包含请求修饰符、客户端信息和可能的内容的头部字段, 以及潜在的消息正文。

服务器以一个或多个响应消息进行响应, 每个消息包含一个起始行, 其中包含协议版本、成功或错误代码, 随后是包含服务器信息、实体元信息和可能的实体内容的头部字段。

2.3. Intermediaries (中间方)

HTTP 允许使用中间方 (intermediaries) 来满足请求, 方式是通过可能的翻译链传递请求。有三种常见的 HTTP 中间方形式: 代理 (proxy)、网关 (gateway) 和隧道 (tunnel)。在某些情况下, 单个中间方可能会根据每个请求的性质表现为源服务器、代理、网关或隧道。

  >             >             >             >
UA =========== A =========== B =========== C =========== O
< < < <

上图显示了用户代理和源服务器之间的三个中间方 (A、B 和 C)。穿过整个请求/响应链的消息使用四个单独的连接。某些 HTTP 通信选项可能仅适用于与最近的非隧道邻居的连接、链的端点或链上的所有连接。虽然图表是线性的, 但每个参与者可能参与多个并发的 HTTP 通信。例如, B 可能从除 A 之外的许多客户端接收请求, 和/或将请求转发到除 C 之外的服务器。

代理 (Proxy) 是一个由客户端选择的消息转发代理, 通常通过本地配置规则, 来接收对某些类型绝对 URI 的请求并尝试通过翻译 (如果必要) 经由 HTTP 接口来满足这些请求。一些翻译是最小的, 如对 "http" URI 的代理请求, 而其他翻译可能需要在完全不同的应用层协议之间进行翻译。代理通常用于通过不受信任的中间方进行组通信、注释请求或响应, 以及通过共享缓存提供对网络的访问。一些代理被设计来应用转换到选定的消息或有效载荷, 如 Section 5.7.2 中所述。

网关 (Gateway) (也称为"反向代理" - reverse proxy) 是一个充当源服务器层前端的中间方, 代表源服务器接收请求, 并在必要时将请求翻译成源服务器的协议来满足请求。网关通常用于封装遗留或不受信任的信息服务、通过"加速器"缓存提高服务器性能, 以及在 HTTP 上启用跨多个机器的分区或负载均衡。

所有对给定资源的请求有效 URI 的 HTTP 要求都针对该资源的源服务器。然而, 如果网络中的其他服务器具有该 URI 的共享缓存, 它们可能能够满足请求。HTTP 缓存语义在 [RFC7234] 中定义。

隧道 (Tunnel) 充当两个连接之间的盲中继, 不更改消息。一旦激活, 隧道就不被认为是 HTTP 通信的一方, 尽管隧道可能由 HTTP 请求初始化。当中继连接在两端都终止时, 隧道就不复存在。隧道用于通过 HTTP 连接扩展虚拟连接, 例如通过共享防火墙传递 TLS (Transport Layer Security, 传输层安全) 信息。

上述中间方的分类仅基于它们参与特定消息时的行为。一个中间方可能会根据每个请求的性质表现为源服务器、代理、网关或隧道。例如, 充当 "http://example.com/foo" 加速器缓存的源服务器可能会将对 "http://example.net/bar" 的请求代理到另一个服务器, 同时仍然响应对 "http://example.com/zzz" 的请求, 就好像它是源服务器一样。

2.4. Caches (缓存)

缓存 (cache) 是之前响应消息的本地存储, 以及控制其存储、检索和删除的子系统。缓存存储可缓存的响应以减少未来等效请求的响应时间和网络带宽消耗。任何客户端或服务器都可能包括缓存, 但缓存不能被充当隧道的服务器使用。

缓存的效果是请求/响应链被缩短, 如果链上的参与者之一具有适用的缓存响应。以下说明了在 B 具有来自 O (通过 C) 的先前响应的缓存副本时产生的链:

  >             >
UA =========== A =========== B - - - - - - C - - - - - - O
< <

响应是"可缓存的" (cacheable), 如果缓存被允许存储响应消息的副本以用于回答后续请求。即使资源在源服务器上可用, 并且客户端愿意接受过时的响应, 缓存仍然可能不使用副本来满足特定请求。有关 HTTP 缓存行为和可缓存响应的更多详细信息, 请参阅 [RFC7234]。

当缓存被用作响应生成器时, 存在一种风险, 即缓存将分发过时的 (即, 不匹配源服务器当前打算发送的内容) 或不适当的响应。[RFC7234] 指定了对缓存的要求, 并定义了用于控制缓存行为和指示可缓存响应的机制。

2.5. Conformance and Error Handling (一致性与错误处理)

本规范针对发送方、接收方、客户端、服务器、用户代理、中间方、源服务器、代理、网关和缓存的角色定义了一致性标准。

实现可能需要满足多个角色的要求。例如, 源服务器可能包含代理组件 (用于与其他服务器通信) 和缓存组件 (用于存储可缓存响应)。但是, 并非 HTTP 的所有方面都适用于每个角色 (尽管本规范可能将这些方面称为 HTTP 通信中某个协议元素的通用要求)。因此, 根据发送方或接收方在该通信的角色, 给定消息的一致性标准可能会有所不同。

发送消息的实现 (即发送方) 必须仅发送符合该发送方角色对该消息的协议元素的语法和语义的消息。当发送方检测到或接收到协议错误的指示时, 发送方应该生成错误响应, 其中包含适当的 4xx (Client Error, 客户端错误) 或 5xx (Server Error, 服务器错误) 状态码 ([RFC7231], [Section 6]), 如协议对该发送方角色所定义的那样。

接收并解析消息的实现 (即接收方) 必须仅将消息作为符合该接收方角色对消息的协议元素的语法和语义的消息处理。如果接收到的消息似乎不符合发送方的语法要求, 或者在消息解析过程中检测到语义无效, 则接收方应该发送包含适当错误状态码的响应 (对于收到请求的服务器) 或丢弃消息并关闭连接 (对于所有接收方)。

除非另有规定, 接收方可以尝试从无效的消息中恢复可用的协议元素。HTTP 不定义针对错误的特定恢复机制, 除了那些与消息框架 (Section 3) 和请求语义 (Section 5) 相关的机制。

注意: 之前版本的 HTTP 允许接收方在某些情况下"应该"或"可以"检测和恢复某些协议错误。然而, 此类指导往往导致可利用的安全漏洞, 尤其是在通过多个协议层或中间方传递消息时。因此, 一个接收方应该拒绝无效的消息。在某些情况下, 客户端可能能够在获取更多信息后重试请求, 例如在初始连接尝试因服务器不接受所呈现的 TLS 版本或密码套件而失败后。

2.6. Protocol Versioning (协议版本控制)

HTTP 使用 "<major>.<minor>" 编号方案来指示协议的版本。本规范定义了版本 "1.1"。协议版本作为一个整体指示发送方对 HTTP 规范的一致性集的符合性, 包括对请求或响应通信要求的理解。

版本号由两个小数位组成, 由一个"."(句点或小数点) 分隔。第一个数字 ("major version", 主版本号) 表示 HTTP 消息语法, 而第二个数字 ("minor version", 次版本号) 表示在该主版本内的最高次版本, 发送方符合该版本并能够理解未来通信。次版本宣传发送方的通信能力, 即使发送方仅使用向后兼容的协议子集, 从而让接收方知道可以使用更高级的功能 (如果有的话)。

当 HTTP/1.1 消息被发送到 HTTP/1.0 接收方 [RFC1945] 或者版本未知的接收方时, HTTP/1.1 消息被构造成在消息被解析为 HTTP/1.0 的情况下仍可以被解释为有效的 HTTP/1.0 消息, 尽管这取决于接收方理解 HTTP/1.0 扩展。HTTP/1.1 为所有接收方提供了一种机制来识别自己的消息版本。

接收到声称为 HTTP/1.1 的请求或响应的实现应该将其作为 HTTP/1.1 消息处理, 即使发送方在选择头部字段、状态短语或请求目标格式方面不完全符合本规范。降级到 HTTP/1.0 不应该被执行, 除非被配置明确要求。通常, 这样的配置仅在识别到特定实现在响应 HTTP/1.1 时有缺陷并且这些缺陷无法通过其他更具体的机制解决时才会发生。

2.7. Uniform Resource Identifiers (统一资源标识符)

统一资源标识符 (Uniform Resource Identifiers, URI) [RFC3986] 在整个 HTTP 中被用作标识资源的手段 ([RFC7231], [Section 2])。URI 引用用于定位请求 (Section 5.1), 指示重定向 ([RFC7231], [Section 6.4]), 以及定义关系 ([RFC7231], [Section 5.1])。

URI 定义在 [RFC3986] 中如下:

URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

绝对 URI (absolute-URI) 形式在某些上下文中需要使用, 其中不允许使用片段标识符:

absolute-URI  = scheme ":" hier-part [ "?" query ]

片段标识符的语法和语义在 [RFC3986], [Section 3.5] 中定义。

部分 URI (partial-URI) 形式由相对部分和可选查询组成:

partial-URI   = relative-part [ "?" query ]

2.7.1. http URI Scheme (http URI 方案)

"http" URI 方案在此定义, 用于通过 HTTP 协议标识可访问的资源。

http-URI    = "http:" "//" authority path-abempty [ "?" query ]
[ "#" fragment ]

http URI 的源服务器由 authority 组件标识, 其中包括主机标识符和可选的 TCP 端口号 ([RFC3986], [Section 3.2.2])。hier-part 的 path-abempty 规则 ([RFC3986], [Section 3.3]) 和 query 组件 ([RFC3986], [Section 3.4]) 一起标识了该源服务器范围内的目标资源。如果端口子组件为空或未给出, TCP 端口 80 (万维网的保留端口) 是默认值。

注意: 虽然 URI 语法不区分大小写 ([RFC3986], [Section 6.2.1]), 但通常情况下, http-URI 的 scheme 和 authority 组件是不区分大小写的。然而, path 和 query 组件的大小写敏感性取决于源服务器的实现。

2.7.2. https URI Scheme (https URI 方案)

"https" URI 方案在此定义, 用于通过加密连接标识可访问的资源。

https-URI   = "https:" "//" authority path-abempty [ "?" query ]
[ "#" fragment ]

https URI 的语义与 http URI 的语义等效, 但其使用指示需要在客户端和目标服务器拥有的直接连接或最终隧道连接上进行额外的传输层安全性 (如 [RFC2818] 中定义的)。

如果端口子组件为空或未给出, TCP 端口 443 (HTTPS 的保留端口) 是默认值。

资源标识符比较 ([RFC3986], [Section 6]) 在应用于 https URI 时与 http URI 完全相同, 除了端口的默认值不同。

2.7.3. http and https URI Normalization and Comparison (http 和 https URI 的规范化与比较)

由于 "http" 和 "https" 方案符合 [RFC3986] 定义的通用语法, 因此此类 URI 引用的规范化和比较在 [RFC3986], [Section 6] 中定义的算法下执行, 使用每个方案各自的默认端口 (80 用于 "http", 443 用于 "https")。

如果端口等于该 URI 方案的默认端口, 则在 URI 的比较之前, 应该从 URI 的规范形式中省略正常形式。当不存在端口组件时, 空端口等效于该 URI 方案的默认端口。例如, 以下三个 URI 是等效的:

http://example.com:80/~smith/home.html
http://example.com/~smith/home.html
http://example.com/%7Esmith/home.html

📍 翻译进度: Section 2 完成

下一章节: Section 3. Message Format (消息格式)

请回复 "继续" 以继续翻译下一章节。