超文本传输协议(HTTP)是互联网及其他计算机网络中客户端与服务器之间通信的应用层协议。它提供了一个通用框架,用于标识资源、请求对资源执行操作,以及交换资源状态或内容的表征。HTTP 最初专为万维网开发,如今不仅传输超文本标记语言文档,还承载图像、视听内容、应用程序数据以及许多其他类型的信息。其客户端包括网页浏览器、自动化程序,以及通过应用程序编程接口进行通信的软件。
HTTP 采用请求-响应模型:客户端发送包含请求方法和目标的请求,服务器则返回包含状态码以及在适用情况下包含内容的响应。方法、状态码、字段、缓存规则以及表征元数据在各个协议版本之间具有共通的语义定义,但它们的传输机制各不相同。HTTP/1.1 采用文本消息格式,HTTP/2 引入了基于 TCP 的二进制分帧与多路复用流,而 HTTP/3 则将 HTTP 映射到 QUIC 协议之上。HTTPS 则指利用传输层安全性协议(TLS)保护的 HTTP 通信,提供机密性、完整性和服务器身份验证。
HTTP 的重要性早已超出文档检索的范畴,广泛延伸至分布式应用、内容分发与服务集成。其设计将可扩展的消息结构与缓存、条件请求、身份验证以及通过中介进行通信的机制融为一体。因此,理解 HTTP 既需要掌握其标准化的语义,也需要了解它与传输协议、安全体系、浏览器策略以及大规模网络基础设施之间的相互作用。
架构模型与协议范围
HTTP 是一种应用层协议,用于围绕资源交换请求与响应。其通用语义在 RFC 9110(2022)中规范,独立于具体的传输格式,定义了请求方法、响应状态码、字段以及表述元数据的含义。资源是可识别的交互目标,不一定是一个存储的文件:它可以是一篇文档、一份即时天气报告、一组订单,或是执行某项计算的服务。表述由反映资源当前或期望状态的信息以及描述该信息的元数据构成。因此,一个天气资源可以提供 HTML 页面或 JSON 文档,而不会仅仅因为格式不同就成为两个本质不同的主体。相比之下,连接是承载报文的通信机制;它既不是资源,也不是资源的表述。
客户端和服务端的角色适用于特定的交互过程,而非永久指代某些机器或软件产品。客户端发送请求,服务端接收请求并发送响应。用户代理是发起请求的客户端程序,例如网页浏览器、命令行获取工具或自动化应用程序。源服务器是能够针对目标资源生成权威响应的程序。它无需将该资源保存在本地文件中:它可以从数据库生成表述、执行应用逻辑或咨询其他服务。例如,一个订单处理应用程序在响应浏览器的请求时充当服务端,而在向其他服务请求物流配送信息时则充当客户端。
中介者参与在用户代理与源服务器之间。正向代理是由客户端选定以转发请求的代理;而网关(通常称为反向代理)则向客户端展现为面向源端的服务,并将请求转发至其他服务器。在常规的转发交互中,中介者在其入站连接上扮演服务端的角色,在其出站连接上扮演客户端的角色。因此,一个逻辑请求在到达源服务器之前可能会穿过若干条独立的连接。这些连接在保留请求语义的同时,可能会使用不同的 HTTP 版本。因此,连接的边界并不一定与应用程序的边界或负责所请求资源的端点重合。
HTTP 的无状态性涉及对请求的解读:每个请求的语义都可以被独立理解,而不需要接收方根据该连接上先前的请求来重构隐式的对话时序。这并不意味着服务器不能存储数据,也不意味着应用程序无法维护会话。一个电子商务应用程序可以将购物车保存在持久化存储中,并通过 Cookie 或其他显式标识符将后续请求与该购物车关联起来。应用程序维护购物车的状态;HTTP 则提供用于查看或更改该状态的交互机制。同样,连接复用和协议级别的压缩状态也不会将 HTTP 语义转变为有状态的应用程序会话。
HTTP 与 HTML、万维网以及互联网并不相同。HTML 是一种文档标记语言;万维网是由相互链接的资源和相关技术构成的系统;互联网则提供了 HTTP 通常运行其上的更广阔的网络基础设施。HTTP 可以承载图像、文本、可执行代码、结构化数据或其他媒体类型,并在无需渲染网页的 API 中得到了广泛应用。它的语义并不限定编程语言、存储系统或服务器实现:动态生成的响应与从磁盘读取的响应可以具有完全相同的协议语义。
起源与标准化
HTTP 起源于蒂姆·伯纳斯-李在欧洲核子研究组织开展的万维网项目。他在 1989 年 3 月的提案中描述了一种互联的信息系统,用于在异构计算机之间共享文档;1990 年期间,他实现了第一个 Web 服务器和 WorldWideWeb 浏览器兼编辑器。最初的协议后来被称为 HTTP/0.9,仅支持极简的检索式交互:客户端发送诸如 GET /index.html 的单行请求,服务器返回该文档,随后关闭连接。该请求不包含协议版本标识,响应也没有状态行或 HTTP 字段部分。这种设计虽能满足早期 Web 传输 HTML 文档的需求,但并未提供通用的框架来描述不同的内容类型、通过状态码报告请求结果或协商内容表征。
随着由不同开发者独立实现的浏览器和服务器迅速普及,各种实现纷纷自行增加了方法、响应码和元数据字段。1996 年 5 月,蒂姆·伯纳斯-李、罗伊·菲尔丁和亨里克·弗里斯蒂克·尼尔森在 RFC 1945 中对这些既成惯例进行了归纳记录,即 HTTP/1.0。其请求与响应格式包含了明确的版本信息,诸如 Content-Type 的字段使服务器能够标识除 HTML 以外的内容类型。RFC 1945 是作为信息性(Informational)RFC 发布的,而非互联网标准跟踪(Standards Track)规范:它记录了主流实现的共通行为,同时也承认了各实现之间的不一致性与实验性扩展。随后,HTTP/1.1 进入标准跟踪流程,先是在 1997 年 1 月作为 RFC 2068 发布,后于 1999 年 6 月在 RFC 2616 中得到修订。默认持久连接、请求中必须包含 Host 字段、分块传输编码,以及更完善的缓存和条件请求规则,解决了 Web 扩张过程中遇到的诸多局限。
后来的标准化工作将 HTTP 语义的梳理与传输格式的演进区分开来。2014 年,RFC 7230–7235 取代了 RFC 2616,分为六份文档,分别涵盖报文语法与路由、语义、条件请求、范围请求、缓存以及身份验证。这项工作由互联网工程任务组(IETF)旗下的 HTTP 工作组(常称 HTTPbis)主导。该工作组成为维护 HTTP 核心规范及制定后续版本的主要机构。将规范拆分后,解析要求、缓存规则和方法语义的修订得以独立进行,而无需将每次修改都视作新的网络传输协议版本。
Google 于 2009 年公开推出的 SPDY 实验项目为 HTTP/2 提供了重要基础。SPDY 探索了在单条连接上进行多路复用交互、压缩头部信息以及设置传输优先级,以降低网页加载延迟。IETF 在这些理念的基础上发展出了一套独立的协议,而非原封不动地照搬 SPDY。HTTP/2 于 2015 年 5 月在 RFC 7540 中完成标准化,由迈克·贝尔什、罗伯托·佩昂和马丁·汤姆森主编。它保留了 HTTP 的方法、状态码和资源语义,同时用二进制分帧和并发流取代了 HTTP/1.1 的文本报文传输机制。Google 随后在 QUIC 协议上的研发工作则探索了基于 UDP 的全新传输层架构。IETF 于 2021 年 5 月在 RFC 9000 中将 QUIC 版本 1 标准化;2022 年 6 月,将 HTTP 映射至 QUIC 的 HTTP/3 亦在 RFC 9114 中发布。
2022 年 6 月发布的系列标准确立了独立于版本的 HTTP 核心定义,并辅以各自独立的传输规范。RFC 9110《HTTP 语义》(HTTP Semantics)定义了包括方法、状态码、字段和表征元数据在内的通用概念;RFC 9111《HTTP 缓存》(HTTP Caching)规范了缓存行为。RFC 9112 定义了 HTTP/1.1 报文语法和连接管理,RFC 9113 取代 RFC 7540 成为 HTTP/2 规范,RFC 9114 则定义了 HTTP/3。在这一体系结构中,版本号主要用于区分交互内容的底层传输方式,而不再为诸如 GET 请求或 404 Not Found 响应等定义不同的应用层语义。
资源标识与请求目标
HTTP 使用统一资源标识符(URI)来标识资源。在 https://www.example.org:8443/catalog/items?sort=price#details 中,方案(scheme)为 https,权属(authority)为 www.example.org:8443,路径(path)为 /catalog/items,查询(query)为 sort=price;details 则是片段标识符。权属部分用于标识主机,并可附带显式端口。尽管通用 URI 语法允许在权属中包含用户信息,但 HTTP 和 HTTPS 的 URI 方案禁止使用该组件。路径和查询用于标识该权属命名空间内的资源:两者都不必对应本地文件系统路径、数据库记录或特定的服务器进程。例如,/catalog/items 可以由静态文件、应用程序处理程序或分布在多台机器上的服务来实现,而无需改变其标识符。
http 和 https 方案的默认端口分别为 80 和 443。省略端口即使用该方案的默认值,因此 https://example.org/ 和 https://example.org:443/ 标识同一个源。HTTPS 要求对所标识的服务进行安全通信与身份验证,但不改变 HTTP 方法的语义。当 URI 包含域名时,客户端在建立通信前通常使用域名系统来获取寻址信息。域名解析与资源标识保持独立:多个名称可以解析到同一个地址,一个名称也可以解析到多个地址。代理服务器还可以代表客户端执行解析。因此,URI 中的主机名并不仅仅是最终连接的 IP 地址的另一种拼写方式。
源由方案、主机和端口组合定义,省略端口时套用特定方案的默认值。因此,http://example.org、https://example.org 和 https://example.org:8443 代表三个不同的源。虚拟主机技术允许多个权属共享同一个网络地址和监听端口,因此仅连接到该网络端点并不能确定目标服务。HTTP/1.1 要求在请求中包含 Host 字段(如 Host: www.example.org:8443)以传递权属信息。HTTP/2 和 HTTP/3 为此提供了 :authority 伪标头字段,并通常在 :scheme 和 :path 中携带方案和路径信息。这些标识符使服务器和中介能够正确路由请求,而无需假设每个网站都拥有独立的专用地址。
HTTP/1.1 定义了四种请求目标形式。源形式(Origin-form)用于直接发送给源服务器的常规请求,包含绝对路径和可选的查询:GET /catalog/items?sort=price HTTP/1.1。空的 URI 路径会被传输为 /。绝对形式(Absolute-form)通常用于向正向代理发送的请求,提供完整的绝对 URI:GET http://www.example.org/catalog/items?sort=price HTTP/1.1。这使得代理能够同时识别目标权属和资源。权属形式(Authority-form)仅用于 CONNECT 方法,提供主机和必填端口,如 CONNECT www.example.org:443 HTTP/1.1;它标识所请求隧道的目的地,而非文档路径。星号形式(Asterisk-form)如 OPTIONS * HTTP/1.1,用于针对整个服务器而非特定资源发起操作。在这些 HTTP/1.1 请求中,Host 字段始终是必需的。
片段在客户端进行解析,不包含在 HTTP 请求目标中。对于 https://www.example.org/guide?edition=2#installation,常规的 HTTP/1.1 请求目标为 /guide?edition=2。浏览器在获取到资源的表现形式后,可以使用 installation 定位其中的特定段落;其他表现形式格式也可以定义不同的片段语义。因此,仅修改片段可以在无需重新获取资源的情况下改变展示位置。与此相反,查询会被发送到服务器,并且可能会影响选择哪一个资源或服务器如何处理该请求。
消息、字段与内容
HTTP 消息具备通用的逻辑结构:控制信息、初始字段节、可选的内容以及可选的尾部字段节。请求控制信息指明方法与请求目标;响应控制信息提供状态码。协议版本信息也会在控制信息中传递,不过其编码方式在不同版本之间存在差异。初始字段通常称为标头(header),用于描述消息本身、其内容或处理该消息所需的上下文。尾部字段(trailer)位于内容之后,携带补充字段,使得在传输过程中计算出的信息(例如内容摘要)无需预先缓冲全部内容即可发送。尾部字段不能直接取代初始字段:在处理内容之前就必须获知的信息(例如成帧信息或身份验证信息)不适合放在尾部字段中。这一逻辑模型定义于 2022 年发布的 RFC 9110 中;各 HTTP 版本对其采用不同的编码实现。
一个 HTTP 字段由字段名和字段值组成,其语法和语义针对该特定字段单独定义。字段名不区分大小写:Content-Type 与 content-type 指向同一个字段,不过 HTTP/2 和 HTTP/3 在编码时要求字段名必须全小写。字段值则并非普遍不区分大小写,标点符号也无法通过单一通用规则来解释。例如,Accept 允许使用逗号分隔的媒体范围列表,但加引号的字段值内部包含的逗号并不一定表示列表项的分隔。只有当字段定义明确允许时,同名的多行字段才能在保持原有顺序的前提下,通过插入逗号合并为一行。Set-Cookie 是常见列表合并规则的显著特例:多次出现的该字段分别传递各自的 Cookie,无法安全地折叠为逗号分隔的列表。除非有更具体的规则约束,接收方通常会忽略无法识别的字段;转发中介在保留哪些字段方面还承担着额外的义务。
内容与界定或传输内容的机制截然不同。在 HTTP/1.1 中,分块大小和分块分隔符属于传输成帧机制,不属于内容;在 HTTP/2 和 HTTP/3 中,帧边界同样不会成为内容的一部分。相比之下,由 Content-Encoding 指明的数据压缩(例如 gzip)则属于作为内容传输的编码表征的一部分。消息可以没有内容,而内容的含义取决于请求方法和响应状态。成功的 GET 响应通常携带目标资源的表征,而 POST 请求的内容则是供目标资源处理的信息。HEAD 请求的响应不包含内容,即使其字段描述了对应 GET 响应理应携带的内容也是如此。信息性响应、204 响应和 304 响应同样不包含内容;而成功的 CONNECT 响应在初始字段之后便转入隧道运作。
以下交互使用 HTTP/1.1 文本表示法来阐明跨版本共用的语义。响应内容恰好为 5 个八位字节,即 Hello,末尾没有换行符:
GET /greeting HTTP/1.1
Host: example.org
Accept: text/plain
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 5
Hello
在请求中,GET 选定了操作,/greeting 是请求目标,Host 标明了目标权威机构;Accept 表示偏好纯文本格式。在响应中,200 是状态码,而 OK 是 HTTP/1.1 的原因短语,并无独立的协议语义。Content-Type 指明媒体类型和字符编码,Content-Length: 5 说明内容长度为 5 个八位字节,而非字符数。空行用于结束初始字段节;实际 HTTP/1.1 行末使用的是回车换行符(CRLF)。此处未出现尾部字段节。HTTP/2 和 HTTP/3 通过二进制成帧和伪标头字段而非此类文本起始行来传递等效的控制信息与字段;两者均未使用示例中的空行分隔符。
请求方法及其属性
HTTP 请求方法指明了对目标资源所请求的操作。方法名称区分大小写:GET 是标准化的方法名,而小写的 get 并不与之等价。GET 请求传输当前所选资源的表述;获取内容并不必然对应读取磁盘文件,因为服务器可以从数据库或其他服务动态生成表述。HEAD 的语义与 GET 相同,区别在于服务器绝对不能返回响应正文。它可用于在不传输完整表述的情况下检查元数据。服务器应返回其在处理 GET 请求时会发送的头部字段,不过仅在生成响应内容过程中才能确定的字段可以省略。GET 请求携带的正文没有通用定义的语义,不能改变请求本身的含义或目标。私下约定的协议有时会为其赋予特定含义,但中介代理可能会拒绝此类请求,从而降低互操作性。
POST 请求目标资源按照其自身的特定语义来处理所附带的表述。常见用例包括提交表单、追加数据或创建从属资源:例如 POST /orders 可能会创建一个新订单,并由服务器为其分配标识符。与此相对,PUT 则请求根据请求附带的表述来创建目标资源的状态,或将其状态完整替换。在 PUT /orders/123 中,客户端已经明确指定了目标资源的标识。通过 PUT 成功创建资源时应返回 201 Created;修改现有资源的表述则应返回 200 OK 或 204 No Content。DELETE 请求解除目标 URI 与其当前功能之间的关联。这并不要求物理抹除底层存储的数据:具体实现可以保留归档记录,或在稍后回收存储空间。
OPTIONS 请求获取目标资源的通信选项信息;当请求目标为 * 时,则获取服务器通用的通信选项;响应可通过 Allow 标头指明所支持的方法。CONNECT 请求建立通往目标主机的隧道,通常用于在代理服务器上承载加密流量。TRACE 请求对收到的请求执行应用层回环测试以供诊断;客户端在发送此类请求时绝对不能包含凭据或 Cookie 等敏感字段。PATCH 于 2010 年在 RFC 5789 中被标准化,它应用补丁文档中所描述的一组修改规则,而不是像 PUT 那样请求整体替换。其媒体类型定义了具体的修改指令。服务器必须以原子方式应用整个补丁,绝不能对外暴露处于部分修改状态的表述;PATCH 本身并不具备天然的幂等性。
若某种方法的已定义语义本质上是只读的,即客户端未请求更改状态,则称该方法是安全(safe)的。GET、HEAD、OPTIONS 和 TRACE 属于安全方法;POST、PUT、DELETE、CONNECT 和 PATCH 则不属于。安全并不排斥附带产生的副作用(例如记录日志或对广告账户计费)。它关乎的是客户端请求的本意以及客户端为此承担责任的行为。因此,像 /item?do=delete 这样的 URI 绝不能在收到 GET 请求时执行删除操作:因为浏览器的自动化预加载或搜索引擎爬取都可以合理地抓取该链接,而并无执行破坏性操作的意图。
幂等性意味着发送多次完全相同的请求与仅发送一次请求在服务器端产生的预期效果相同。所有安全方法以及 PUT 和 DELETE 都是幂等的;POST、CONNECT 和 PATCH 则不具备这一普适保证。各次请求的响应内容并不要求完全一致:例如第一次 DELETE 请求可能返回 204,而重复请求可能返回 404(因为关联已经被移除)。为每次请求单独记录日志也不会违背幂等性。单个 POST 或 PATCH 操作完全可以设计为幂等的,但仅凭其方法名称无法推断出该属性。
这些属性规范了通信故障发生时的恢复机制。如果连接在响应送达前关闭,客户端可以自动重试幂等请求;但除非已知该操作本身具有幂等性或原请求从未被服务器执行,否则客户端不应自动重试非幂等请求,以防出现重复支付等问题。可缓存性(cacheability)是另一项独立的属性。POST 响应仅在包含明确的新鲜度信息且 Content-Location 与 POST 目标 URI 相同的情况下才可缓存。这种已存储的响应可用于满足后续的 GET 或 HEAD 请求,但不能直接满足另一个 POST 请求;与检索类响应的缓存相比,各类实现对 POST 响应缓存的支持并不普遍。
状态码、重定向与错误
HTTP 响应状态码是一个三位整数,用于描述请求的处理结果以及响应的语义。其首位数字定义了五大类别:1xx 信息响应,表示最终响应前的过渡通信;2xx 成功,表示请求已被成功接收、理解和接受;3xx 重定向,表示需要采取进一步操作才能完成请求;4xx 客户端错误,表示请求本身似乎存在问题;5xx 服务器错误,表示服务器未能成功处理看似有效的请求。这些属于协议层面的分类,并非对具体应用业务结果的保证。例如,API 可以在返回 200 OK 的同时,在表征内容中报告业务规则拒绝;不过这种做法会导致仅依赖状态码的监控系统无法察觉该业务拒绝。这些通用定义适用于各版本 HTTP,并在 2022 年发布的 RFC 9110 中进行了统一整合。
信息性响应允许在最终结果产生前进行通信。100 Continue 表示服务器在检查了初始请求头部字段后,希望继续接收请求内容,常见于客户端发送了 Expect: 100-continue 的场景;它并不保证请求最终会被接受。2017 年在 RFC 8297 中标准化的 103 Early Hints 可以携带 Link 字段,以便浏览器在服务器准备最终响应的同时预先开始加载依赖资源。在成功响应中,200 OK 表示成功,其具体含义取决于请求方法:GET 响应通常包含所请求的资源表征,而 POST 响应则可能描述处理结果。201 Created 表示已创建了一个或多个资源,当存在 Location 字段时,该字段指示主资源位置,否则由目标 URI 指示。202 Accepted 表示请求已被接受处理但尚未完成,且不保证最终一定会成功。204 No Content 表示请求已成功执行,但响应不包含任何实体内容。
重定向响应通常利用 Location 字段指向另一个 URI,但不同重定向状态码在永久性及请求方法的处理方式上存在差异。301 Moved Permanently 分配了一个永久性的替代 URI,而 302 Found 则表示临时替代。出于历史兼容性原因,客户端在遵循这两种重定向时可能会将 POST 方法更改为 GET。相比之下,303 See Other 明确指示客户端使用 GET 或 HEAD 检索另一个资源;一个常见示例是表单提交后重定向到确认页面。307 Temporary Redirect 和 308 Permanent Redirect 消除了方法更改的歧义:自动重定向的请求必须保留原有的请求方法。因此,使用 307 或 308 重定向 POST 请求时,仍会保持 POST 语义,而不会将其转换为数据检索请求。客户端是否自动跟随重定向仍取决于其自身策略,包括跨源转发凭据时的安全限制。
客户端错误响应细分了多种失败情况。400 Bad Request 涵盖客户端请求中存在的语法畸形或帧结构无效等问题。404 Not Found 表示服务器未找到当前表征,或不愿透露该资源是否存在;它并不代表资源永久缺失。405 Method Not Allowed 表示服务器知晓该方法,但目标资源不支持此方法,此时响应中必须包含 Allow 字段以列出当前支持的方法。409 Conflict 指示请求与资源的当前状态冲突,例如不兼容的版本更新。415 Unsupported Media Type 表示由于请求内容的格式或编码不受支持而拒绝该操作。2012 年在 RFC 6585 中定义的 429 Too Many Requests 表示触发了速率限制,并且可以包含 Retry-After 字段。
服务器错误响应标明了不同故障发生的位置。500 Internal Server Error 描述服务器遇到了阻止其完成请求的意外情况。502 Bad Gateway 表示网关或代理服务器从上游接收到了无效响应,而 504 Gateway Timeout 则表示未能及时收到上游响应。503 Service Unavailable 表示服务器暂时无法处理请求,通常是因为超载或停机维护。503 响应可包含 Retry-After 字段,以 HTTP 日期或非负秒数形式指定;该字段也可以随重定向响应一同发送,以指示在尝试跟随重定向之前需要等待的最短时间。
状态码具有可扩展性。客户端必须能够识别未知状态码所属的类别,并将其视为该类别对应的 x00 代码进行处理,同时不得缓存带有未知状态码的响应。即使是 HTTP 错误响应,也依然是正常接收到的协议报文,可能包含解释性内容及重试信息。若在响应头部送达之前发生域名解析失败、连接被拒或传输中断,则根本不会产生任何 HTTP 状态码;在此类情况下客户端所展示的任何错误标签,均非服务器返回的状态。
表示与内容协商
由 URI 标识的资源可以具有多种表示:例如,一份天气报告既可以提供英文 HTML、法文 HTML,也可以提供结构化数据。选定表示是指专门为响应选取的特定表示,而非资源本身。表示元数据描述了应如何解释其内容。Content-Type 用于标识媒体类型,并可附带可选参数:例如 Content-Type: text/html; charset=utf-8 指定了采用 UTF-8 编码的 HTML。参数的含义由具体的媒体类型定义;charset 并非适用于所有格式的通用参数。Content-Language: fr 标明了该表示的目标受众语言,并不一定指其内部出现的所有语言。例如,一份语言学习文档可能包含多种语言,但其目标受众是英语读者。
在主动内容协商中,服务器根据请求中提供的偏好来选择表示。Accept 表达对媒体类型的偏好,Accept-Encoding 表达对内容编码的偏好,Accept-Language 则表达对自然语言的偏好。偏好权重使用介于 0 到 1 之间的 q 参数;若省略权重,则默认值为 1,而 0 则表示在适用的匹配规则下不可接受。因此,Accept: application/json, text/html;q=0.7 表示优先选择 JSON,而 Accept-Language: fr, en;q=0.8 表示法语优先于英语。这些字段是服务器进行选择时的参考输入,而非强制生成不可用变体的指令:服务器还会综合考虑资源的可用性及其自身的选择策略。若不存在可接受的变体,服务器可以返回 406 Not Acceptable,但 HTTP 也允许忽略协商偏好而不返回该错误。在被动协商中,服务器则提供多个备选项供用户代理选择,这可能需要发起另一次请求。诸如 300 Multiple Choices 或 406 之类的响应可以为此流程提供支持,尽管 HTTP 并未规定统一的格式或自动选择算法。
内容编码在不改变基础媒体类型的前提下,改变表示的编码形式。包含 Content-Type: text/html 和 Content-Encoding: gzip 的响应承载的是经 gzip 压缩的 HTML;接收方在解释 HTML 之前须先解压 gzip。Brotli 于 2016 年在 RFC 7932 中标准化,使用编码标记 br。两者都是无损数据压缩机制,通过 Accept-Encoding 进行协商。多个编码可以按应用顺序排列,并以相反的顺序解码。这与 HTTP/1.1 的传输编码不同:Transfer-Encoding: chunked 是为了传输而划分报文内容的分界,并非选定表示本身的属性。内容编码也不同于将压缩文件格式直接选作媒体类型:application/gzip 标识的是 gzip 格式数据本身,而不是声明应用于某种其他媒体类型的编码。
Vary 用于声明除请求方法和目标 URI 之外,还有哪些请求字段可能会影响表示的选择。选择语言和压缩变体的服务器可能会发送 Vary: Accept-Language, Accept-Encoding。这表明,即便其他方面相同的请求,也可能因这些字段不同而获得不同的表示。它既不列出所有可用变体,也不标识实际交付的表示;后一功能由 Content-Language 和 Content-Encoding 等字段承担。Vary: * 表示选择结果可能取决于请求的其他方面,甚至可能包含这些字段之外的信息。它对已存储响应复用的影响属于 HTTP 缓存规则的范畴。
多部分表示在单个内容主体中包含多个由分隔符分开的部分。其 Content-Type 包含一个必需的 boundary 参数,该参数的值决定了分隔这些部分的分界线。multipart/form-data 于 2015 年在 RFC 7578 中规范化,常用于传输提交的表单字段和上传的文件。每个部分都带有包含 name 参数的 Content-Disposition: form-data;文件部分还可以提供 filename 参数及其自身的 Content-Type。多部分封装本身并不意味着协商。类似地,选择 JSON、XML 还是特定于应用程序的二进制格式,与定义该格式的模式(schema)是两回事:HTTP 负责协商媒体类型,而应用程序则决定在其中序列化的字段、结构和含义。
验证器、前置条件与范围请求
验证器用于标识所选资源表征或为其标注日期,以便接收方检验其是否已发生变化。实体标签由 ETag 承载,是一个用引号包裹的不透明字符串(如 "revision-42");客户端不得根据其内容推断特定的哈希算法或版本管理方案。只要表征数据发生变动,强验证器就会改变;而带有 W/ 标记的弱验证器则可将服务器认为在语义上等价的表征归为一组,即便它们在字节层面存在差异。因此,W/"revision-42" 无法确保逐字节的一致性。Last-Modified 以 HTTP 日期格式提供修改时间。由于其分辨率仅为一秒且依赖精确的修改追踪,通常属于弱验证器,不过 HTTP 规范也明确了其可被视为强验证器的特定情形。
条件式获取允许客户端仅在本地存储的表征已过时的情况下才请求内容。例如,附带 If-None-Match: "revision-42" 的 GET /manual 请求要求服务器仅在所选表征的实体标签不匹配时才传输该表征。此条件采用弱比较:只要不透明标签值相同即视为匹配,无论是否存在弱验证标记。若该条件针对 GET 或 HEAD 请求判定为假(即标签匹配),服务器将返回不包含响应正文的 304 Not Modified;客户端随后可保留已存储的表征并更新相应的元数据。If-Modified-Since 提供了基于时间戳的替代机制:符合条件且在指定日期之后未被修改的表征同样会触发 304 响应。当两个字段同时出现时,If-None-Match 享有优先权,If-Modified-Since 会被忽略。缓存复用规则与这些条件请求语义彼此独立。
前置条件还支持乐观并发控制,防止某个客户端悄无声息地覆盖另一客户端的更改。假设两位编辑获取了带有 ETag: "v7" 的同一份文档。在其中一位成功更新后,服务器为其分配了 "v8"。另一位编辑随后发送带有 If-Match: "v7" 的 PUT 请求时,将收到 412 Precondition Failed 报错,而不会直接覆盖已更新的文档。If-Match 采用强比较,因此弱标签无法满足该条件。If-Unmodified-Since 提供了基于时间戳的条件,但在 If-Match 存在时会被忽略。通配符 If-Match: * 要求当前表征必须存在;反之,If-None-Match: * 可确保新建请求不会覆盖已有的表征。对于 GET 或 HEAD 之外的方法,若 If-None-Match 条件为假,服务器将返回 412 而非 304。
字节范围请求仅获取所选表征的一部分,常用于断点续传或媒体按需拖拽进度。Range: bytes=0-499 请求前 500 个字节:偏移量从零开始,且包含两端端点。诸如 bytes=500- 的开放式形式请求剩余的所有字节,而 bytes=-500 则请求末尾的 500 个字节。服务器通过 Accept-Ranges: bytes 声明对此特性的支持,但客户端即便未收到该声明,也可以尝试发起范围请求。服务器可以忽略 Range 并返回常规的 200 OK 响应。范围请求的处理位于前置条件评估之后,因此理应返回 304 的条件式 GET 请求不会演变成部分内容响应。
对于可满足的单个范围,服务器通常返回 206 Partial Content,并附带类似 Content-Range: bytes 0-499/1234 的字段;响应内容即为该 1234 字节表征中的这 500 个字节。若返回多个范围,则使用 Content-Type: multipart/byteranges,并在每个正文分块中各附一个独立的 Content-Range 字段。服务器可以合并重叠或相邻的范围。若请求的所有范围均无法满足,服务器可返回 416 Range Not Satisfiable;此时字节范围响应应当包含诸如 Content-Range: bytes */1234 的字段,用以标明当前总长度。字节偏移量作用于所选表征的已编码字节序列,未必对应其解压后的形式。
If-Range 使得部分内容获取能够以表征的一致性为前提。恢复下载的客户端可同时发送 Range: bytes=500- 与 If-Range: "v7"。若强实体标签相匹配,服务器将返回剩余字节;否则将忽略范围请求并传输完整的表征,且通常返回 200 而非 412。弱实体标签不可用于 If-Range;日期也仅在满足特定的强验证器条件下才被允许。在将多次独立获取的片段进行拼接时,相同的强验证器能确保它们来自同一表征,从而避免重组后的文件混杂不同版本的字节。
缓存与复用
RFC 9111(2022年)规范了 HTTP 缓存机制,该机制将响应存储起来以供后续请求使用,而不必每次都联系源服务器。私有缓存通常位于 Web 浏览器内部,仅服务于单一用户;共享缓存则通过代理或内容分发网络(CDN)服务于多个用户。可复用的本地响应能够免除网络通信,而共享缓存则可以缩短网络距离并合并重复的数据获取。两者均能降低带宽消耗和源服务器负载,但共享缓存必须遵循针对个性化及身份验证响应的限制。响应是否可缓存取决于请求方法、状态码、头部字段以及存储规则,而不仅仅取决于响应内容是图像还是 HTML 文档。
新鲜度决定了存储的响应通常能否在不经过验证的情况下直接满足请求。当响应的新鲜度寿命超过其当前存活时间(age)时,该响应即为新鲜的。对于共享缓存,Cache-Control: s-maxage 优先于 max-age;否则,max-age 会提供一个以秒为单位的显式保鲜期。若两者皆无,Expires 则提供一个绝对过期时间,通常参照 Date 进行解析。当前存活时间的计算综合了响应在接收时的表观存活时间、接收到的任何 Age 值、传输延迟以及随后在存储中度过的时间;它并不会在经过每个中间设备时简单地重置归零。例如,一个带有 max-age=600 且计算出的当前存活时间为 240 秒的响应,其剩余新鲜度为 360 秒。缓存从存储中生成响应时,会发送 Age 字段以报告其估算的存活秒数。
在未提供显式寿命的情况下,若协议规则允许,缓存可以赋予响应启发式新鲜度。一种常见的启发式算法是取 Last-Modified 与 Date 之间时间间隔的一定比例;RFC 9111 给出的典型参考值为 10%,但这并非强制算法。存储许可与新鲜度属于相互独立的决策:一个响应可以被允许存储,但在复用前却必须经过验证。private 禁止共享缓存进行存储,但允许私有缓存存储;而 public 则显式允许缓存,包括在某些原本会阻止共享缓存存储的条件下也是如此。no-cache 允许存储,但在复用前必须成功完成验证。no-store 禁止存储当前发生交互的数据;它并不是一条要求清除该资源所有既往已存储响应的指令。响应一旦变旧(stale),must-revalidate 就会禁止在未成功完成验证的情况下复用它,即便源服务器当前不可达也是如此。
缓存键(cache key)至少包含请求方法和目标 URI,尽管许多已部署的缓存只存储 GET 响应,因而实质上主要以 URI 作为键。Vary 为那些影响表征选取的请求头部字段增加了匹配要求。例如,Vary: Accept-Encoding 可防止存储的压缩表征被错误地复用给不兼容的请求。查询字符串的差异通常会区分不同的键;URL 片段(fragment)则不会,因为它们不会在请求中发送。带有 Vary: * 的响应无法通过常规的变体选择机制匹配后续请求。具体实现可以进一步对缓存进行分区,但不能忽略协议对于表征匹配的要求。
重新验证是通过条件请求联系源服务器,以确定所存储的表征是否依然可用。304 Not Modified 响应允许缓存更新存储的元数据并复用既有内容,从而避免重新传输该内容;而替换性的响应则会提供全新内容。成功的状态变更请求也会影响已存储的条目:当缓存收到针对非安全方法的非错误响应时,必须使目标 URI 失效,即将其移除或标记为需要验证。根据同源限制,相关的 URI(例如由 Location 或 Content-Location 所标识的 URI)也可能被一并置为无效。此类失效操作仅会传达给该次交互路径上的各级缓存,而非全球的所有缓存。
受控的陈旧内容复用是以即时一致性换取响应速度或可用性。RFC 5861(2010年)定义了 stale-while-revalidate(允许在限定的后台验证时间窗口内使用陈旧内容)以及 stale-if-error(允许在发生指定的网络或服务器故障时在有限时间内使用陈旧内容)。例如,在设置了 max-age=60, stale-while-revalidate=30 的情况下,缓存在其新鲜度寿命超出 30 秒以内仍可提供该响应,同时在后台尝试异步验证。这些扩展指令并未赋予无限制提供旧数据的权限,也不会覆盖其他更严格的适用禁令。因此,在缓存条目过期或刷新的过程中,不同的客户端可能会暂时接收到不同的表征。
身份认证、Cookie 与应用会话
HTTP 的质询-响应框架允许服务器请求凭据,而无需对应用程序的账户体系做出硬性规定。带有 401 Unauthorized 状态码的响应必须包含至少一个带有质询的 WWW-Authenticate 字段,例如 WWW-Authenticate: Basic realm="members"。客户端随后可在 Authorization 字段中发送凭据;客户端也可以不等待质询而直接发送凭据。认证方案标明凭据的表示方式,而诸如 realm 之类的参数则用于标识保护空间。身份认证用于确定身份或验证凭据;授权则确定是否允许访问。尽管名称如此,401 Unauthorized 针对的是凭据缺失或未通过身份验证的情形。403 Forbidden 则表示服务器理解该请求但拒绝执行,包括已认证凭据缺乏足够权限的情况。
基本认证(Basic authentication)于 2015 年在 RFC 7617 中规范化,它使用 Base64 对由冒号分隔的用户名和密码进行编码。这种编码不提供机密性保证:防止网络窃听完全依赖于 HTTPS 等安全传输通道。摘要认证(Digest authentication)于 2015 年在 RFC 7616 中得到更新,它使用凭据、服务器提供的随机数(nonce)以及请求信息来计算响应,而非直接传输密码。它支持包括 SHA-256 在内的算法,但其本身并不加密报文内容,也不能替代传输层安全。Bearer 认证于 2012 年在针对 OAuth 2.0 的 RFC 6750 中规范化,通常采用 Authorization: Bearer <token> 的形式。持有令牌即足以行使其被授予的权限。此类令牌可表示受限的访问授权而非完整的登录会话;HTTP 并不要求它必须指向服务器端的会话记录。
服务器通过类似 Set-Cookie: sid=abc123; Path=/; Secure; HttpOnly; SameSite=Lax 的响应字段来设置 HTTP Cookie。浏览器存储该 Cookie,并将名称和值附加到符合条件的后续请求中,例如 Cookie: sid=abc123。属性用于控制存储和传输规则,但不会重复出现在请求字段中。若不指定 Domain 属性,Cookie 即为仅主机(host-only)Cookie;指定可接受的父域名可以将适用范围扩展到其子域名,但受制于浏览器的公共后缀检查等限制。Path 限制匹配的请求路径,但它并不是应用程序之间的安全边界。Cookie 不按端口进行隔离。Max-Age 以秒为单位指定生存期,两者同时存在时其优先级高于 Expires。如果两者均未设置,Cookie 即为会话 Cookie,不过浏览器的会话恢复功能可能会在重启后保留它。
Secure 属性将 Cookie 的传输限制在安全通道中;它不会单独对 Cookie 进行加密,不能保护服务器端的存储,也无法在原本不安全的通信中建立机密性。HttpOnly 防止其被 JavaScript 的 document.cookie 等非 HTTP 脚本接口访问,但浏览器仍会在符合条件的脚本发起的请求中附加该 Cookie。因此,它并不能阻止注入的恶意脚本执行已认证的操作。SameSite 限制在跨站上下文中的附加行为:Strict 完全禁止跨站请求携带 Cookie,而 Lax 允许特定采用安全方法的顶级导航携带。None 允许跨站附加,且在现代浏览器中必须与 Secure 一起使用。“同站”(Same-site)与“同源”(Same-origin)不同:协议方案与可注册域名决定了站点,因此不同的子域名即使属于不同源,也可以属于同站。这些属性限制了凭据的暴露面,但并不能替代应用层的访问控制检查。
应用会话通常将一个不透明且不可预测的 Cookie 值与服务器端的记录关联起来,该记录包含用户标识符、认证状态、购物车内容或偏好设置。该记录可驻留在内存、数据库或由多个应用服务器共享的存储中。Cookie 的过期与服务器端会话的过期是相互独立的:浏览器可能保留一条在服务端已过期的标识符,而删除 Cookie 也并不一定会删除对应的服务端记录。另一些设计则将签名后的状态保存在客户端持有的 Cookie 或令牌中;签名能检测篡改,但不会隐藏内容。在上述每种设计中,请求都会携带定位或验证应用状态所需的信息。因此,持久登录、购物车和个性化响应是在 HTTP 无状态请求语义之上运行的,而并未改变其无状态的本质。
代理、网关与内容分发
正向代理是客户端主动选定并代表其处理请求的中介,例如浏览器借以访问外部网站的企业代理服务器。网关通常被称为反向代理,它向客户端呈现为负责目标资源的服务器,并将请求转发至一个或多个上游服务。客户端无需知晓这些服务的实际地址或实现细节。这些角色是依具体交互而定的:一台服务器可以作为网关接收请求,随后又作为客户端向上游服务器发起请求。隧道则与两者皆有不同:一旦建立,它便只转发原始字节,而不将其解释为 HTTP 消息。
反向代理可通过轮询调度、连接数统计或特定于应用的路由规则,将请求分发至各个后端实例。健康检查有助于排除不可用的实例,而会话亲和性则能将相关联的请求定向至同一后端。路由决策可基于请求的目标主机(authority)、路径或其他字段:例如在同一个公开源站下,/images/ 和 /api/ 可以被路由至不同的后端服务。在 TLS 终结模式下,代理会解密传入的 HTTPS 流量,并建立独立的上游连接(该连接自身亦可使用 TLS)。此时,机密性受限于各自独立的连接边界,而非自始至终无缝延伸至后端。内容分发网络(CDN)将反向代理基础设施部署在地理上分散的边缘节点。通过基于 DNS 的调度或任播路由,客户端会被定向至某个边缘节点,由其提供缓存内容或将请求转发至源站;仅凭地理距离并不足以决定最低延迟的路由。
HTTP 明确区分了旨在供端到端解释的字段与仅适用于单跳的连接特定信息。在 HTTP/1.1 中,Connection 字段用于声明不得继续转发的其他字段:处理 Connection: keep-alive, Example-Control 的中介在转发前必须同时移除 Connection 字段本身及被列出的字段。HTTP/2 和 HTTP/3 禁止使用诸如 Connection 之类的连接特定字段,因此协议转换需要进行语义级处理,而非逐字复制。Via 字段记录了沿途各中介的协议信息与标识符;一条中介链可能生成类似 1.1 proxy-a, 1.1 proxy-b 的值。该字段有助于请求追踪和环路检测,但由于允许使用化名,它并不一定会暴露真实的物理主机。2014 年发布的 RFC 7239 对 Forwarded 及其参数(包括 for、by、host 和 proto)进行了标准化。然而,无论是 Forwarded 还是被广泛部署的 X-Forwarded-For 都非天然可信:由于客户端可以自行伪造这些字段,其凭据价值完全取决于具体是由哪些中介生成或进行了清洗过滤。
对于传统的 CONNECT 隧道,客户端会向代理发送类似 CONNECT example.com:443 HTTP/1.1 的请求。一旦收到成功的 2xx 响应,该连接即转变为隧道;后续传输的字节不再被该代理作为普通的 HTTP 请求与响应来解析。浏览器随后便可通过该隧道与 example.com 进行 TLS 握手。代理知晓所请求的隧道目标地址,但只要不终结该 TLS 连接,就无法读取其中封装的 HTTP 字段或内容。较新的 HTTP 版本支持基于数据流的 CONNECT,允许隧道仅占用单一数据流,而非独占整条底层连接。
中介链由此形成了多个独立管理的通信网段。例如,浏览器可能使用 HTTP/3 连接到 CDN 边缘节点,边缘节点使用 HTTP/2 连接到区域网关,区域网关再使用 HTTP/1.1 连接到应用服务器。尽管各段之间共享统一的请求方法与状态码语义,但在每一处边界上,分帧机制、字段编码、连接复用以及故障处理方式都可能截然不同。因此,访问日志反映的只是特定观测点的情况,而非交互的全貌。由网关生成的 502 Bad Gateway(错误网关)或 504 Gateway Timeout(网关超时)仅代表该网关视角下的上游通信异常,并不必然意味着最终源站应用发生了故障。若要跨跳关联请求,需要显式传递追踪标识符并采用兼容的日志记录规范;否则,中介引入的缓冲机制、重试逻辑和路由决策极易掩盖延迟或故障实际发生的具体位置。
HTTP/1.1 传输与连接管理
HTTP/1.1 将消息序列化为起始行、以回车换行(CRLF)结尾的字段行、表示头部字段结束的空 CRLF 行以及可选的消息体。请求行包含方法、请求目标和版本,如 GET /report HTTP/1.1;响应状态行包含版本、状态码及可选的原因短语,如 HTTP/1.1 200 OK。2022 年发布的 RFC 9112 规定应将消息解析为八位字节序列,而非无限制的 Unicode 文本:在确定消息边界前进行解码可能导致不一致的解释。发送方必须遵循规定的语法(包括 CRLF 分隔符),且不得生成已过时的折叠字段行。接收方虽有少许宽容解析的余地,但不同接收方之间的解释差异会带来安全隐患。
消息分帧决定了一条消息在何处结束、下一条消息在何处开始。Content-Length: 1200 指定消息体严格为 1200 个八位字节,而非字符。在包含 Transfer-Encoding: chunked 时,分块传输编码会将消息体划分为若干块,每一块均以其十六进制表示的大小和 CRLF 开头,并以 CRLF 结尾。大小为零的分块标志着数据结束,其后跟随可选的尾部字段和最终的空行以完成整条消息。例如,4\r\nWiki\r\n0\r\n\r\n 承载了 Wiki 这四个内容字节,其中 \r\n 表示 CRLF。发送方不得同时包含 Content-Length 与 Transfer-Encoding;二者共存可能预示着分帧攻击。处理此类消息时,传输编码优先,且在转发时必须移除接收到的 Content-Length。
消息体长度的规则还取决于响应状态和请求方法。对 HEAD 请求的响应,以及状态码为 1xx、204 或 304 的响应,均在头部字段后的空行处结束,不得包含消息体或尾部字段。对 CONNECT 的成功 2xx 响应则立即转入隧道操作。既未指定传输编码也未指定内容长度的请求,其消息体长度为零;关闭连接不能作为请求内容的边界划分依据。在允许的情况下,没有显式长度分帧的响应会在服务器关闭连接时结束。这种依靠关闭连接定界的形式阻止了连接复用,且无法可靠区分传输完毕与传输中断,因此显式分帧更为理想。
持久连接是 HTTP/1.1 的默认模式,与早期 HTTP 实现中常见的“单次请求即开闭连接”模式不同。多次交互可以复用同一条传输控制协议连接,从而避免反复建立连接的开销。持久性要求消息边界必须明确无歧义:客户端必须在复用连接前消费完整响应,而服务器必须消费请求体或直接关闭连接。Connection: close 标明连接在本次交互结束后不再保持;但它不能取代消息分帧规则。尽管如此,持久连接仍可能因超时或网络故障而意外断开,因此重试行为取决于请求方法的语义以及是否已收到响应。
管线化允许客户端无需等待前一个请求的响应即可连续发送多个请求。即便服务器可以并发处理安全请求,也必须按照收到请求的顺序返回相应的最终响应。因此,连接上较慢的第一个响应会延迟交付后续的所有响应:即应用层队头阻塞。这与 TCP 层的阻塞不同,后者是指传输流中丢失的字节会阻碍后续字节的交付。管线化无法提供独立交错的响应流,加之其在错误恢复和互操作性方面的难题,限制了其实际部署;HTTP/2 的多路复用则采用了不同的机制。
Expect: 100-continue 允许客户端先发送请求头并作短暂等待,然后再传输可能十分庞大的消息体。服务器可以返回 100 Continue,也可以立即返回最终响应(例如身份验证失败);客户端无需无休止等待即可发送内容。分块消息可在尾部携带生成较晚的字段(例如完整性元数据);Trailer 字段可预先声明预期的尾部字段名,而 TE: trailers 则表示愿意接收响应尾部。尾部字段不能替代处理内容前所需的字段。最后,通过 Connection: Upgrade 指定的 Upgrade 用于提议在现有连接上切换协议;接受切换的服务器会返回 101 Switching Protocols。后续的 HTTP 版本保留了许多相关语义,但采用帧和流而非分块分隔符,且不再使用 HTTP/1.1 这种针对整个连接的 Upgrade 交换机制。
HTTP/2 分帧与多路复用
HTTP/2 保留了 HTTP 的方法、状态码和表述语义,同时用二进制帧取代了 HTTP/1.1 的文本消息分帧机制。其当前规范为 RFC 9113(2022年),废弃了 RFC 7540(2015年)。每个帧都以 9 字节长的头部开头,包含载荷长度、帧类型、标志位以及一个 31 位的流标识符。流 0 保留用于连接级别的通信;其他标识符则用于区分该连接内的各个交互。HEADERS 帧承载压缩后的字段块(必要时配合 CONTINUATION 帧使用),DATA 帧则承载消息内容。SETTINGS 帧用于传递并发限制、最大帧大小等参数。GOAWAY 帧用于通知连接即将关闭,并指出可能已被处理的由对端发起的最高编号流,帮助客户端确定哪些请求可能需要重试。
一个请求及其响应通常占用一个由客户端发起的流,该流由奇数编号标识。属于不同流的帧可以交错传输:服务器可以先传输一张大图响应的部分数据,随后响应一个较小的元数据请求,然后再恢复传输该图像。在单个流内部,消息顺序仍是确定的;但不同流上的响应无需按照请求发起的顺序完成。这与 HTTP/1.1 的流水线机制不同:在流水线机制中,一个连接上可以有多个未完成的请求,但它们的响应必须按请求顺序返回。END_STREAM 标志用于标记流在单向上的传输完成;它不会关闭底层连接。端点可以通过 SETTINGS_MAX_CONCURRENT_STREAMS 限制对端发起的并发流数量。
字段节使用在 RFC 7541(2015年)中标准化的 HPACK 压缩,而非重复传输完整的文本字段行。HPACK 结合了预定义的静态表、连接范围的动态表以及可选的字符串霍夫曼编码。动态表条目可以通过索引引用的方式表示反复出现的名称和值,例如权威主机(authority)或 Cookie 值。每个传输方向都维护着各自的压缩上下文,并在该方向的所有流之间共享;因此编码器和解码器必须保持表状态的同步。SETTINGS_HEADER_TABLE_SIZE 限制了对端编码器可以使用的表容量。伪首部字段取代了原先在请求行或状态行中携带的信息:常规请求使用 :method、:scheme 和 :path,通常还包含用于标识目标主机的 :authority;响应则使用 :status。伪首部字段必须位于常规字段之前,且常规字段的名称必须全部为小写。
流量控制在流和连接两个级别上独立运行,且仅适用于 DATA 帧,不适用于 HEADERS 或其他控制帧。发送方在传输数据前,必须在两个窗口中都拥有足够的额度。初始状态下,两个窗口均为 65,535 字节;SETTINGS_INITIAL_WINDOW_SIZE 用于修改流的初始窗口大小,而 WINDOW_UPDATE 则用于增加流或连接的可用额度。这些机制允许接收方在限制入站内容的同时,不阻断所有控制流量。RST_STREAM 帧用于取消单个流,而不会终止其他流。服务器推送(Server Push)是另一项协议特性:PUSH_PROMISE 帧将承诺的请求与现有流关联起来,推送的响应则使用由服务器发起的偶数编号流。客户端可以通过 SETTINGS_ENABLE_PUSH 禁用推送;由于各端实现的差异以及浏览器的支持策略不同,协议层面的支持并不意味着其得到了广泛应用。
多路复用并没有消除底层 TCP 连接中的队头阻塞问题。TCP 提供严格按序交付的字节流,因此一旦发生报文段丢失,后续字节即使包含无关流的帧,也无法被交付给 HTTP/2。因此,丢包可能会导致共享该连接的所有流暂时停滞。流级别的流量控制和取消机制无法消除这种传输层依赖;HTTP/3 则通过采用不同的传输层映射解决了这一问题。
连接合并(Connection coalescing)允许客户端在服务器对多个源(origin)均具有权威性的情况下,为这些不同的源复用同一个 HTTP/2 连接。对于 HTTPS,证书必须对追加的主机名有效,且客户端必须确认该连接确实连接到了合适的服务器(通常通过匹配 DNS 解析出的地址实现)。仅有证书覆盖范围并不足以允许任意的跨源复用。若服务器无法或不愿在该连接上处理某个请求,可以返回 421 Misdirected Request 状态码,使客户端能够通过其他连接重试。连接合并会保留每个请求的权限信息,不会合并浏览器的同源安全上下文。
HTTP/3 与 QUIC
HTTP/3 于 2022 年 6 月在 RFC 9114 中完成标准化,它将 HTTP 语义映射到 QUIC 而非直接映射到 TCP。QUIC 版本 1 于 2021 年 5 月在 RFC 9000 中规范化,其安全集成定义在 RFC 9001 中。QUIC 运行于 UDP 之上,但提供了 UDP 本身所缺乏的传输层功能:流内可靠交付、丢包检测、流量控制以及拥塞控制。QUIC 整合了 TLS 1.3 握手以验证服务器身份并建立加密密钥;应用数据在 QUIC 数据包内部受到保护,而不是通过常规 TLS 记录进行承载。HTTP 方法、状态码和表征语义保持不变。标准 HTTP/3 协商的应用层协议标识符为 h3。
每个常规的请求-响应交互都占用一条由客户端发起的双向 QUIC 流:客户端向一个方向发送请求,服务器向另一个方向发送响应。包括 HEADERS 和 DATA 在内的 HTTP/3 帧在该流上传输字段与内容。QUIC 在每个流内部可靠且按顺序交付字节,但不同流之间不需要保证交付顺序。如果一个丢失的数据包包含了某一个响应的数据,另一个数据已到达的响应可以继续处理,而无需等待该丢失恢复。这消除了 TCP 对 HTTP/2 多路复用流所施加的传输层队头阻塞。不过,这种独立性并非绝对:单个数据包可包含来自多个流的数据,拥塞控制管理它们的聚合流量,且连接级流量控制限制或连接故障都会影响所有交互。
HTTP/3 还使用单向流进行连接管理。每个端点都会打开一条控制流,其首个帧必须为 SETTINGS 帧;后续控制帧可包含支持平稳关闭的 GOAWAY 帧。字段部分使用 QPACK 进行压缩(于 2022 年在 RFC 9204 中标准化)。QPACK 将预定义的静态表与连接专用的动态表相结合,使重复的字段名和字段值能够以紧凑形式表示。与 HTTP/2 的 HPACK 不同,QPACK 专为可以独立到达的流而设计。专用的编码器流传递动态表的变更,而解码器流则传递确认和取消信息。如果一个字段部分引用了尚未接收到的动态表条目,其处理仍可能因等待该条目而发生阻塞。设置项可以限制暴露于此类阻塞中的流的数量,编码器也可以通过使用字面量或已有条目来避免依赖。因此,QPACK 旨在有效管理压缩依赖,而非将其彻底消除。
QUIC 将传输连接的建立与 传输层安全协议 1.3 握手合并在一起。新连接通常只需一次网络往返即可开始交换应用数据;若服务器要求通过 Retry 数据包进行地址验证,则会增加一次往返。连接标识符(Connection ID)使得数据包即使在网络地址或端口发生变化时仍能与该连接保持关联,而无需完全依赖地址和端口构成的四元组。在对端支持和路径验证的前提下,这支持了连接迁移,例如移动设备从 Wi-Fi 切换至蜂窝网络时仍能保持连接。会话恢复还可以选择性地以零往返时间(0-RTT)的早期数据形式携带 HTTP 请求。此类数据可能被重放,因此是否允许发送取决于请求的语义及应用策略;仅靠加密并不能防范重放。不愿处理早期请求的服务器可以返回 425 Too Early,允许客户端在握手完成后重试。
客户端可以通过 Alt-Svc 响应字段发现 HTTP/3,例如 Alt-Svc: h3=":443"; ma=86400,用于在指定的有效期内通告 UDP 443 端口上的替代服务。这会在不改变资源源(origin)的情况下更改连接目标。2023 年在 RFC 9460 中标准化的域名系统 HTTPS 记录也可以通过 alpn 参数通告支持的协议,从而有可能在发起首次 HTTP 交互之前就获知 HTTP/3 的可用性。但通告并不保证一定可达:防火墙、被拦截的 UDP 或其他网络状况都可能阻止 QUIC 连接。客户端可以并行发起多个连接尝试竞速,或根据服务所支持的协议及客户端自身的连接策略,回退到基于 TCP 的 HTTP/2 或 HTTP/1.1。
HTTPS 与传输安全
HTTPS 为 HTTP 引入了经过身份验证的加密传输;它并未改变 GET 或 POST 等方法或响应状态码的含义。https URI 方案用于标识针对指定源的安全访问,通常使用 443 端口。HTTP/1.1 和 HTTP/2 通常在 TCP 之上使用传输层安全协议(TLS)。而 HTTP/3 则使用 QUIC,QUIC 整合了 TLS 1.3 握手和密钥协商,并通过自身的传输机制保护数据包,而不是在 TCP 之上传输普通的 TLS 记录。2022 年发布的 RFC 9110 独立于这些特定版本的传输协议,统一定义了 HTTPS 的通用语义。
基于证书的服务器身份验证将连接与所请求 URI 中的主机名绑定。服务器会出示终端实体证书以及通常包含的中间证书;客户端构建并验证一条通向本地受信任根证书的证书链。验证内容包括证书签名、有效期、适用约束以及正在访问的目标身份。例如,仅对 shop.example 有效的证书不能用于验证 payments.example 的身份。现代服务身份验证规则使用证书“使用者可选名称”(Subject Alternative Name)扩展中的名称,而非早期的通用名称(Common Name)字段。身份验证还需要证明对端持有对应的私钥:在基于证书认证的 TLS 1.3 握手中,CertificateVerify 消息对握手上下文记录进行签名。可选的客户端证书认证允许服务器通过类似机制对客户端进行身份验证,但将该身份映射到具体账户及其权限仍属于应用程序的范畴。
TLS 在其端点之间为 HTTP 字段和内容提供机密性与完整性。因此,路径上的观察者通常无法读取请求路径、查询字符串、Cookie 或响应体,也无法在不被发现的情况下对其进行篡改。但这些保护措施并不能隐藏所有的通信元数据:目标 IP 地址、数据包大小和时序特征仍然可见,而主机名是否暴露则取决于 DNS 和握手阶段的安全保护。除非受到加密客户端问候(Encrypted Client Hello)的保护,否则服务器名称指示(Server Name Indication)扩展仍可能泄露主机名。此外,TLS 在哪里终止,保护就止步于哪里。终止浏览器连接的内容分发服务或反向代理可以读取 HTTP 报文;其到源站的独立加密连接保护的则是另一段传输。因此,HTTPS 并不保证源站应用程序本身是可信的,也不保证存储的数据是安全的,或应用程序生成的内容是无害的。
2014 年在 RFC 7301 中标准化的应用层协议协商(ALPN)允许端点在建立安全连接期间协商选择应用层协议。客户端宣告其支持的协议标识符,服务器则从中选择一个受支持的选项。对于基于 TLS-over-TCP 的 HTTP,标识符包括用于 HTTP/2 的 h2 和用于 HTTP/1.1 的 http/1.1。HTTP/3 则通过集成在 QUIC 中的 TLS 握手使用 h3。ALPN 的作用是在当前连接上标明要运行的协议;它本身并不负责发现 HTTP/3 端点,也不会将 TCP 连接转换为 QUIC。
2012 年在 RFC 6797 中规范的HTTP严格传输安全(HSTS)允许 HTTPS 服务器发送 Strict-Transport-Security 字段,指示浏览器在后续请求中强制使用安全连接。例如,max-age=31536000; includeSubDomains 会建立一条有效期为一年并涵盖所有子域名的策略。浏览器仅在经过身份验证的安全连接上接受该策略;通过明文 HTTP 接收到的首部无法建立该策略。浏览器预加载列表可以保护原本不受保护的首次访问;并且当安全连接建立失败时,HSTS 要求直接终止连接,而不允许用户手动忽略警告并绕过。混合内容(Mixed Content)处理解决的则是另一个问题:HTTPS 文档通过不安全的 HTTP 请求加载资源。浏览器会阻止不安全脚本等活动混合内容,并可能自动将包括图像在内的某些其他请求升级为 HTTPS。这些机制保障了传输安全协议的使用,但并不能证明应用程序的脚本、授权规则或业务逻辑本身是正确的。
浏览器安全与协议层威胁
同源策略限制了网页浏览器中的脚本与来自另一源的资源进行交互的方式;这里的“源”由协议方案、主机名和端口号共同定义。例如,尽管 https://shop.example 与 https://api.example 共享一个父域名,它们仍属于不同的源。跨源嵌入、页面导航以及部分请求的发送依然可行;该策略主要限制的是对响应内容及其他受保护数据的访问。跨源资源共享(CORS)允许服务器通过 Access-Control-Allow-Origin 等字段,授权指定的跨源访问响应权限。使用不在 CORS 安全列表范围内的请求方法或字段的请求,通常需要先自动发起 OPTIONS 预检请求,声明拟使用的请求方法和字段名。浏览器会在发送实际请求前先行核查预检响应。符合条件的请求即使无法被发起脚本读取其后续响应,也可以在不发送预检请求的情况下直接发出。
对于附带凭证的跨源访问,浏览器请求必须使用适用的凭证模式,同时服务器必须返回 Access-Control-Allow-Credentials: true 并明确指定允许的源,而不能使用通配符 *。Cookie 的限制规则仍然独立生效:CORS 权限并不会推翻 SameSite 属性或浏览器限制第三方 Cookie 的策略。预检请求本身通常不包含凭证。CORS 是一种由浏览器强制执行的响应访问控制机制,而非身份认证机制:非浏览器客户端无须遵循此机制,且获得许可的源也无法据此确定用户的身份或权限。因此,服务器接受某个请求与浏览器允许公开该请求的响应是两个截然不同的决策。
跨站请求伪造(CSRF)利用的是浏览器会自动附带的凭证(尤其是 Cookie)。攻击者控制的页面可以诱导用户向另一网站发起请求,服务器会将其视为已认证用户执行的操作,即使攻击者无法读取该请求的响应也是如此。相比之下,跨站脚本(XSS)发生在攻击者控制的内容作为脚本在受信任网站的安全上下文中执行之时。此类脚本可读取当前上下文可访问的应用数据,或代表该上下文发起经过身份认证的请求。带有 HttpOnly 属性的 Cookie 能阻止脚本直接读取其值,但无法阻止浏览器将其附带在由脚本发起的请求中。防 CSRF 令牌和来源检查针对的是请求的真实性;针对特定上下文的输出编码以及对脚本执行的限制,则分别防范 XSS 的不同攻击面。
当中间链路上的各接收方对请求边界的解析产生分歧时,便会出现请求走私。在 HTTP/1.1 中,对 Content-Length、Transfer-Encoding 的不一致处理或畸形的字段语法,可能导致前端代理和后端服务器将相同的字节流解析为不同的请求序列。不同协议版本之间的转换也可能引入歧义性的分帧。发布于 2022 年的 RFC 9112 针对冲突的分帧信息规定了处理规则,因为这种歧义会波及在复用连接上发起的后续请求。响应拆分关注的则是不可信数据被错误解析为响应语法的问题——例如回车符和换行符混入动态生成的字段中——这可能导致生成意外的字段或看似多出的附加响应。
缓存投毒与缓存欺骗利用了缓存与应用行为之间不同层面的脱节。在缓存投毒中,受攻击者影响的内容被缓存存储并随后提供给其他用户;其诱因之一是响应依赖了未包含在缓存键中的输入。例如,未计入缓存键的请求字段可能篡改生成的重定向目标,而缓存却将该重定向缓存在普通的请求目标 URI 之下。缓存欺骗则诱使系统存储本不应共享的内容,这通常是因为缓存将某路径视为静态资源,而应用端却将其路由至个性化端点。因此,文件名后缀、路径规范化、路由规则以及表示选择都会引发具有安全隐患的解析差异。
HTTP 还会通过元数据带来数据隐私风险。敏感的 URL 路径或查询参数可能会留存在浏览器历史记录、服务器日志、监控系统中,并在遵循 Referrer 策略的前提下传入拼写有历史遗留错误的 Referer 字段中。Cookie 和具有标识性的请求字段可用于跨请求识别用户;浏览器各项特征的组合则可用于指纹追踪。传输加密并不能对通信端点或经过授权的 TLS 终结中间设备隐瞒这些值。资源耗尽是另一种协议级别的威胁:缓慢的消息传递会长期占用连接状态,过大的字段或正文会消耗内存与处理性能,而在多路复用服务器上快速创建和取消流则会带来不成比例的处理负担。因此,请求数量、传输字节数以及计算开销从不同维度衡量了滥用流量的规模。
Web 应用、API 与流传输
在页面导航过程中,Web 浏览器通常会检索 HTML 文档并对其进行解析,以发现所依赖的资源,包括层叠样式表、JavaScript 脚本、图像和字体。这些资源是分别请求的,可能来自不同的源;缓存的响应可以消除部分网络传输。脚本可以通过 fetch() 等接口发起进一步的请求,在不替换整个文档的情况下更新页面。资源的加载不一定是线性的:浏览器可以并发请求依赖项,不过样式表处理、脚本执行以及新发现的 URL 都会影响特定请求何时开始。HTTP 提供了检索和响应语义,而 HTML 和浏览器处理规则决定了返回的资源如何呈现为可交互的渲染页面。
HTTP API 通过请求和响应公开应用程序操作和数据,其内容可以使用 JSON、XML 或二进制格式。例如,GET /orders/42 可以获取订单的表述,而 POST /orders 则提交用于创建订单的数据。HTTP 并不规定这些路径、表述模式或应用程序的业务规则。罗伊·菲尔丁(Roy Fielding)在其 2000 年的博士论文中提出的表述性状态转移(REST)是一种架构风格,而非 HTTP API 的别称。其约束条件包括无状态交互、缓存、统一接口以及分层架构;统一接口约束又包括资源标识以及超媒体驱动的应用程序状态转换。相比之下,特定于应用程序的远程过程调用(RPC)可能会将操作名称和参数提交至单个端点。仅仅将 HTTP 用作传输通道,并不会使该接口成为 REST 风格的接口。
Web 分布式创作与版本控制(WebDAV)展示了对 HTTP 超越单纯文档检索的标准化扩展。WebDAV 最初于 1999 年在 RFC 2518 中实现标准化,其核心规范于 2007 年在 RFC 4918 中进行了修订。WebDAV 增加了若干方法,包括用于检索属性和集合信息的 PROPFIND、用于更改属性的 PROPPATCH、用于创建集合的 MKCOL,以及用于资源管理的 COPY 和 MOVE。LOCK 和 UNLOCK 通过锁定令牌支持协同编辑。207 Multi-Status 响应可以携带 XML,以描述针对多个资源或属性的分别处理结果。尽管名字中带有版本控制,但 WebDAV 核心规范本身并未提供完整的版本控制系统;版本控制扩展后来在 2002 年的 DeltaV 规范(RFC 3253)中单独做了规定。
长轮询(long polling)、服务器发送事件(SSE)和增量响应流式传输(incremental response streaming)支持在无需重复下载完整页面的情况下获取更新,但它们的传递组织方式各不相同。在长轮询中,服务器会悬挂请求直到发生事件或超时才作出响应,随后客户端再发起另一个请求;重新连接可能会导致数据出现间隙,除非应用程序对事件位置进行跟踪。SSE 使用媒体类型为 text/event-stream 的持续响应,并采用 UTF-8 解码。其面向行的格式使用 data、event 和 id 等字段,并通过空行来分隔各个事件。浏览器的 EventSource 接口支持重新连接,并可发送 Last-Event-ID 以便于断点续传。更普遍地,客户端可以增量消费响应体,例如使用换行分隔的 JSON 等应用层分帧方式。无论是 HTTP/1.1 的分块边界,还是 HTTP/2 或 HTTP/3 的 DATA 帧边界,本质上都不限定应用程序记录的边界。代理缓冲、压缩缓冲区以及服务器端延迟写入都可能阻碍增量生成的内容被立即传递。
于 2011 年在 RFC 6455 中标准化的 WebSocket 提供了面向消息的双向通信,而非一系列常规的 HTTP 请求与响应。在 HTTP/1.1 中,其初始握手采用带有升级字段的 GET 请求;服务器成功响应 101 Switching Protocols 后,该连接便改由 WebSocket 帧协议接管。随后,任一端均可在无需等待对应请求的情况下发送消息。后续的 HTTP 版本采用了不同的建立机制:2018 年发布的 RFC 8441 定义了基于 HTTP/2 扩展 CONNECT 的 WebSocket,而 2022 年发布的 RFC 9220 规范了其在 HTTP/3 上的用法。在协商获得所需协议支持后,WebSocket 流量仅占用单个流,而不会将整个多路复用连接转换为 WebSocket 连接。
性能、测量与实现
观察到的请求延迟涵盖多个层面的工作:DNS 解析、连接建立、安全协商、请求传输、服务器处理以及响应交付。在新建的 TCP 连接上,三次握手通常会在发送普通应用数据之前增加约一个网络往返时间。基于 TCP 的完整 TLS 1.3 握手在客户端发送 HTTP 请求前通常还会再增加一个往返时间,不过会话恢复和早期数据机制可以改变这一时序。HTTP/3 则通过 QUIC 将传输层和安全层的握手合并。这些开销并非每个请求都需要重新承担:DNS 缓存、连接复用以及先前建立的安全状态都会改变各个阶段的耗时。服务器处理过程可能包括排队、数据库查询以及对其他服务的调用;因此,即使网络带宽充裕,一个很小的响应也可能会耗时较长。
连接池、压缩、多路复用和优先级排序针对不同的瓶颈起作用,且可能相互影响。连接池复用已建立的连接,避免了重复握手,但其容量大小会影响并发能力和服务器的资源消耗。HTTP/2 和 HTTP/3 实现了请求的多路复用,减少了对大量并行连接的需求;然而,共享的拥塞控制和带宽仍会限制总体的传输进度。文本压缩以消耗 CPU 为代价减少了传输字节数,而对已压缩的图像进行二次压缩则几乎无益。字段压缩减少了重复的元数据,但引入了连接特有的压缩状态。优先级排序可以让渲染所需的资源优先于后台传输,但其实际效果取决于整个交付路径上的调度决策。2022 年发布的 RFC 9218 定义了可扩展的优先级信号,包括从 0(最高优先级)到 7(最低优先级)的紧迫度值;这些信号仅供参考,并不构成交付保证。
协议的实现形式多种多样,涵盖浏览器、命令行客户端、应用库、源服务器以及托管网关。Apache HTTP Server 和 nginx 既可以直接提供内容,也可以充当反向代理;curl 提供了命令行客户端,而 libcurl 则将传输功能嵌入其他软件中。Java 的 java.net.http.HttpClient 和 Go 的 net/http 包通过编程接口暴露 HTTP 功能,但在连接池、超时和流处理行为上各有不同。托管网关通常负责终结安全连接、路由请求并实施服务配额限制。必须针对具体的软件版本、构建配置和连接跳步来评估协议支持情况:例如,浏览器可以与边缘服务协商使用 HTTP/3,而该边缘服务则通过 HTTP/1.1 将请求转发给源站。
测量工具揭示了一次交互过程中的不同阶段。访问日志记录了服务器侧观察到的事件,如状态码、传输字节数和处理耗时,但通常不包含客户端前期的 DNS 查询和连接建立过程。浏览器开发者工具展示了请求瀑布流、缓存使用情况、协商协议以及细分的时间线分布。curl 可以报告名称解析、连接建立、接收到首字节以及传输完成的累积时间。因此,解读首字节时间必须结合其测量边界:从页面导航开始测得的值与在请求发送完毕后测得的值所包含的工作完全不同。吞吐量可能指应用内容的传输速率,也可能指每秒的链路总字节数;而错误率则需要明确的分母,以及对 HTTP 错误、超时和连接失败的具体分类。数据包分析能够揭示重传和连接行为,但加密的 HTTP 内容需要端点访问权限或相应的会话密钥才能查看。分布式链路追踪将传入请求与下游操作关联起来;2020 年的 W3C Trace Context 规范对用于传递追踪上下文的 traceparent 和 tracestate 字段进行了标准化。
互操作性测试考察客户端、服务器与中间件的组合行为,而不是仅满足于一次成功的直接交互。相关的测试用例包括连接复用、并发流、请求取消、超大字段部分、分块结尾字段、慢速接收端以及网关处的协议转换。畸形消息测试用于检验各实现是否能够一致地拒绝无效帧,而压力测试和故障测试则能揭示系统资源极限与恢复行为。协议合规性与实际运行兼容性是两码事:当中间件不支持某项可选功能时,即使该功能完全合规也可能无法使用;两个实现之间能成功通信,并不代表它们都能正确处理所有必须支持的场景。
可扩展性与部署约束
HTTP 扩展通过互联网号码分配局(IANA)的注册表进行协调,这些注册表涵盖请求方法、状态码、字段名以及特定版本的参数(如 HTTP/2 和 HTTP/3 的设置项)。注册操作记录了一个标识符及其定义规范;但这并不意味着已有实现都支持它。各个注册表的注册流程各不相同,有些需要经过标准审查,有些则允许依据已发布的规范进行登记。应用程序也可以定义专有字段或私有约定,而不必创建通用的互操作扩展:例如某个服务能识别的 Example-Account-ID 字段,并没有通用的 HTTP 含义。2012 年发布的 RFC 6648 废弃了使用 X- 前缀来区分私有参数的通用做法,部分原因在于这些实验性名称经常演变为永久性的部署依赖。
结构化字段值为新的 HTTP 字段提供了可复用的语法和解析规则。该规范最初于 2021 年在 RFC 8941 中实现标准化,并于 2024 年由 RFC 9651 修订,定义了条目(item)、列表(list)、字典(dictionary)、参数(parameter)以及整数、字符串、词元(token)和字节序列等数据类型。字典可以表示带有附加参数的具名成员,而无需每个字段设计者自行构想另一套引号、转义或分隔符约定。2022 年在 RFC 9218 中定义的 Priority 字段提供了一个具体范例:Priority: u=1, i 利用结构化字典表达了紧急程度和渐进式处理偏好。这种通用语法减少了各实现之间的解析差异,但字段规范仍必须定义每个成员的含义、允许的值、默认值以及错误处理方式。当既有字段已确立的语法不同时,不能简单地将其重新解释为结构化字段。
兼容性取决于具体扩展的规则,而不是一条“忽略一切陌生内容”的通用指令。代理通常会转发无法识别的字段,除非它们专属于特定连接或转发受到了其他限制。未知的状态码会按其所属类别来解释——例如未知的 4xx 状态码会被当作常规的客户端错误——但带有未知状态码的响应严禁被缓存。HTTP/2 要求忽略未知的设置标识符,从而使新的设置能够与旧版对端共存。需要明确协商的功能必须有显式信号:例如 2018 年在 RFC 8441 中标准化的 HTTP/2 扩展 CONNECT 机制,客户端必须在接收到 SETTINGS_ENABLE_CONNECT_PROTOCOL 之后才能使用该机制。若复用已有的方法或字段但赋予不兼容的语义,可能会导致旧版客户端、缓存和网关在不知情的情况下沿用原先的解释。
当已部署的组件强制执行比协议允许范围更狭窄的假设时,就会发生协议僵化。网关可能会拒绝不熟悉的方法、剥离字段,或者假定所有有效响应都必须使用某组固定的状态码。这种行为可能导致一个本应有效的扩展在其他部分均正常的通信链路中无法使用。加密限制了非参与网络设备检查和约束 HTTP 语法的机会,有助于为协议演进保留空间。然而,加密并不能消除端点或终止 TLS 的反向代理所施加的约束,因为它们依然需要解析消息。协议部署还取决于传输层的可达性:封锁 UDP 的网络可能会阻碍 HTTP/3 的使用(即使双方端点都支持),因此在运维层面上,回退到其他 HTTP 版本至关重要。
采纳情况的测量必须区分支持能力与实际使用量。网站调研可能会统计成功响应 HTTP/3 探测的原站数量;浏览器测量可能会统计通过 HTTP/3 传输的请求数;而网络运营商可能会报告其传输字节数的占比。这些分母并不互通:少数大文件下载可能会在字节量上占据主导地位,而支持多种版本的网站也可能与不同的客户端协商不同的版本。有意义的指标结论会指明测量日期、采样群体、地域和客户端覆盖范围、对失败尝试的处理方式,以及测量单位是网站数、请求数、连接数还是字节数。发现状态、连接复用、中介组件配置以及回退行为,都可能导致对外宣称的支持与实际协商的流量之间产生差异。