Apache HTTP Server从2.4.53版本开始提供实验性的HTTP/3支持,通过mod_http3模块协同ngtcp2和nghttp3库实现QUIC与HTTP/3传输。如果将Apache作为反向代理并启用磁盘缓存,可以显著降低源站压力,但直接从源码构建这些模块涉及多个依赖,传统autotools流程较为零散。使用CMake组织依赖与编译步骤,可让整套过程更可控、更适合自动化部署。

依赖库与模块间的关系
Apache启用HTTP/3需要两个关键协议库:ngtcp2负责QUIC传输层,nghttp3负责HTTP/3帧编码与流控制。mod_http3作为Apache模块连接这两者,并在Apache事件MPM中处理UDP套接字。如果还要承担反向代理和缓存,则需要mod_proxy、mod_proxy_http、mod_cache和mod_cache_disk。mod_proxy负责将请求转发到后端,mod_cache在代理层缓存响应,mod_cache_disk使用磁盘存储缓存数据。
从依赖角度看,ngtcp2和nghttp3可以使用系统包或源码编译。对于需要自定义TLS后端或希望静态链接的场景,建议从源码构建。Apache本身需要启用so模块和ssl模块,而mod_http3必须与mod_http2共存,因为HTTP/3的握手依赖TLS,且存在协议协商。
在Ubuntu或Debian环境下,可通过下面的命令安装编译依赖,其中libngtcp2-dev和libnghttp3-dev是HTTP/3支持的基础。
sudo apt update
sudo apt install -y build-essential cmake ninja-build \
libssl-dev libnghttp3-dev libngtcp2-dev \
libapr1-dev libaprutil1-dev libpcre2-dev
需要注意的是,发行版仓库中的nghttp3/ngtcp2可能版本较旧,若要匹配Apache所要求的最低版本,建议从GitHub获取官方源码后使用CMake单独构建,并把安装前缀统一到/opt/quic目录,方便后续Apache配置时查找。
用CMake包装Apache构建流程
Apache官方构建依赖autoconf、automake和libtool,但这不影响用CMake作为外层调度。核心思路是使用ExternalProject_Add定义ngtcp2、nghttp3和httpd三个外部项目,并通过依赖顺序控制构建。CMake负责设置环境变量、传递编译选项、输出统一安装目录。
下面这个CMakeLists片段展示了如何把Apache源码与QUIC依赖纳入同一次构建。假设所有源码已放置在项目根目录的third_party下。
cmake_minimum_required(VERSION 3.20)
project(apache_h3_proxy LANGUAGES C)
include(ExternalProject)
set(INSTALL_PREFIX ${CMAKE_BINARY_DIR}/dist)
ExternalProject_Add(ngtcp2
SOURCE_DIR ${CMAKE_SOURCE_DIR}/third_party/ngtcp2
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX}
-DENABLE_SHARED_LIB=OFF -DENABLE_OPENSSL=ON
BUILD_COMMAND cmake --build . --config Release
INSTALL_COMMAND cmake --install . --config Release
)
ExternalProject_Add(nghttp3
SOURCE_DIR ${CMAKE_SOURCE_DIR}/third_party/nghttp3
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${INSTALL_PREFIX}
-DENABLE_LIB_ONLY=ON -DENABLE_SHARED_LIB=OFF
BUILD_COMMAND cmake --build . --config Release
INSTALL_COMMAND cmake --install . --config Release
)
ExternalProject_Add(httpd
SOURCE_DIR ${CMAKE_SOURCE_DIR}/third_party/httpd
CONFIGURE_COMMAND ./configure --prefix=${INSTALL_PREFIX}
--with-nghttp3=${INSTALL_PREFIX}
--with-ngtcp2=${INSTALL_PREFIX}
--enable-http3 --enable-proxy --enable-cache
--enable-cache-disk --enable-ssl --enable-so
BUILD_COMMAND make -j8
INSTALL_COMMAND make install
DEPENDS ngtcp2 nghttp3
)
这里的configure命令显式启用了http3、proxy、cache等模块。实际编译时,如果某些模块依赖apr和apr-util的头文件路径,还需要通过CFLAGS和LDFLAGS环境变量把它们加入搜索路径。另一个常见问题是ngtcp2的OpenSSL支持使用了BoringSSL时,Apache需要指定对应路径,否则链接阶段会报未定义符号。
如果不希望把Apache也放进ExternalProject,可以只构建依赖库,然后手动进入httpd目录执行configure和make。但为了CI自动化,将所有步骤统一在CMake中更省心。构建完成后,可以使用cmake --build . --target httpd单独重建Apache,或执行ctest运行简单模块检查。
配置Apache反向代理与缓存策略
编译安装完成后,配置文件的修改集中在httpd.conf和extra/httpd-ssl.conf。先加载必需模块,再设置监听与协议。HTTP/3要求TLS,因此需要配置证书,并且监听指令要显式声明quic。
LoadModule http3_module modules/mod_http3.so LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule cache_module modules/mod_cache.so LoadModule cache_disk_module modules/mod_cache_disk.so Listen 443 quic Protocols h2 http/1.1 H3 on H3MaxData 268435456
反向代理与磁盘缓存的典型配置如下。这里将根路径代理到后端应用服务器,同时对可缓存内容启用基于URI和响应头的缓存策略。
<VirtualHost *:443>
ServerName ipipp.com
Protocols h2 http/1.1
H3 on
SSLEngine on
SSLCertificateFile /etc/apache2/ssl/server.crt
SSLCertificateKeyFile /etc/apache2/ssl/server.key
ProxyPreserveHost On
ProxyPass / http://192.168.1.20:8080/
ProxyPassReverse / http://192.168.1.20:8080/
CacheRoot /var/cache/apache2/mod_cache_disk
CacheEnable disk /
CacheHeader on
CacheDefaultExpire 3600
CacheMaxExpire 86400
CacheIgnoreNoLastMod On
</VirtualHost>
这里CacheEnable disk /表示对根路径启用磁盘缓存。Apache会根据响应头中的Cache-Control、Expires和Last-Modified判断是否可以存储副本。如果后端返回Set-Cookie或Authorization头,默认不会缓存,这是符合HTTP语义的安全行为。可以通过CacheIgnoreHeaders微调,但要谨慎,避免错误缓存个性化内容。
HTTP/3协议走UDP,443端口必须同时允许UDP和TCP入站。防火墙规则不能只放行TCP 443,否则UDP包被丢弃后客户端会回退到TCP,看似正常但实际没有用上QUIC。可以使用下面的命令验证UDP监听是否正常。
sudo ss -ulnp | grep httpd
若看到httpd进程绑定在UDP 443,说明QUIC监听已生效。客户端访问时,浏览器开发者工具的Protocol列会显示h3。使用curl也可验证:
curl --http3-only -I https://ipipp.com/
如果curl未编译HTTP/3支持,需要安装支持HTTP/3的curl版本,或者改用h2load、ngtcp2客户端等工具。缓存命中可以通过响应头中的Age字段或X-Cache来判断。
常见构建问题与排查
编译Apache HTTP/3模块时最容易遇到的问题之一是ngtcp2与OpenSSL版本不匹配。如果OpenSSL低于1.1.1,QUIC相关API不可用,因此建议使用1.1.1及以上版本,或使用BoringSSL。另一个常见错误是Apache configure阶段找不到nghttp3,可以通过设置PKG_CONFIG_PATH指向安装目录下的lib/pkgconfig解决。
export PKG_CONFIG_PATH=/opt/quic/lib/pkgconfig:$PKG_CONFIG_PATH ./configure --with-nghttp3=/opt/quic --with-ngtcp2=/opt/quic ...
此外,mod_http3与mod_http2的版本需要匹配,Apache官方发布包中两者通常同步更新。如果使用Git开发版,确保都从同一代码快照编译,否则可能出现协议协商失败、连接直接断开但无错误日志的情况。排查时打开Apache的debug日志,将LogLevel设置为debug,并观察mod_http3是否输出QUIC事件。
在CMake构建中,如果ExternalProject没有正确传递编译标志,可能导致生成的可执行文件或模块找不到动态库。将安装前缀统一并设置LD_LIBRARY_PATH,可以在运行时避免大部分找不到.so的问题。静态链接虽能减少部署复杂度,但GPL与OpenSSL的许可证兼容性需自行评估。

