diff --git a/changelog.md b/changelog.md index ba2d485..7031b89 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-03-17 - 25.11.20 - fix(rustproxy-http) +avoid downgrading cached backend protocol on H2 stream errors + +- Treat HTTP/2 stream-level failures as retryable request errors instead of evidence that the backend only supports HTTP/1.1 +- Keep protocol cache entries unchanged after successful H2 handshakes so future requests continue using HTTP/2 +- Lower log severity for this fallback path from warning to debug while still recording backend H2 failure metrics + ## 2026-03-16 - 25.11.19 - fix(rustproxy-http) avoid reusing pooled HTTP/2 connections for requests with bodies to prevent upload flow-control stalls diff --git a/rust/crates/rustproxy-http/src/proxy_service.rs b/rust/crates/rustproxy-http/src/proxy_service.rs index 74e710e..fec26dd 100644 --- a/rust/crates/rustproxy-http/src/proxy_service.rs +++ b/rust/crates/rustproxy-http/src/proxy_service.rs @@ -1388,23 +1388,19 @@ impl HttpProxyService { self.build_streaming_response(upstream_response, route, route_id, source_ip, conn_activity).await } Err(e) => { - // H2 request failed — backend advertises h2 via ALPN but doesn't - // actually speak it. Update cache so future requests use H1. + // H2 request failed on a stream level (e.g. RST_STREAM PROTOCOL_ERROR). + // The H2 handshake succeeded, so the backend genuinely speaks H2 — don't + // poison the protocol cache. Only handshake-level failures (below) should + // downgrade the cache to H1. let bk = format!("{}:{}", upstream.host, upstream.port); - warn!( + debug!( backend = %bk, domain = %domain, error = %e, error_debug = ?e, - "Auto-detect: H2 request failed, falling back to H1" + "H2 stream error, retrying this request as H1" ); self.metrics.backend_h2_failure(&bk); - let cache_key = crate::protocol_cache::ProtocolCacheKey { - host: upstream.host.clone(), - port: upstream.port, - requested_host: requested_host.clone(), - }; - self.protocol_cache.insert(cache_key, crate::protocol_cache::DetectedProtocol::H1); // Retry as H1 for bodyless requests; return 502 for requests with bodies if let Some((method, headers)) = retry_state { diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 2b90c85..54212f7 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '25.11.19', + version: '25.11.20', description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' }