# 1. 快速 HTTP 提示

> HTTP 事务、请求、响应、头和协议术语

---

LLMS 索引： [llms.txt](/zh/llms.txt)

---

<!-- Generated by scripts/generate-haproxy-docs.py from pinned upstream text. -->

本文涵盖上述指定版本中实现的配置语言。
本文不提供任何提示、示例或建议。
如需此类文档，请参阅参考手册或架构手册。
编号章节在 HAProxy 扁平侧边栏中按顺序排列，支持直接导航。

当 HAProxy 以 HTTP 模式运行时，请求和响应均会被完整分析并索引，因此可基于内容中发现的几乎任何信息构建匹配条件。

然而，理解 HTTP 请求和响应的构成方式，以及 HAProxy 如何对其进行解析，至关重要。掌握这些原理后，编写正确的规则以及排查现有配置将变得更加容易。

首先，HTTP 由一系列 RFC 标准化，HAProxy 尽可能严格地遵循这些标准：

- RFC 9110：HTTP 语义（解释协议元素的含义）
- RFC 9111：HTTP 缓存（解释 HTTP 缓存应遵循的规则）
- RFC 9112：HTTP/1.1（表示形式、互操作性规则、安全性）
- RFC 9113：HTTP/2（表示形式、互操作性规则、安全性）
- RFC 9114：HTTP/3（表示形式、互操作性规则、安全性）

此外，RFC 8999 至 9002 规定了 HTTP/3 协议所使用的 QUIC 传输层。

## 1.1. HTTP 事务模型 {#section-1-1}

HTTP 协议是基于事务的。这意味着每个请求只会对应一个且仅一个响应。最初，在协议版本 1.0 时，每个连接仅支持一个请求：客户端与服务器建立 TCP 连接，客户端通过该连接发送请求，服务器返回响应，随后连接关闭。新的请求则需要建立新的连接：

```text
[CON1] [REQ1] ... [RESP1] [CLO1] [CON2] [REQ2] ... [RESP2] [CLO2] ...
```

在该模式下，通常称为 "HTTP close" 模式，连接建立次数与 HTTP 事务次数相同。由于服务器在发送响应后关闭连接，客户端无需知晓内容长度，当连接关闭时即认为响应已完整。这也意味着，若因网络错误导致部分响应被截断，客户端可能误认为响应已完整，从而导致图像偶尔出现渲染不全的情况。

由于协议的事务性特征，可以对其进行优化，以避免在两个后续事务之间关闭连接。然而，在此模式下，服务器必须为每个响应标明内容长度，以免客户端无限期等待。为此，使用了一个特殊头："Content-length"。此模式称为 "keep-alive" 模式，随 HTTP/1.1 一同引入（部分 HTTP/1.0 代理也支持），在请求间复用的连接被称为“持久连接”：

```text
[CON] [REQ1] ... [RESP1] [REQ2] ... [RESP2] [CLO] ...
```

其优势在于事务间的延迟更低，服务器端所需的处理能力更少，并且能够检测到响应被截断的情况。通常情况下，该模式比关闭模式更快，但并非总是如此，因为部分客户端通常会将其并发连接数限制在较低值，因而对较差网络连接的补偿作用更小。此外，部分服务器需长时间保持连接以等待可能到来的新请求，可能导致因连接数量过多而产生较高的内存使用，过快关闭连接可能中断恰好在连接关闭瞬间到达的请求。

在此模式下，响应大小需事先知晓，因此对于动态生成或压缩的内容，这并不总是可行。为此，实现了另一种模式，即“分块传输模式”，在该模式中，发送方不再一次性声明整个响应的大小，而是仅通告其当前缓冲区中已有的下一段 "chunk" 响应的大小，并可在任意时刻以大小为零的分块终止传输。在此模式下，不使用 Content-Length 头。

通信机制的另一项改进是流水线模式。该模式仍使用持久连接，但客户端无需等待首个响应即可发送第二个请求。此模式在获取构成页面的大量图像时尤为有用：

```text
[CON] [REQ1] [REQ2] ... [RESP1] [RESP2] [CLO] ...
```

这显然能显著提升性能，因为后续请求之间的网络延迟被消除。许多 HTTP 代理并不能正确支持流水线，因为在 HTTP 中无法将响应与对应的请求关联。因此，服务器必须以与接收请求完全相同的顺序进行回复。实际上，经过多个客户端多次尝试部署后，由于在某些服务器上可靠性不足，该机制已被彻底放弃。但服务器必须支持此功能。

下一个改进是多路复用模式，如 HTTP/2 和 HTTP/3 中所实现。在此模式下，多个事务（即请求-响应对）可在单个连接上并行传输，且各自以独立速度推进，互不干扰。采用多路复用协议时，引入了 "stream" 的新概念，用于表示在同一连接上发生的并行通信。每个流通常为特定连接分配唯一的标识符，两端均使用该标识符确定数据的交付位置。客户端在同一个连接上同时开启多个流（最多可达 100 个，有时更多）十分常见，由服务器负责整理这些请求，并根据已有响应的情况以任意顺序进行响应。多路复用模式的主要优势在于显著减少了往返次数，从而加快了高延迟网络上的页面加载速度。在使用大量图片的网站上，这一特性有时可明显观察到所有图片几乎同时加载。

这些协议通过采用某些机制压缩头字段，以减少网络传输的字节数，因此在缺乏适当工具的情况下，它们无法像 HTTP/1 那样通过人工方式实际操作或肉眼直接阅读。出于这一原因，尽管协议版本更新，文献（包括本文）中仍继续使用 HTTP/1 语法来表示各种 HTTP 消息示例。

HTTP/2 存在一些设计上的局限性，例如数据包丢失会同时影响所有流，若客户端获取对象耗时过长（例如需要将其存储到磁盘），可能会导致其自身获取速度变慢，并在此期间无法访问其后方待处理的数据。这被称为“队头阻塞”或“HoL 阻塞”，有时也简称为 "HoL"。

HTTP/3 基于 QUIC 实现，而 QUIC 本身基于 UDP 实现。QUIC 通过独立处理流的方式，在传输层解决了队首阻塞问题。当发生丢包时，受影响的流不会影响其他流，所有流均可并行访问。QUIC 还提供连接迁移支持，但当前 HAProxy 尚不支持该功能。

默认情况下，HAProxy 以持久连接模式运行：对于每个连接，它在处理完每个请求和响应后，会在响应结束与新请求开始之间使两侧连接保持空闲。当从客户端接收 HTTP/2 连接时，它会并行处理所有请求，并保持连接空闲，等待新的请求，其行为与持久连接的 HTTP 连接相同。

HAProxy 本质上支持三种连接模式：

- keep alive：所有请求和响应均被处理，面向客户端和面向服务器的连接将保持活跃，以供新请求使用。这是默认行为，适用于现代 Web 及现代协议（HTTP/2 和 HTTP/3）。

- 服务端关闭：在响应发送完成后，面向服务器的连接被关闭。

- close：在双方响应结束后，主动关闭连接。

此外，默认情况下，面向服务器的连接可被任意客户端的任意请求复用，这是由 HTTP 协议规范所要求的，因此如需使用特定客户端的信息（例如客户端的源地址等），必须随每个请求一并传递。当使用 HTTP/2 与服务器通信时，默认情况下 HAProxy 会将该连接专用于同一客户端，以避免客户端之间出现队头阻塞风险。

## 1.2. 术语 {#section-1-2}

在 HAProxy 中，术语随着时代演进而有所演变，以适应 HTTP 协议及其使用方式的发展。最初，连接、会话、流或事务之间并无显著区别，但随着时间推移，这些术语逐渐明确，以更贴近现代 HTTP 协议的实际形态，尽管部分术语仍保留在配置或命令行界面中，以维持历史兼容性。

以下是适用于当前 HAProxy 版本的一些定义：

- 连接：连接是客户端或服务器等远程代理与 HAProxy 之间在最低层级上建立的单个双向通信通道。通常对应于一对 IP 地址和端口之间建立的 TCP 套接字。在面向客户端的一侧，当客户端连接到 HAProxy 时，连接是首先被实例化的实体，作用于连接层级的规则是最早生效的规则。

- 会话：会话为连接附加了一些上下文信息，包括与传输层相关的信息（例如 TLS 密钥等）或变量。该术语长期以来在 HAProxy 中用于表示两端之间的端到端 HTTP/1.0 通信，因此尽管如今其含义已演变为流，但某些 CLI 命令或统计信息的名称中仍保留该术语，不过帮助消息和描述力求使其含义清晰明确。在网络层术语中（例如操作系统中的 TCP 会话，或防火墙两侧的 TCP 会话）或非 HTTP 的用户级应用中（例如 Telnet 会话或 SSH 会话），该术语仍然适用。不得将其与“应用会话”混淆，后者用于在 Cookie 中存储完整的用户上下文，并要求始终将请求发送至同一服务器。

- 流：流在应用层精确对应于端到端的双向通信，可在其上应用分析和转换。在 HTTP 中，流包含一个请求及其关联的响应，由请求到达时创建，并在响应传输结束时终止。在此上下文中，此类流与多路复用协议的流之间存在一对一关系。在 TCP 通信中，每个连接对应单一的流。

- 事务：事务仅指一个请求及其关联的响应。该术语在流出现之前曾与会话一同使用，但如今事务与流之间存在一对一关系。其本质体现在变量作用域 "txn" 中，该作用域贯穿整个事务，因此也覆盖整个流。

- 请求：指从客户端流向服务器的流量。主要用于 HTTP，表示操作执行的位置。该术语在 TCP 操作中也存在，用于表示数据处理的位置。请求通常以流量或活动单位的形式出现在计数器中。请求不一定意味着存在响应（例如由于错误），但由于没有请求就不会有自发响应，因此请求仍是衡量整体活动的合理指标。在 TCP 中，请求的数量与连接数量相等。

- 响应：指从服务器流向客户端的流量，或在 HAProxy 自行生成响应时（例如 HTTP 重定向），从 HAProxy 流向客户端的流量。

- 服务：通常表示 HAProxy 内部无需服务器参与的处理过程，例如统计页面、缓存或用于实现小型应用的 Lua 代码。服务通常会读取请求，执行某些操作，并生成响应。

## 1.3. HTTP 请求 {#section-1-3}

首先，考虑以下 HTTP 请求：

```text
行号     内容

   1     GET /serv/login.php?lang=en&profile=2 HTTP/1.1
   2     Host: www.mydomain.com
   3     User-agent: my small browser
   4     Accept: image/jpeg, image/gif
   5     Accept: image/png
```

### 1.3.1. 请求行 {#section-1-3-1}

第 1 行为“请求行”。它始终由 3 个字段组成：

- 方法：GET
- URI：/serv/login.php?lang=en&profile=2
- 版本标签：HTTP/1.1

所有字段均以标准所称的 LWS（线性空白字符）分隔，通常为空格，但也可能为制表符，或换行符/回车符后跟空格/制表符。方法本身不得包含冒号（':'），且仅限字母字符。这些多种组合使得 HAProxy 自行执行分割操作更为可取，而非由用户编写复杂或不准确的正则表达式。

URI 本身可以有多种形式：

- 相对 URI：

```text
  /serv/login.php?lang=en&profile=2

这是不含主机部分的完整 URL，通常是服务器、反向代理和透明代理接收到的形式。
```

- "绝对 URI"，也称为 "URL"：

```text
  http://192.168.0.12:8080/serv/login.php?lang=en&profile=2

它由 "scheme"（协议名称后接 '://'）、主机名或地址、可选的冒号（':'）及端口号，
以及从地址部分后的第一个斜杠（'/'）开始的相对 URI 组成。
这是代理通常接收到的形式，但支持 HTTP/1.1 的服务器也必须接受此形式。
```

- 星号（'\*'）：此形式仅在与 OPTIONS 方法关联时被接受，且不可中继。它用于查询下一跳的能力。

- 地址：端口组合：192.168.0.12:80。此形式与 CONNECT 方法配合使用，该方法用于通过 HTTP 代理建立 TCP 隧道，通常用于 HTTPS，有时也用于其他协议。

在相对 URI 中，识别出两个子部分。问号之前的部分称为 "path"，通常为服务器上静态资源的相对路径。问号之后的部分称为“查询字符串”，主要用于发送至动态脚本的 GET 请求，其具体格式高度依赖于所使用的语言、框架或应用程序。

HTTP/2 和 HTTP/3 不会在请求中传递版本信息，因此默认认为其版本与底层协议一致（即 "HTTP/2"）。此外，这些协议不会将请求行作为一个整体发送，而是将其拆分为若干字段，称为 "pseudo-headers"，其名称以冒号开头，HAProxy 会将其方便地重新组合为等效的请求行。因此，日志中记录的请求行在 HTTP/1.x 与 HTTP/2 或 HTTP/3 之间可能存在细微差异。

### 1.3.2. 请求头 {#section-1-3-2}

头从第二行开始。头由行首的名称组成，名称后立即跟一个冒号（':'）。传统上，冒号后会添加一个线性空白字符（LWS），但并非必须。随后是值。多个相同的头可以合并为一行，通过逗号分隔值，前提是必须保持顺序。这在 "Cookie:" 字段中较为常见。如果后续行以 LWS 开头，头可以跨多行。在 1.3 节的示例中，第 4 行和第 5 行共同为 "Accept:" 头定义了总共 3 个值。最后，根据规范，头开头或结尾的所有 LWS 均被忽略，不计入值中。

与常见误解相反，头名称不区分大小写，其值在引用其他头名称（如 "Connection:" 头）时同样不区分大小写。在 HTTP/2 和 HTTP/3 中，头名称始终以小写形式发送，这一点在启用调试模式运行时可以观察到。内部实现中，所有头名称均被规范化为小写，以确保 HTTP/1.x 与 HTTP/2 或 HTTP/3 使用完全相同的表示形式，并在另一端原样发送。这解释了为何以驼峰命名法输入的 HTTP/1.x 请求在接收时会以小写形式呈现。

头的结束由第一个空行指示。人们常说这是两个换行符，这并不准确，尽管两个换行符是空行的一种有效形式。

幸运的是，HAProxy 在处理头索引、值检查和计数时会自动处理所有复杂的组合，因此无需担心头的写法，但若应用程序执行了非寻常但合法的操作，不应将其归咎于存在缺陷。

重要提示：

```text
按照 RFC7231 的建议，HAProxy 将头中间的换行替换为 LWS，以合并多行头并规范化头信息。
这对于正确分析十分必要，也有助于能力较弱的 HTTP 解析器正确工作，
避免受到此类复杂结构的欺骗。
```

## 1.4. HTTP 响应 {#section-1-4}

HTTP 响应与 HTTP 请求非常相似。两者均称为 HTTP 消息。请考虑以下 HTTP 响应：

```text
行号     内容

   1     HTTP/1.1 200 OK
   2     Content-length: 350
   3     Content-Type: text/html
```

作为特例，HTTP 支持所谓的“信息性响应”，即状态码 1xx。这类消息的特殊之处在于，它们不携带响应的任何部分，仅用作一种信号，例如通知客户端继续发送其请求。对于状态码 100 的响应，所请求的信息将由后续非 100 状态码的响应消息携带。这意味着单个请求可能收到多个响应，且此机制仅在启用持久连接时有效（1xx 消息出现在 HTTP/1.1 中）。HAProxy 能够正确处理此类消息，可将其转发并跳过，仅处理下一个非 100 状态码的响应。因此，除非明确另行说明，否则这些消息既不会被记录，也不会被转换。状态码 101 的响应表示协议将在同一连接上发生变更，HAProxy 必须切换至隧道模式，如同发生了 CONNECT 请求一般。此时，Upgrade 头将包含关于连接所切换协议类型的附加信息。

### 1.4.1. 响应行 {#section-1-4-1}

第 1 行是“响应行”。它始终由 3 个字段组成：

- 版本标签：HTTP/1.1
- 状态码：200
- 原因：OK

状态码始终为三位数字。第一位数字表示总体状态：

- 1xx = 信息性消息，应跳过（例如 100、101）
- 2xx = 成功，后续有内容（例如 200、206）
- 3xx = 成功，后续无内容（例如 302、304）
- 4xx = 客户端引起的错误（例如 401、403、404）
- 5xx = 服务器引起的错误（例如 500、502、503）

状态码大于 599 时，不得在通信中发出，尽管某些代理可能会在日志中生成此类状态码以报告其内部状态。有关所有此类状态码的详细含义，请参阅 RFC9110。HTTP/2 及以上版本不使用版本标签，而是使用 ":status" 伪头来报告状态码。

"reason" 字段仅作为提示，客户端不会解析该字段。该字段可包含任意内容，但通常建议遵循已确立的通用消息规范。其内容可由一个或多个单词组成，例如 "OK"、"Found" 或 "Authentication Required"。该字段在 HTTP/2 及以上版本中不存在，也不会在这些版本中发出。当从 HTTP/2 或更高版本返回的响应传输至 HTTP/1 客户端时，HAProxy 将生成与状态码匹配的通用原因字段。

HAProxy 可能自行发出以下状态码：

```text
代码  使用时机 / 原因
 200  访问统计信息页面，以及响应监控请求时
 301  执行重定向时，取决于配置的状态码
 302  执行重定向时，取决于配置的状态码
 303  执行重定向时，取决于配置的状态码
 307  执行重定向时，取决于配置的状态码
 308  执行重定向时，取决于配置的状态码
 400  请求无效或过大时
 401  执行动作需要认证时（访问统计信息页面时）
 403  请求被 "http-request deny" 规则禁止时
 404  找不到请求的资源时
 408  请求尚未完成就达到请求超时时
 410  请求的资源已不可用，且以后也不会恢复可用时
 413  HTTP/1.0 的 GET/HEAD/DELETE 请求包含负载时，另见 "h1-accept-payload-with-any-method" 选项
 500  HAProxy 遇到不可恢复的内部错误时，例如内存分配失败；此类情况本不应发生
 501  HAProxy 因不支持某项功能而无法满足客户端请求时
 502  服务器返回空响应、无效响应或不完整响应时，或响应被 "http-response deny" 规则阻止时
 503  没有可用服务器处理请求时，或响应匹配 "monitor fail" 条件的监控请求时
 504  服务器尚未响应就达到响应超时时
```

上述 4xx 和 5xx 错误码可自定义（参见 [第 4.2 节](/zh/docs/haproxy/proxies/#section-4-2) 中的 "errorloc"）。其他状态码可通过特定动作主动发出（例如，参见 [第 4.3 节](/zh/docs/haproxy/proxies/#section-4-3) 中的 "deny"、"return" 和 "redirect" 动作）。

### 1.4.2. 响应头 {#section-1-4-2}

响应头的工作方式与请求头完全相同，因此 HAProxy 对两者使用相同的解析函数。详情请参见第 1.3.2 段。

---

反链：

- [HAProxy](/zh/docs/haproxy/)
- [1. 先决条件](/zh/docs/haproxy/prerequisites/)
- [资源](/zh/docs/haproxy/resources/)
