1. 快速 HTTP 提示
本文涵盖上述指定版本中实现的配置语言。 本文不提供任何提示、示例或建议。 如需此类文档,请参阅参考手册或架构手册。 编号章节在 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 事务模型
HTTP 协议是基于事务的。这意味着每个请求只会对应一个且仅一个响应。最初,在协议版本 1.0 时,每个连接仅支持一个请求:客户端与服务器建立 TCP 连接,客户端通过该连接发送请求,服务器返回响应,随后连接关闭。新的请求则需要建立新的连接:
在该模式下,通常称为“HTTP 关闭”模式,连接建立次数与 HTTP 事务次数相同。由于服务器在发送响应后关闭连接,客户端无需知晓内容长度,当连接关闭时即认为响应已完整。这也意味着,若因网络错误导致部分响应被截断,客户端可能误认为响应已完整,从而导致图像偶尔出现渲染不全的情况。
由于协议的事务性特征,可以对其进行优化,以避免在两个后续事务之间关闭连接。然而,在此模式下,服务器必须为每个响应标明内容长度,以免客户端无限期等待。为此,使用了一个特殊头:“Content-length”。此模式称为 “keep-alive” 模式,随 HTTP/1.1 一同引入(部分 HTTP/1.0 客户端也支持),在请求间复用的连接被称为“持久连接”:
其优势在于事务间的延迟更低,服务器端所需的处理能力更少,并且能够检测到响应被截断的情况。通常情况下,该模式比关闭模式更快,但并非总是如此,因为部分客户端通常会将其并发连接数限制在较低值,这在网络连接较差时难以弥补。此外,部分服务器需长时间保持连接以等待可能到来的新请求,可能导致因连接数量过多而产生较高的内存使用,过快关闭连接可能中断恰好在连接关闭瞬间到达的请求。
在此模式下,响应大小需事先知晓,因此对于动态生成或压缩的内容,这并不总是可行。为此,实现了另一种模式,即“分块传输模式”,在该模式中,发送方不再一次性声明整个响应的大小,而是仅通告其当前缓冲区中已有的下一段 “chunk” 响应的大小,并可在任意时刻以大小为零的分块终止传输。在此模式下,不使用 Content-Length 头。
通信机制的另一项改进是流水线模式。该模式仍使用持久连接,但客户端在未收到首个响应时即可发送第二个请求。此模式在获取构成页面的大量图像时尤为有用:
这显然能显著提升性能,因为后续请求之间的网络延迟被消除。许多 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. 术语
在 HAProxy 中,术语随着时代演进而有所演变,以适应 HTTP 协议及其使用方式的发展。最初,连接、会话、流或事务之间并无显著区别,但随着时间推移,这些术语逐渐明确,以更贴近现代 HTTP 协议的实际形态,尽管部分术语仍保留在配置或命令行界面中,以维持历史兼容性。
以下是适用于当前 HAProxy 版本的一些定义:
连接:连接是客户端或服务器等远程代理与 HAProxy 之间在最低层级上建立的单个双向通信通道。通常对应于一对 IP 地址和端口之间建立的 TCP 套接字。在面向客户端的一侧,当客户端连接到 HAProxy 时,连接是首先被实例化的实体,作用于连接层级的规则是最早生效的规则。
会话:会话为连接附加了一些上下文信息,包括与传输层相关的信息(例如 TLS 密钥等)或变量。该术语长期以来在 HAProxy 中用于表示两端之间的端到端 HTTP/1.0 通信,因此尽管如今其含义已演变为流,但某些 CLI 命令或统计信息的名称中仍保留该术语,不过帮助消息和描述力求使其含义清晰明确。在网络层术语中(例如操作系统中的 TCP 会话,或防火墙两侧的 TCP 会话)或非 HTTP 的用户级应用中(例如 Telnet 会话或 SSH 会话),该术语仍然适用。不得将其与“应用会话”混淆,后者用于在 Cookie 中存储完整的用户上下文,并要求始终将请求发送至同一服务器。
流:流在应用层精确对应于端到端的双向通信,可在其上应用分析和转换。在 HTTP 中,流包含一个请求及其关联的响应,由请求到达时创建,并在响应传输结束时终止。在此上下文中,此类流与多路复用协议的流之间存在一对一关系。在 TCP 通信中,每个连接对应单一的流。
transaction:事务仅指一个请求及其关联的响应。该术语在流出现之前曾与会话一同使用,但如今事务与流之间存在一对一关系。其本质体现在变量作用域 “txn” 中,该作用域贯穿整个事务,因此也覆盖整个流。
request:指从客户端流向服务器的流量。主要用于 HTTP,表示操作执行的位置。该术语在 TCP 操作中也存在,用于表示数据处理的位置。请求通常以流量或活动单位的形式出现在计数器中。请求不一定意味着存在响应(例如由于错误),但由于没有请求就不会有自发响应,因此请求仍是衡量整体活动的合理指标。在 TCP 中,请求的数量与连接数量相等。
response:此选项指定从服务器流向客户端的流量,或在 HAProxy 自行生成响应时从 HAProxy 流向客户端的流量(例如 HTTP 重定向)。
service:通常表示 HAProxy 内部无需服务器参与的处理过程,例如统计页面、缓存或用于实现小型应用的 Lua 代码。服务通常会读取请求,执行某些操作,并生成响应。
1.3. HTTP 请求
首先,考虑以下 HTTP 请求:
1.3.1. 请求行
第 1 行为“请求行”。它始终由 3 个字段组成:
- 方法:GET
- URI:/serv/login.php?lang=en&profile=2
- 版本标签:HTTP/1.1
所有字段均以标准所称的 LWS(线性空白字符)分隔,通常为空格,但也可能为制表符,或换行符/回车符后跟空格/制表符。方法本身不得包含冒号(’:’),且仅限字母字符。这些多种组合使得 HAProxy 自行执行分割操作更为可取,而非由用户编写复杂或不准确的正则表达式。
URI 本身可以有多种形式:
- 相对 URI:
- “绝对 URI”,也称为 “URL”:
星号(’*’):此形式仅在与 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. 请求头
头从第二行开始。头由行首的名称组成,名称后立即跟一个冒号(’:’)。传统上,冒号后会添加一个线性空白字符(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 在处理头索引、值检查和计数时会自动处理所有复杂的组合,因此无需担心头的写法,但若应用程序执行了非寻常但合法的操作,不应将其归咎于存在缺陷。
请注意:
1.4. HTTP 响应
HTTP 响应与 HTTP 请求非常相似。两者均称为 HTTP 消息。请考虑以下 HTTP 响应:
作为特例,HTTP 支持所谓的“信息性响应”,即状态码 1xx。这类消息的特殊之处在于,它们不携带响应的任何部分,仅用作一种信号,例如通知客户端继续发送其请求。对于状态码 100 的响应,所请求的信息将由后续非 100 状态码的响应消息携带。这意味着单个请求可能收到多个响应,且此机制仅在启用持久连接时有效(1xx 消息出现在 HTTP/1.1 中)。HAProxy 能够正确处理此类消息,可将其转发并跳过,仅处理下一个非 100 状态码的响应。因此,除非明确另行说明,否则这些消息既不会被记录,也不会被转换。状态码 101 的响应表示协议将在同一连接上发生变更,HAProxy 必须切换至隧道模式,如同发生了 CONNECT 请求一般。此时,Upgrade 头将包含关于连接所切换协议类型的附加信息。
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 可能自行发出以下状态码:
上述 4xx 和 5xx 错误码可自定义(参见 “errorloc” 在 第 4.2 节 )。其他状态码可通过特定动作主动发出(例如,参见 “deny”、“return” 和 “redirect” 动作在 第 4.3 节 )。
1.4.2. 响应头
响应头的工作方式与请求头完全相同,因此 HAProxy 对两者使用相同的解析函数。详情请参见第 1.3.2 段。