5. 消息路由
HTTP 请求消息的路由由每个客户端依据目标资源、客户端的代理配置, 以及入站连接的建立或复用来确定. 相应的响应路由沿同一条连接链回到客户端.
5.1. 标识目标资源
HTTP 被用于各种各样的应用, 从通用计算机到家用电器. 在某些情况下, 通信选项被硬编码在客户端配置中. 然而, 大多数 HTTP 客户端依赖与通用 Web 浏览器相同的资源标识机制和配置技术.
HTTP 通信由用户代理出于某种目的而发起. 该目的是请求语义 (定义于 [RFC7231]) 与 "施加这些语义的目标资源" 两者的组合. URI 引用 (第 2.7 节) 通常被用作 "目标资源" 的标识符; 用户代理会把该引用解析为其绝对形式, 以获得 "目标 URI". 目标 URI 排除该引用中的片段分量 (如果有的话), 因为片段标识符保留给客户端本地处理 ([RFC3986] 第 3.5 节).
5.2. 入站连接
一旦确定了目标 URI, 客户端就需要判断: 完成所需语义是否必须发起网络请求; 如果需要, 该请求应被送往何处.
如果客户端有缓存 [RFC7234], 且该请求能由缓存满足, 那么请求通常首先被送往缓存.
如果请求不能由缓存满足, 那么典型客户端会检查自己的配置, 以确定是否应使用代理来满足该请求. 代理配置取决于实现, 但通常基于 URI 前缀匹配、选择性的 authority 匹配, 或者两者兼有; 代理本身通常由一个 "http" 或 "https" URI 标识. 如果存在适用的代理, 客户端就通过建立 (或复用) 到该代理的连接来进行入站连接.
如果没有适用的代理, 那么典型客户端会调用一个处理程序例程 (通常特定于目标 URI 的方案), 以直接连接到目标资源的某个 authority. 具体如何完成取决于目标 URI 方案, 并由其关联的规范定义; 这类似于本规范为解析 "http" (第 2.7.1 节) 和 "https" (第 2.7.2 节) 方案而定义源服务器访问方式的做法.
关于连接管理的 HTTP 要求定义在第 6 节.
5.3. 请求目标
一旦获得入站连接, 客户端就发送一条 HTTP 请求消息 (第 3 节), 其中带有由目标 URI 导出的请求目标. 请求目标有四种不同的格式, 具体取决于所请求的方法以及请求是否发往代理.
request-target = origin-form
/ absolute-form
/ authority-form
/ asterisk-form
5.3.1. origin-form
最常见的请求目标形式是 origin-form.
origin-form = absolute-path [ "?" query ]
当直接向源服务器发起请求时 (CONNECT 或服务器范围的 OPTIONS 请求除外, 详见下文), 客户端 MUST 只把目标 URI 的绝对路径和查询分量作为请求目标发送. 如果目标 URI 的路径分量为空, 客户端 MUST 在 origin-form 请求目标中发送 "/" 作为路径. 同时还要发送 Host 头部字段, 如第 5.4 节所定义.
例如, 希望获取标识为
http://www.example.org/where?q=now
的资源的表示, 并直接从源服务器获取的客户端, 会打开 (或复用) 一条到主机 "www.example.org" 80 端口的 TCP 连接, 并发送如下各行:
GET /where?q=now HTTP/1.1
Host: www.example.org
随后发送请求消息的其余部分.
5.3.2. absolute-form
当向代理发起请求时 (CONNECT 或服务器范围的 OPTIONS 请求除外, 详见下文), 客户端 MUST 以 absolute-form 发送目标 URI 作为请求目标.
absolute-form = absolute-URI
代理被要求尽可能从有效的缓存中满足该请求, 或者代表客户端把同样的请求发往下一个入站代理服务器, 或者直接发往由请求目标指明的源服务器. 关于这类消息 "转发" 的要求定义在第 5.7 节.
一个 absolute-form 请求行的例子是:
GET http://www.example.org/pub/WWW/TheProject.html HTTP/1.1
为了便于在未来某个 HTTP 版本中让所有请求都过渡到 absolute-form, 服务器 MUST 接受请求中的 absolute-form, 尽管 HTTP/1.1 客户端只会在发往代理的请求中发送这种形式.
5.3.3. authority-form
请求目标的 authority-form 只用于 CONNECT 请求 ([RFC7231] 第 4.3.6 节).
authority-form = authority
当发起 CONNECT 请求以穿过一个或多个代理建立隧道时, 客户端 MUST 只把目标 URI 的 authority 分量 (排除任何 userinfo 及其 "@" 定界符) 作为请求目标发送. 例如,
CONNECT www.example.com:80 HTTP/1.1
5.3.4. asterisk-form
请求目标的 asterisk-form 只用于服务器范围的 OPTIONS 请求 ([RFC7231] 第 4.3.7 节).
asterisk-form = "*"
当客户端希望对整个服务器 (而不是该服务器的某个具体命名资源) 请求 OPTIONS 时, MUST 只把 "*" (%x2A) 作为请求目标发送. 例如,
OPTIONS * HTTP/1.1
如果代理收到一个绝对形式的请求目标且其中 URI 的路径为空、没有查询分量, 那么请求链上的最后一个代理在把该请求转发给所指源服务器时, MUST 发送 "*" 作为请求目标.
例如, 请求
OPTIONS http://www.example.org:8001 HTTP/1.1
会被最终代理在连接到主机 "www.example.org" 的 8001 端口之后, 转发为
OPTIONS * HTTP/1.1
Host: www.example.org:8001
5.4. Host
请求中的 "Host" 头部字段提供来自目标 URI 的主机和端口信息, 使源服务器在为单个 IP 地址上的多个主机名服务请求时能够区分资源.
Host = uri-host [ ":" port ] ; Section 2.7.1
客户端 MUST 在所有 HTTP/1.1 请求消息中发送 Host 头部字段. 如果目标 URI 包含 authority 分量, 那么客户端 MUST 发送与该作者分量完全相同的 Host 字段值, 但排除任何 userinfo 子分量及其 "@" 定界符 (第 2.7.1 节). 如果目标 URI 缺少或未定义 authority 分量, 那么客户端 MUST 发送字段值为空的 Host 头部字段.
由于 Host 字段值对处理请求而言是关键信息, 用户代理 SHOULD 把 Host 生成为紧随请求行之后的第一个头部字段.
例如, 对 http://www.example.org/pub/WWW/ 的源服务器的 GET 请求会以如下内容开头:
GET /pub/WWW/ HTTP/1.1
Host: www.example.org
即使请求目标采用 absolute-form, 客户端也 MUST 在 HTTP/1.1 请求中发送 Host 头部字段, 因为这样可以使得 Host 信息能穿过那些可能没有实现 Host 的古老 HTTP/1.0 代理而被转发.
当代理收到请求目标为 absolute-form 的请求时, 代理 MUST 忽略收到的 Host 头部字段 (如果有的话), 并用请求目标的主机信息替换它. 转发此类请求的代理 MUST 基于收到的请求目标生成新的 Host 字段值, 而不是转发收到的 Host 字段值.
由于 Host 头部字段充当一种应用层路由机制, 它经常成为恶意软件的目标, 后者试图投毒共享缓存, 或者把请求重定向到非预期的服务器. 如果拦截代理依赖 Host 字段值把请求重定向到内部服务器, 或者把它用作共享缓存中的缓存键, 而没有先验证被拦截的连接所指向的是该主机的有效 IP 地址, 那么它就特别脆弱.
对于任何缺少 Host 头部字段的 HTTP/1.1 请求消息, 以及任何包含多个 Host 头部字段或字段值非法的 Host 头部字段的请求消息, 服务器 MUST 以 400 (Bad Request) 状态码响应.
5.5. 有效请求 URI
由于请求目标往往只包含用户代理目标 URI 的一部分, 服务器会重建出预期的目标, 即 "有效请求 URI" (effective request URI), 以便正确处理该请求. 这种重建既涉及服务器的本地配置, 也涉及请求目标、Host 头部字段和连接上下文中传达的信息.
对用户代理而言, 有效请求 URI 就是目标 URI.
如果请求目标采用 absolute-form, 那么有效请求 URI 与请求目标相同. 否则, 有效请求 URI 按如下方式构造:
如果服务器的配置 (或出站网关) 提供了固定的 URI 方案, 则该方案被用作有效请求 URI 的方案. 否则, 如果请求是通过受 TLS 保护的 TCP 连接收到的, 则有效请求 URI 的方案为 "https"; 如果不是, 则为 "http".
如果服务器的配置 (或出站网关) 提供了固定的 URI authority 分量, 则该 authority 被用作有效请求 URI 的 authority. 如果不是, 那么: 如果请求目标采用 authority-form, 则有效请求 URI 的 authority 分量与请求目标相同. 如果不是, 那么: 如果提供了 Host 头部字段且其字段值非空, 则 authority 分量与 Host 字段值相同. 否则, authority 分量被赋为为该服务器配置的默认名称; 并且如果连接的入站 TCP 端口号不同于有效请求 URI 方案的默认端口, 则在该 authority 分量之后追加一个冒号 (":") 和入站端口号 (十进制形式).
如果请求目标采用 authority-form 或 asterisk-form, 则有效请求 URI 的合并路径与查询分量为空. 否则, 合并路径与查询分量与请求目标相同.
按上述方式确定之后, 有效请求 URI 的各个分量可以通过拼接方案、"://"、authority 以及合并路径与查询分量而组合为 absolute-URI 形式.
例 1: 下面这条通过不安全的 TCP 连接收到的消息
GET /pub/WWW/TheProject.html HTTP/1.1
Host: www.example.org:8080
其有效请求 URI 为
http://www.example.org:8080/pub/WWW/TheProject.html
例 2: 下面这条通过受 TLS 保护的 TCP 连接收到的消息
OPTIONS * HTTP/1.1
Host: www.example.org
其有效请求 URI 为
https://www.example.org
对于缺少 Host 头部字段的 HTTP/1.0 请求, 其接收方可能需要使用启发式方法 (例如检查 URI 路径中是否有某主机特有的内容) 来猜测有效请求 URI 的 authority 分量.
一旦构造出有效请求 URI, 源服务器就需要决定是否通过收到该请求的那条连接为该 URI 提供服务. 例如, 该请求可能是被有意或无意地错误导向的, 以致收到的请求目标或 Host 头部字段中的信息与建立连接所用的主机或端口不一致. 如果连接来自受信任的网关, 这种不一致可能是可预期的; 否则, 它可能表明有人试图绕过安全检查、诱使服务器交付非公开内容, 或者投毒缓存. 关于消息路由的安全考量见第 9 节.
5.6. 把响应关联到请求
HTTP 没有包含用于把给定请求消息与其对应的一或多条响应消息关联起来的请求标识符. 因此, 它依赖响应到达的顺序与同一连接上发出请求的顺序完全一致. 只有当一条或多条信息性响应 (1xx, 见 [RFC7231] 第 6.2 节) 先于对同一请求的最终响应出现时, 一个请求才会有多于一条的响应消息.
如果客户端在一条连接上有多个尚未完成的请求, 它 MUST 按发送顺序维护一份未完成请求列表, 并且 MUST 把该连接上收到的每条响应消息关联到尚未收到最终 (非 1xx) 响应的、排序最靠前的那个请求.
5.7. 消息转发
如第 2.3 节所述, 中间方在处理 HTTP 请求和响应时可以承担多种角色. 有些中间方用于提升性能或可用性, 另一些用于访问控制或内容过滤. 由于 HTTP 流具有类似管道-过滤器 (pipe-and-filter) 架构的特征, 中间方对流任一方向的增强 (或干扰) 程度没有固有限制.
未充当隧道的中间方 MUST 实现 Connection 头部字段 (如第 6.1 节所规定), 并阻止那些仅用于入站连接的字段被转发.
中间方 MUST NOT 把消息转发给它自己, 除非它受到保护而不会产生无限请求循环. 一般而言, 中间方应当能识别出自己的服务器名 (包括任何别名、本地变体或字面 IP 地址), 并直接响应此类请求.
5.7.1. Via
"Via" 头部字段表明用户代理与服务器之间 (对请求而言) 或源服务器与客户端之间 (对响应而言) 存在中间协议和接收方, 类似于电子邮件中的 "Received" 头部字段 ([RFC5322] 第 3.6.7 节). Via 可用于跟踪消息转发、避免请求循环, 以及识别请求/响应链上各发送方的协议能力.
Via = 1#( received-protocol RWS received-by [ RWS comment ] )
received-protocol = [ protocol-name "/" ] protocol-version
; see Section 6.7
received-by = ( uri-host [ ":" port ] ) / pseudonym
pseudonym = token
多个 Via 字段值分别代表转发过该消息的每个代理或网关. 每个中间方追加自己关于消息是 "如何收到" 的信息, 因此最终结果是按转发接收方的顺序排列的.
代理 MUST 在它转发的每条消息中发送适当的 Via 头部字段 (如下文所述). HTTP 到 HTTP 网关 MUST 在每条入站请求消息中发送适当的 Via 头部字段, 并且 MAY 在转发的响应消息中发送 Via 头部字段.
对每个中间方而言, received-protocol 表明该消息的上游发送方所使用的协议及协议版本. 因此, Via 字段值记录了请求/响应链上所声明的协议能力, 使这些能力对下游接收方保持可见; 这对于判断在响应中或在后续请求中安全使用哪些不向后兼容的特性可能有用, 如第 2.6 节所述. 为简洁起见, 当收到的协议是 HTTP 时, 省略 protocol-name.
字段值中的 received-by 部分通常是随后转发该消息的接收方服务器或客户端的主机及可选端口号. 然而, 如果真实主机被视为敏感信息, 发送方 MAY 用一个假名替换它. 如果未提供端口, 接收方 MAY 把它解释为是在 received-protocol 的默认 TCP 端口 (如果有的话) 上收到的.
发送方 MAY 在 Via 头部字段中生成注释以标识每个接收方的软件, 类似于 User-Agent 和 Server 头部字段. 然而, Via 字段中的所有注释都是可选的, 接收方 MAY 在转发该消息之前移除它们.
例如, 一条请求消息可能由某个 HTTP/1.0 用户代理发往代号为 "fred" 的内部代理, 后者用 HTTP/1.1 把该请求转发给 p.example.net 上的一个公共代理, 该公共代理再把请求转发给 www.example.com 上的源服务器以完成该请求. 那么 www.example.com 收到的请求将带有如下 Via 头部字段:
Via: 1.0 fred, 1.1 p.example.net
用作穿过网络防火墙的门户的中间方 SHOULD NOT 转发防火墙区域内部主机的主机名和端口, 除非它被显式启用这样做. 如果未启用, 此类中间方 SHOULD 把防火墙之后任何主机的每个 received-by 主机替换为该主机的适当假名.
如果 Via 头部字段的若干条目具有完全相同的 received-protocol 值, 中间方 MAY 把其中有序的一个子序列合并为单个这样的条目. 例如,
Via: 1.0 ricky, 1.1 ethel, 1.1 fred, 1.0 lucy
可以折叠为
Via: 1.0 ricky, 1.1 mertz, 1.0 lucy
发送方 SHOULD NOT 合并多个条目, 除非它们都处于同一组织控制之下, 且其中的主机都已被替换为假名. 发送方 MUST NOT 合并 received-protocol 值不同的条目.
5.7.2. 变换
有些中间方带有用于变换消息及其载荷的特性. 例如, 代理可能在图像格式之间转换, 以节省缓存空间或减少慢速链路上的流量. 然而, 当这些变换被施加于面向关键应用的载荷 (例如医学影像或科学数据分析) 时, 就可能出现运行问题; 尤其是在使用完整性校验或数字签名来确保收到的载荷与原始载荷完全一致时.
如果某个 HTTP 到 HTTP 代理被设计或配置为以语义上有意义的方式修改消息 (即超出正常 HTTP 处理所要求的修改, 其改动对原始发送方而言是重要的, 或者对下游接收方而言可能重要), 则称之为 "变换代理" (transforming proxy). 例如, 变换代理可能充当共享标注服务器 (修改响应以包含对本地标注数据库的引用)、恶意软件过滤器、格式转码器或隐私过滤器. 这类变换被假定为选定该代理的客户端 (或客户端组织) 所期望的.
如果代理收到的请求目标带有的主机名不是完全限定域名, 它 MAY 在转发该请求时把收到的该主机名加上自己的域. 如果请求目标包含完全限定域名, 代理 MUST NOT 改变该主机名.
在把收到的请求目标转发给下一个入站服务器时, 代理 MUST NOT 修改其中的 "absolute-path" 和 "query" 部分, 但上文中把空路径替换为 "/" 或 "*" 的情形除外.
代理 MAY 通过施加或移除某种传输编码 (第 4 节) 来修改消息主体.
对于包含 no-transform 缓存控制指令 ([RFC7234] 第 5.2 节) 的消息, 代理 MUST NOT 变换其载荷 ([RFC7231] 第 3.3 节).
对于不包含 no-transform 缓存控制指令的消息, 代理 MAY 变换其载荷. 变换载荷的代理 MUST 在消息中 (如果尚未存在的话) 添加一个 warn-code 为 214 ("Transformation Applied") 的 Warning 头部字段 (见 [RFC7234] 第 5.5 节). 变换 200 (OK) 响应载荷的代理还可以把响应状态码改为 203 (Non-Authoritative Information) ([RFC7231] 第 6.3.4 节), 以进一步告知下游接收方已施加了变换.
代理 SHOULD NOT 修改那些提供关于通信链端点、资源状态或所选表示 (载荷之外) 信息的头部字段, 除非该字段的定义明确允许此类修改, 或者该修改被认为是隐私或安全所必需的.