广告位 · 页头(后台-模块管理-新建模块后替换此区域)

Apache HTTP/2连接前言Preface到底有什么用?

HTTP/2协议在设计时引入了一个非常特殊但容易被忽略的步骤:连接前言,也就是Preface。当客户端和服务器完成TCP连接,并且在TLS上通过ALPN协商出h2协议后,客户端不能像HTTP/1.1那样立即发送请求行,而是必须先发送一段固定的魔法字符串。Apache的mod_http2模块正是通过严格校验这段字符串来确认对端是否真正进入了HTTP/2模式。如果这段前言缺失、被截断或者被中间设备篡改,连接会直接失败,而且错误信息往往并不直观。下面结合协议规范与Apache的实际处理逻辑展开分析。

Apache HTTP/2连接前言Preface到底有什么用?

一、Preface的协议定义与字节构成

HTTP/2规范中,客户端连接前言是一个固定的24字节字符串,内容是:PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n。其中\r\n表示回车换行符,即十六进制的0D 0A。这个序列看起来很像一个不完整的HTTP/1.1请求,但实际上它的作用完全不同。客户端发送完这24字节之后,必须紧跟一个SETTINGS帧,而服务器端则不需要发送同样的魔法字符串,只需要在收到客户端前言后发送自己的SETTINGS帧作为服务端连接前言。

从设计动机来看,Preface主要用于解决两个问题。第一,在明文TCP环境下,如果客户端使用h2c直接模式,服务器需要一种方式来快速区分HTTP/1.1请求与HTTP/2连接。第二,在TLS ALPN协商完成后,虽然理论上双方已经知道协议是h2,但Preface仍然保留了协议初始化时的一种显式握手信号,方便中间层和调试工具识别连接类型。Apache的mod_http2在接收数据时,会先读取前24个字节并与标准Preface进行逐字节比对,不一致则立即断开连接。

下面这段十六进制内容展示了完整的前言字节序列,最后两个字节0D 0A结束后,下一个字节就属于SETTINGS帧的帧头了。

50 52 49 20 2A 20 48 54 54 50 2F 32 2E 30 0D 0A 0D 0A 53 4D 0D 0A 0D 0A

如果把这个序列还原成ASCII文本,就是PRI * HTTP/2.0,后面跟着两个CRLF,然后是SM,最后再跟两个CRLF。很多抓包工具会把这段数据标记为HTTP/2 Magic,Wireshark中也可以直接使用过滤条件http2.preface来定位。

二、Apache mod_http2如何处理Preface

Apache从2.4.17版本开始引入mod_http2模块,实现了HTTP/2的服务端支持。mod_http2在处理新连接时,会根据连接建立方式的不同采取不同的Preface校验策略。如果连接通过TLS建立,并且ALPN协商结果为h2,Apache会等待客户端发送Preface。客户端在TLS握手结束后发送的第一段数据必须是那24字节的前言,随后是压缩后的SETTINGS帧。如果客户端先发送了普通HTTP/1.1请求,Apache会认为协议不匹配并关闭连接。

在明文TCP上,mod_http2支持两种协商方式。一种是HTTP/1.1升级机制,客户端先发送一个包含Upgrade: h2c头的普通HTTP/1.1请求,Apache返回101 Switching Protocols,之后客户端再发送Preface。另一种是h2c先验知识模式,客户端跳过升级请求,直接发送Preface。Apache通过H2Direct指令控制是否接受直接h2c连接,通过H2Upgrade指令控制是否接受升级请求。默认情况下H2Direct是关闭的,H2Upgrade在非TLS端口上是开启的。

下面是一个典型的Apache虚拟主机配置,同时启用了TLS上的h2和明文升级能力。注意配置中的标签名在正文中已做转义处理,实际写配置时使用正常的尖括号。

<VirtualHost *:443>
    ServerName www.ipipp.com
    Protocols h2 http/1.1
    SSLEngine on
    SSLCertificateFile /etc/httpd/certs/server.crt
    SSLCertificateKeyFile /etc/httpd/certs/server.key
    H2Direct on
    H2Upgrade on
</VirtualHost>

Protocols h2 http/1.1表示该虚拟主机既支持HTTP/2也支持HTTP/1.1,具体使用哪个协议由ALPN或升级机制决定。如果只写Protocols h2,则非HTTP/2的连接会被直接拒绝。H2Direct on允许客户端在没有升级请求的情况下直接发送Preface,常用于内网服务或客户端明确知道自己要使用h2c的场景。开启这个选项后,Apache会在收到TCP连接数据的第一时间尝试按HTTP/2 Preface解析,如果对端发送的是HTTP/1.1请求行,解析会失败。

服务端连接前言同样不可忽略。客户端发送完自己的Preface和SETTINGS帧后,会等待服务端的SETTINGS帧。Apache在验证完客户端前言后,会立即发送一个服务端SETTINGS帧,其中包含默认的MAX_CONCURRENT_STREAMS、INITIAL_WINDOW_SIZE等参数。双方收到SETTINGS帧后,还需要回复SETTINGS确认帧ACK,连接才算真正进入可传输请求的状态。如果服务端SETTINGS帧延迟发送,客户端可能会超时,这一点在Apache反向代理场景下尤其明显。

三、从抓包看Preface与SETTINGS帧的交互

使用Wireshark抓包分析Apache与浏览器之间的HTTP/2连接,可以清晰地看到Preface之后紧跟着SETTINGS帧。以curl命令为例,当使用先验知识模式访问一个支持h2c的Apache服务时,可以在客户端观察到完整的发送序列。

下面的命令使用--http2-prior-knowledge参数,强制curl跳过升级请求,直接发送HTTP/2 Preface。这种方式适合测试配置了H2Direct on的Apache实例。

curl --http2-prior-knowledge -I http://192.168.1.100:8080/

抓包时可以看到客户端发送的第一个TCP数据段中包含24字节的Preface,随后是一个SETTINGS帧。SETTINGS帧的头是9字节,其中第一个字节表示帧长度,第二字节表示帧类型,第三字节表示标志位,后四个字节是流标识符。SETTINGS帧的流标识符必须为0,因为它是连接级控制帧。在Wireshark中展开SETTINGS帧,可以看到SETTINGS_MAX_CONCURRENT_STREAMS、SETTINGS_INITIAL_WINDOW_SIZE等参数的具体值。

Apache返回的数据中,不会出现PRI字符串,因为服务端不需要发送Preface。服务端的第一帧直接是SETTINGS帧,可能还会带上一个WINDOW_UPDATE帧用于扩大连接级流量控制窗口。很多刚接触HTTP/2的开发者在抓包时只找服务端的PRI字符串,结果发现没有,就误认为Apache没有正确实现HTTP/2。实际上这正是协议的设计,服务端通过发送SETTINGS帧来替代客户端Preface。

如果需要在Wireshark中快速过滤HTTP/2 Preface,可以使用以下表达式。它匹配TCP负载中出现的固定ASCII序列。

tcp.payload contains "PRI * HTTP/2.0"

这个过滤条件对于排查代理层是否完整透传Preface非常有用。某些老旧的七层负载均衡器或反向代理会尝试解析HTTP/1.1请求,当看到PRI * HTTP/2.0时可能将其当作非法请求丢弃,导致客户端只见连接被重置,却看不到任何HTTP错误码。通过抓包对比负载均衡器前后的流量,可以快速定位是哪一段设备截断了前言。

四、常见Preface相关问题与排查

Apache在遇到Preface校验失败时,错误日志中通常会出现类似Invalid HTTP/2 connection preface的记录。这个错误的直接含义是mod_http2从连接中读取的前24字节与标准Preface不一致。可能的原因有很多,比如客户端没有发送Preface而是直接发送HTTP/1.1请求,或者代理层将Preface转换成了HTTP/1.1格式,又或者TCP连接被中间设备注入了额外数据。

排查时首先要确认客户端使用的HTTP/2协商方式。如果客户端通过TLS连接并且ALPN协商成功,那么正常情况下浏览器都会自动发送Preface,不需要额外配置。如果客户端通过明文TCP连接,需要区分是升级模式还是直接模式。升级模式下客户端先发送HTTP/1.1请求头,Apache返回101后再发送Preface;直接模式下客户端从第一个字节开始就是Preface。如果客户端把这两种模式混用,比如在H2Direct off的情况下直接发送Preface,Apache会试图按HTTP/1.1请求行解析,结果就会报错。

另一个容易被忽略的问题是反向代理。假设Apache前面有一台Nginx或HAProxy,它们负责终结TLS连接,然后以明文方式把请求转发给后端Apache。这时如果代理层只配置了转发HTTP/1.1流量,即使客户端到代理之间使用了HTTP/2,代理到Apache之间的连接仍然是HTTP/1.1。Apache的mod_http2永远不会在后端连接上看到Preface,自然不会启用HTTP/2。要让后端Apache也使用HTTP/2,代理层必须支持http2转发,并且在与后端的连接上发送正确的Preface。

还可以通过nghttp工具手动发送Preface来测试Apache的行为。下面的命令模拟一个h2c直接连接,先发送标准Preface,再发送一个空的SETTINGS帧。观察Apache返回的服务端SETTINGS帧可以判断握手是否成功。

nghttp -nv http://192.168.1.100:8080/

如果Apache返回recv SETTINGS frame并且继续处理请求,说明Preface校验通过。如果返回连接被关闭或者错误日志中出现Invalid HTTP/2 connection preface,则需要检查客户端发出的数据是否完整。有些自研客户端在拼接Preface时遗漏了最后的\r\n,导致总长度只有22字节或23字节,Apache会在等待完整24字节时超时,然后关闭连接。这个问题在二进制协议调试中非常隐蔽,建议使用十六进制日志或抓包工具进行字节级确认。

最后需要强调,Preface虽然只有24字节,但它是HTTP/2连接的入口契约。Apache的mod_http2严格按照RFC 9113处理这部分数据,任何层级的设备都不应该修改或缓存这些字节。理解了Preface的结构和Apache的校验时机,再结合抓包与日志,大部分HTTP/2连接建立阶段的问题都能快速找到根因。

广告位 · 文章底部(后台-模块管理替换此区域)