fix(http-proxy): avoid repeated HTTP/3 recaching after QUIC fallback and document backend protocol selection

This commit is contained in:
2026-03-19 20:57:48 +00:00
parent 15d0a721d5
commit 37c7233780
4 changed files with 81 additions and 10 deletions

View File

@@ -696,13 +696,17 @@ impl HttpProxyService {
};
// Derive legacy flags for the existing H1/H2 connection path
let (use_h2, needs_alpn_probe) = match &protocol_decision {
let (use_h2, mut needs_alpn_probe) = match &protocol_decision {
ProtocolDecision::H1 => (false, false),
ProtocolDecision::H2 => (true, false),
ProtocolDecision::H3 { .. } => (false, false), // H3 path handled separately below
ProtocolDecision::AlpnProbe => (false, true),
};
// Track whether H3 connect failed — suppresses Alt-Svc re-caching to prevent
// the loop: H3 cached → QUIC timeout → H2/H1 fallback → Alt-Svc re-caches H3 → repeat
let mut h3_connect_failed = false;
// --- H3 path: try QUIC connection before TCP ---
if let ProtocolDecision::H3 { port: h3_port } = protocol_decision {
let h3_pool_key = crate::connection_pool::PoolKey {
@@ -738,14 +742,13 @@ impl HttpProxyService {
Err(e) => {
warn!(backend = %upstream_key, error = %e,
"H3 backend connect failed, falling back to H2/H1");
// Invalidate H3 from cache — next request will ALPN probe for H2/H1
if is_auto_detect_mode {
self.protocol_cache.insert(
protocol_cache_key.clone(),
crate::protocol_cache::DetectedProtocol::H1,
);
h3_connect_failed = true;
// Force ALPN probe on TCP fallback so we correctly detect H2 vs H1
// (don't cache anything yet — let the ALPN probe decide)
if is_auto_detect_mode && upstream.use_tls {
needs_alpn_probe = true;
}
// Fall through to TCP path (ALPN probe for auto, or H1 for explicit)
// Fall through to TCP path
}
}
}
@@ -946,7 +949,11 @@ impl HttpProxyService {
self.metrics.backend_connection_closed(&upstream_key);
// --- Alt-Svc discovery: check if backend advertises H3 ---
if is_auto_detect_mode {
// Suppress Alt-Svc caching when we just failed an H3 attempt to prevent the loop:
// H3 cached → QUIC timeout → fallback → Alt-Svc re-caches H3 → repeat.
// The ALPN probe already cached H1 or H2; it will expire after 5min TTL,
// at which point we'll re-probe and see Alt-Svc again, retrying QUIC then.
if is_auto_detect_mode && !h3_connect_failed {
if let Ok(ref resp) = result {
if let Some(alt_svc) = resp.headers().get("alt-svc").and_then(|v| v.to_str().ok()) {
if let Some(h3_port) = parse_alt_svc_h3_port(alt_svc) {