BREAKING CHANGE(ts-api,rustproxy): remove deprecated TypeScript protocol and utility exports while hardening QUIC, HTTP/3, WebSocket, and rate limiter cleanup paths
This commit is contained in:
@@ -43,6 +43,7 @@ impl H3ProxyService {
|
||||
_fallback_route: &RouteConfig,
|
||||
port: u16,
|
||||
real_client_addr: Option<SocketAddr>,
|
||||
parent_cancel: &CancellationToken,
|
||||
) -> anyhow::Result<()> {
|
||||
let remote_addr = real_client_addr.unwrap_or_else(|| connection.remote_address());
|
||||
debug!("HTTP/3 connection from {} on port {}", remote_addr, port);
|
||||
@@ -55,35 +56,44 @@ impl H3ProxyService {
|
||||
.map_err(|e| anyhow::anyhow!("H3 connection setup failed: {}", e))?;
|
||||
|
||||
loop {
|
||||
match h3_conn.accept().await {
|
||||
Ok(Some(resolver)) => {
|
||||
let (request, stream) = match resolver.resolve_request().await {
|
||||
Ok(pair) => pair,
|
||||
let resolver = tokio::select! {
|
||||
_ = parent_cancel.cancelled() => {
|
||||
debug!("HTTP/3 connection from {} cancelled by parent", remote_addr);
|
||||
break;
|
||||
}
|
||||
result = h3_conn.accept() => {
|
||||
match result {
|
||||
Ok(Some(resolver)) => resolver,
|
||||
Ok(None) => {
|
||||
debug!("HTTP/3 connection from {} closed", remote_addr);
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("HTTP/3 request resolve error: {}", e);
|
||||
continue;
|
||||
debug!("HTTP/3 accept error from {}: {}", remote_addr, e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let http_proxy = Arc::clone(&self.http_proxy);
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handle_h3_request(
|
||||
request, stream, port, remote_addr, &http_proxy,
|
||||
).await {
|
||||
debug!("HTTP/3 request error from {}: {}", remote_addr, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(None) => {
|
||||
debug!("HTTP/3 connection from {} closed", remote_addr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (request, stream) = match resolver.resolve_request().await {
|
||||
Ok(pair) => pair,
|
||||
Err(e) => {
|
||||
debug!("HTTP/3 accept error from {}: {}", remote_addr, e);
|
||||
break;
|
||||
debug!("HTTP/3 request resolve error: {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let http_proxy = Arc::clone(&self.http_proxy);
|
||||
let request_cancel = parent_cancel.child_token();
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = handle_h3_request(
|
||||
request, stream, port, remote_addr, &http_proxy, request_cancel,
|
||||
).await {
|
||||
debug!("HTTP/3 request error from {}: {}", remote_addr, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -103,13 +113,25 @@ async fn handle_h3_request(
|
||||
port: u16,
|
||||
peer_addr: SocketAddr,
|
||||
http_proxy: &HttpProxyService,
|
||||
cancel: CancellationToken,
|
||||
) -> anyhow::Result<()> {
|
||||
// Stream request body from H3 client via an mpsc channel.
|
||||
let (body_tx, body_rx) = tokio::sync::mpsc::channel::<Bytes>(4);
|
||||
|
||||
// Spawn the H3 body reader task
|
||||
// Spawn the H3 body reader task with cancellation
|
||||
let body_cancel = cancel.clone();
|
||||
let body_reader = tokio::spawn(async move {
|
||||
while let Ok(Some(mut chunk)) = stream.recv_data().await {
|
||||
loop {
|
||||
let chunk = tokio::select! {
|
||||
_ = body_cancel.cancelled() => break,
|
||||
result = stream.recv_data() => {
|
||||
match result {
|
||||
Ok(Some(chunk)) => chunk,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut chunk = chunk;
|
||||
let data = Bytes::copy_from_slice(chunk.chunk());
|
||||
chunk.advance(chunk.remaining());
|
||||
if body_tx.send(data).await.is_err() {
|
||||
@@ -128,7 +150,6 @@ async fn handle_h3_request(
|
||||
|
||||
// Delegate to HttpProxyService — same backend path as TCP/HTTP:
|
||||
// route matching, ALPN protocol detection, connection pool, H1/H2/H3 auto.
|
||||
let cancel = CancellationToken::new();
|
||||
let conn_activity = ConnActivity::new_standalone();
|
||||
let response = http_proxy.handle_request(req, peer_addr, port, cancel, conn_activity).await
|
||||
.map_err(|e| anyhow::anyhow!("Backend request failed: {}", e))?;
|
||||
|
||||
@@ -203,6 +203,10 @@ pub struct HttpProxyService {
|
||||
route_rate_limiters: Arc<DashMap<String, Arc<RateLimiter>>>,
|
||||
/// Request counter for periodic rate limiter cleanup.
|
||||
request_counter: AtomicU64,
|
||||
/// Epoch for time-based rate limiter cleanup.
|
||||
rate_limiter_epoch: std::time::Instant,
|
||||
/// Last rate limiter cleanup time (ms since epoch).
|
||||
last_rate_limiter_cleanup_ms: AtomicU64,
|
||||
/// Cache of compiled URL rewrite regexes (keyed by pattern string).
|
||||
regex_cache: DashMap<String, Regex>,
|
||||
/// Shared backend TLS config for session resumption across connections.
|
||||
@@ -233,6 +237,8 @@ impl HttpProxyService {
|
||||
connect_timeout: DEFAULT_CONNECT_TIMEOUT,
|
||||
route_rate_limiters: Arc::new(DashMap::new()),
|
||||
request_counter: AtomicU64::new(0),
|
||||
rate_limiter_epoch: std::time::Instant::now(),
|
||||
last_rate_limiter_cleanup_ms: AtomicU64::new(0),
|
||||
regex_cache: DashMap::new(),
|
||||
backend_tls_config: Self::default_backend_tls_config(),
|
||||
backend_tls_config_alpn: Self::default_backend_tls_config_with_alpn(),
|
||||
@@ -258,6 +264,8 @@ impl HttpProxyService {
|
||||
connect_timeout,
|
||||
route_rate_limiters: Arc::new(DashMap::new()),
|
||||
request_counter: AtomicU64::new(0),
|
||||
rate_limiter_epoch: std::time::Instant::now(),
|
||||
last_rate_limiter_cleanup_ms: AtomicU64::new(0),
|
||||
regex_cache: DashMap::new(),
|
||||
backend_tls_config: Self::default_backend_tls_config(),
|
||||
backend_tls_config_alpn: Self::default_backend_tls_config_with_alpn(),
|
||||
@@ -524,9 +532,13 @@ impl HttpProxyService {
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic rate limiter cleanup (every 1000 requests)
|
||||
// Periodic rate limiter cleanup (every 1000 requests or every 60s)
|
||||
let count = self.request_counter.fetch_add(1, Ordering::Relaxed);
|
||||
if count % 1000 == 0 {
|
||||
let now_ms = self.rate_limiter_epoch.elapsed().as_millis() as u64;
|
||||
let last_cleanup = self.last_rate_limiter_cleanup_ms.load(Ordering::Relaxed);
|
||||
let time_triggered = now_ms.saturating_sub(last_cleanup) >= 60_000;
|
||||
if count % 1000 == 0 || time_triggered {
|
||||
self.last_rate_limiter_cleanup_ms.store(now_ms, Ordering::Relaxed);
|
||||
for entry in self.route_rate_limiters.iter() {
|
||||
entry.value().cleanup();
|
||||
}
|
||||
@@ -2134,12 +2146,26 @@ impl HttpProxyService {
|
||||
let ws_max_lifetime = self.ws_max_lifetime;
|
||||
|
||||
tokio::spawn(async move {
|
||||
// RAII guard: ensures connection_ended is called even if this task panics
|
||||
struct WsUpstreamGuard {
|
||||
selector: UpstreamSelector,
|
||||
key: String,
|
||||
}
|
||||
impl Drop for WsUpstreamGuard {
|
||||
fn drop(&mut self) {
|
||||
self.selector.connection_ended(&self.key);
|
||||
}
|
||||
}
|
||||
let _upstream_guard = WsUpstreamGuard {
|
||||
selector: upstream_selector,
|
||||
key: upstream_key_owned.clone(),
|
||||
};
|
||||
|
||||
let client_upgraded = match on_client_upgrade.await {
|
||||
Ok(upgraded) => upgraded,
|
||||
Err(e) => {
|
||||
debug!("WebSocket: client upgrade failed: {}", e);
|
||||
upstream_selector.connection_ended(&upstream_key_owned);
|
||||
return;
|
||||
return; // _upstream_guard Drop handles connection_ended
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2298,9 +2324,7 @@ impl HttpProxyService {
|
||||
watchdog.abort();
|
||||
|
||||
debug!("WebSocket tunnel closed: {} bytes in, {} bytes out", bytes_in, bytes_out);
|
||||
|
||||
upstream_selector.connection_ended(&upstream_key_owned);
|
||||
// Bytes already reported per-chunk in the copy loops above
|
||||
// _upstream_guard Drop handles connection_ended on all paths including panic
|
||||
});
|
||||
|
||||
let body: BoxBody<Bytes, hyper::Error> = BoxBody::new(
|
||||
@@ -2822,6 +2846,8 @@ impl Default for HttpProxyService {
|
||||
connect_timeout: DEFAULT_CONNECT_TIMEOUT,
|
||||
route_rate_limiters: Arc::new(DashMap::new()),
|
||||
request_counter: AtomicU64::new(0),
|
||||
rate_limiter_epoch: std::time::Instant::now(),
|
||||
last_rate_limiter_cleanup_ms: AtomicU64::new(0),
|
||||
regex_cache: DashMap::new(),
|
||||
backend_tls_config: Self::default_backend_tls_config(),
|
||||
backend_tls_config_alpn: Self::default_backend_tls_config_with_alpn(),
|
||||
|
||||
Reference in New Issue
Block a user