在将Gravit服务暴露到公网时,直接使用原生QUIC可以显著降低首字节时间与连接迁移成本。但不少团队希望复用现有Apache作为统一入口,这就带来了Apache如何终结HTTP/3并同时提供代理缓存的问题。Gravit自身基于QUIC实现私有传输逻辑,若Apache仅做TCP反向代理,QUIC包会被当作普通UDP流转发,缓存完全失效。正确做法是由Apache通过mod_http3与mod_proxy_http3完成HTTP/3协议解析,再对后端Gravit发起HTTP/3或HTTP/2代理请求,使缓存命中发生在应用层而非传输层。

Apache终结HTTP/3与QUIC的核心模块
Apache从2.4.48之后实验性引入HTTP/3支持,依赖mod_http3、mod_proxy_http3以及底层的ngtcp2与quictls。与传统mod_proxy_http2不同,mod_proxy_http3要求前端监听UDP 443,并在TLS握手阶段通过ALPN通告h3。当客户端使用HTTP/3连接Gravit前置域名时,Apache先完成QUIC连接迁移与拥塞控制,再将解密后的HTTP语义交给代理缓存处理器。此时Gravit后端即便只支持HTTP/2,也可由Apache做协议转换,对外统一呈现QUIC能力。
配置上需要明确指定协议与证书。以下片段展示最小可用监听设置,注意QuicTLS证书与普通TLS共用文件,但需开启Protocols h3 http/1.1以允许回退。若遗漏Listen 443 udp,Apache不会绑定UDP端口,客户端h3握手直接失败。许多部署错误源于只在TCP 443开启h3,而UDP未放行,导致浏览器静默降级。
Listen 443 udp
Listen 443 tcp
<VirtualHost *:443>
Protocols h3 http/1.1
SSLEngine on
SSLCertificateFile /etc/apache2/tls/fullchain.pem
SSLCertificateKeyFile /etc/apache2/tls/privkey.pem
QUICEnable on
Http3 on
</VirtualHost>
需要强调的是,mod_proxy_http3当前并不支持将所有后端响应无脑缓存。它要求后端返回明确的Cache-Control头,且Apache的CacheQuickHandler在h3场景下默认开启会带来竞态:QUIC流可能未完全接收就被缓存处理器截断。因此在Gravit前置代理中,建议显式关闭该指令,改用标准缓存过滤器链,虽然损失少量延迟,但保证缓存内容完整。
Gravit后端代理缓存键的设计要点
HTTP/3使用一组伪头字段如:authority、:path、:method替代HTTP/2的Host与请求行。Apache默认缓存键在h3下仅拼接IP与端口,会造成不同域名的Gravit租户互相命中错误缓存。必须通过CacheKeyBaseURL与CacheKeyIgnoreQueryString配合重写,将:authority纳入键计算。例如多租户Gravit实例通过同一IP暴露,仅靠路径区分,若忽略authority,用户A的资产配置可能被用户B读取。
下面配置演示如何强制缓存键包含权威域名,并排除无意义查询参数。实践中Gravit的QUIC接口常带?t=时间戳防重放,若纳入键会导致命中率为零。使用CacheKeyIgnoreQueryString可忽略全部参数,也可借助CacheKeyQueryString白名单只保留room与uid。对于私有数据接口,应结合CacheDenyFilter禁止带Authorization头的响应进入共享缓存,防止越权。
<Location /gravit/>
ProxyPass https://127.0.0.1:8443/gravit/ h3
ProxyPassReverse https://127.0.0.1:8443/gravit/
CacheEnable disk
CacheRoot /var/cache/apache/gravit
CacheKeyBaseURL https://gravit.ippipp.com
CacheKeyIgnoreQueryString on
CacheQuickHandler off
ProxyCacheForceCompletion 5
</Location>
另一个易错点是QUIC连接复用导致的请求交织。单个QUIC连接可承载多流,Apache在处理代理缓存时若按连接维度加锁,会串行化Gravit的并发拉取。较新版本通过Http3StreamPerRequest指令确保每个缓存查找绑定独立流状态,避免一个慢响应阻塞其他流的缓存写入。部署后可用apachectl -M确认模块加载顺序,mod_proxy_http3须晚于mod_cache加载,否则缓存钩子无法注入h3处理阶段。
弱网环境下QUIC缓存加速效果调优
Gravit常用于跨地区协同,客户端可能处于丢包率3%的移动互联网。此时HTTP/3的0-RTT与连接迁移优势明显,但代理缓存若频繁因字节未齐就淘汰,反而增加回源。应通过ProxyCacheForceCompletion设定最小完成百分比,例如上文的5代表后端断流前至少缓存5%即保留碎片,供后续Range合并。配合Gravit后端的Accept-Ranges支持,可大幅减少重复拉取大体积场景资产。
监控方面,可开启Apache的mod_status扩展,观察cache.hit与h3.conn计数。若发现h3握手成功但缓存命中低,多半是伪头键缺失或CacheControl头被Gravit错误设为no-store。此时可在代理层用Header edit强制覆写,但需评估安全性。如下示例在代理侧将特定静态前缀的no-store改为最大年龄一小时,仅用于公开贴图,不涉及私有状态。
<Location /gravit/static/>
Header edit Cache-Control "no-store" "max-age=3600"
ProxyPass https://127.0.0.1:8443/gravit/static/ h3
</Location>
最后,Apache的QUIC实现仍处演进中,升级时应保留TCP回退入口。一旦后端Gravit发布兼容原生QUIC的更新,可逐步将ProxyPass后端协议从h3改为直接h3到Gravit,绕过Apache应用层缓存,改为边缘CDN缓存。这种渐进路径能令团队在不变更客户端的情况下,验证Gravit QUIC与Apache代理缓存的协同瓶颈,最终获得稳定的弱网加速收益。