Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 61db285e04 | |||
| d165829022 | |||
| 5e6cf391ab | |||
| 2b1a21c599 | |||
| b8e1c9f3cf | |||
| c65369540c | |||
| 59e108edbd | |||
| 1e2ca68fc7 |
24
changelog.md
24
changelog.md
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-16 - 25.11.14 - fix(rustproxy-http)
|
||||
forward vectored write support in ShutdownOnDrop AsyncWrite wrapper
|
||||
|
||||
- Implements poll_write_vectored by delegating to the wrapped writer
|
||||
- Exposes is_write_vectored so the wrapper preserves underlying AsyncWrite capabilities
|
||||
|
||||
## 2026-03-16 - 25.11.13 - fix(rustproxy-http)
|
||||
remove hot-path debug logging from HTTP/1 connection pool hits
|
||||
|
||||
- Stops emitting debug logs when reusing HTTP/1 idle connections in the connection pool.
|
||||
- Keeps pool hit behavior unchanged while reducing overhead on a frequently executed path.
|
||||
|
||||
## 2026-03-16 - 25.11.12 - fix(rustproxy-http)
|
||||
remove connection pool hit logging and keep logging limited to actual failures
|
||||
|
||||
- Removes debug and warning logs for HTTP/2 connection pool hits and age checks.
|
||||
- Keeps pool behavior unchanged while reducing noisy per-request logging in the Rust HTTP proxy layer.
|
||||
|
||||
## 2026-03-16 - 25.11.11 - fix(rustproxy-http)
|
||||
improve HTTP/2 proxy error logging with warning-level connection failures and debug error details
|
||||
|
||||
- Adds debug-formatted error fields to HTTP/2 handshake, retry, fallback, and request failure logs
|
||||
- Promotes upstream HTTP/2 connection error logs from debug to warn to improve operational visibility
|
||||
|
||||
## 2026-03-16 - 25.11.10 - fix(rustproxy-http)
|
||||
validate pooled HTTP/2 connections asynchronously before reuse and evict stale senders
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartproxy",
|
||||
"version": "25.11.10",
|
||||
"version": "25.11.14",
|
||||
"private": false,
|
||||
"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.",
|
||||
"main": "dist_ts/index.js",
|
||||
|
||||
@@ -10,7 +10,7 @@ use bytes::Bytes;
|
||||
use dashmap::DashMap;
|
||||
use http_body_util::combinators::BoxBody;
|
||||
use hyper::client::conn::{http1, http2};
|
||||
use tracing::{debug, warn};
|
||||
// No per-request logging in the pool — only log on actual failures (in proxy_service.rs)
|
||||
|
||||
/// Maximum idle connections per backend key.
|
||||
const MAX_IDLE_PER_KEY: usize = 16;
|
||||
@@ -82,7 +82,7 @@ impl ConnectionPool {
|
||||
while let Some(idle) = idles.pop() {
|
||||
// Check if the connection is still alive and ready
|
||||
if idle.idle_since.elapsed() < IDLE_TIMEOUT && idle.sender.is_ready() && !idle.sender.is_closed() {
|
||||
debug!("Pool hit (h1): {}:{}", key.host, key.port);
|
||||
// H1 pool hit — no logging on hot path
|
||||
return Some(idle.sender);
|
||||
}
|
||||
// Stale or closed — drop it
|
||||
@@ -120,21 +120,13 @@ impl ConnectionPool {
|
||||
let pooled = entry.value();
|
||||
let age = pooled.created_at.elapsed();
|
||||
|
||||
// Check if the h2 connection is still alive and not too old
|
||||
if pooled.sender.is_closed() || age >= MAX_H2_AGE {
|
||||
let reason = if pooled.sender.is_closed() { "closed" } else { "max_age" };
|
||||
debug!("Pool evict (h2): {}:{} (reason={}, age={:.1}s)", key.host, key.port, reason, age.as_secs_f64());
|
||||
drop(entry);
|
||||
self.h2_pool.remove(key);
|
||||
return None;
|
||||
}
|
||||
|
||||
if pooled.sender.is_ready() {
|
||||
if age > Duration::from_secs(30) {
|
||||
warn!("Pool hit (h2): {}:{} — connection age {:.1}s (>30s, may be stale)", key.host, key.port, age.as_secs_f64());
|
||||
} else {
|
||||
debug!("Pool hit (h2): {}:{} (age={:.1}s)", key.host, key.port, age.as_secs_f64());
|
||||
}
|
||||
return Some((pooled.sender.clone(), age));
|
||||
}
|
||||
None
|
||||
|
||||
@@ -990,7 +990,7 @@ impl HttpProxyService {
|
||||
) = match tokio::time::timeout(self.connect_timeout, h2_builder.handshake(io)).await {
|
||||
Ok(Ok(h)) => h,
|
||||
Ok(Err(e)) => {
|
||||
error!(backend = %backend_key, domain = %domain, error = %e, "Backend H2 handshake failed");
|
||||
error!(backend = %backend_key, domain = %domain, error = %e, error_debug = ?e, "Backend H2 handshake failed");
|
||||
self.metrics.backend_handshake_error(&backend_key);
|
||||
return Ok(error_response(StatusCode::BAD_GATEWAY, "Backend H2 handshake failed"));
|
||||
}
|
||||
@@ -1008,7 +1008,7 @@ impl HttpProxyService {
|
||||
let key = pool_key.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = conn.await {
|
||||
debug!("HTTP/2 upstream connection error: {}", e);
|
||||
warn!("HTTP/2 upstream connection error: {} ({:?})", e, e);
|
||||
}
|
||||
pool.remove_h2(&key);
|
||||
});
|
||||
@@ -1140,7 +1140,7 @@ impl HttpProxyService {
|
||||
) = match tokio::time::timeout(self.connect_timeout, h2_builder.handshake(io)).await {
|
||||
Ok(Ok(h)) => h,
|
||||
Ok(Err(e)) => {
|
||||
error!(backend = %backend_key, domain = %domain, error = %e, "H2 retry: handshake failed");
|
||||
error!(backend = %backend_key, domain = %domain, error = %e, error_debug = ?e, "H2 retry: handshake failed");
|
||||
self.metrics.backend_handshake_error(&backend_key);
|
||||
self.metrics.backend_connection_closed(&backend_key);
|
||||
return Ok(error_response(StatusCode::BAD_GATEWAY, "Backend H2 retry handshake failed"));
|
||||
@@ -1159,7 +1159,7 @@ impl HttpProxyService {
|
||||
let key = pool_key.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = conn.await {
|
||||
debug!("H2 retry: upstream connection error: {}", e);
|
||||
warn!("H2 retry: upstream connection error: {} ({:?})", e, e);
|
||||
}
|
||||
pool.remove_h2(&key);
|
||||
});
|
||||
@@ -1288,7 +1288,7 @@ impl HttpProxyService {
|
||||
let key = pool_key.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = conn.await {
|
||||
debug!("HTTP/2 upstream connection error: {}", e);
|
||||
warn!("HTTP/2 upstream connection error: {} ({:?})", e, e);
|
||||
}
|
||||
pool.remove_h2(&key);
|
||||
});
|
||||
@@ -1343,6 +1343,7 @@ impl HttpProxyService {
|
||||
backend = %bk,
|
||||
domain = %domain,
|
||||
error = %e,
|
||||
error_debug = ?e,
|
||||
"Auto-detect: H2 request failed, falling back to H1"
|
||||
);
|
||||
self.metrics.backend_h2_failure(&bk);
|
||||
@@ -1600,11 +1601,11 @@ impl HttpProxyService {
|
||||
// Evict the dead sender so subsequent requests get fresh connections
|
||||
if let Some(key) = pool_key {
|
||||
let bk = format!("{}:{}", key.host, key.port);
|
||||
error!(backend = %bk, domain = %domain, error = %e, "Backend H2 request failed");
|
||||
error!(backend = %bk, domain = %domain, error = %e, error_debug = ?e, "Backend H2 request failed");
|
||||
self.metrics.backend_request_error(&bk);
|
||||
self.connection_pool.remove_h2(key);
|
||||
} else {
|
||||
error!(domain = %domain, error = %e, "Backend H2 request failed");
|
||||
error!(domain = %domain, error = %e, error_debug = ?e, "Backend H2 request failed");
|
||||
}
|
||||
return Ok(error_response(StatusCode::BAD_GATEWAY, "Backend H2 request failed"));
|
||||
}
|
||||
|
||||
@@ -50,6 +50,18 @@ impl<S: AsyncRead + AsyncWrite + Unpin + Send + 'static> AsyncWrite for Shutdown
|
||||
Pin::new(self.get_mut().inner.as_mut().unwrap()).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
bufs: &[io::IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
Pin::new(self.get_mut().inner.as_mut().unwrap()).poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
self.inner.as_ref().unwrap().is_write_vectored()
|
||||
}
|
||||
|
||||
fn poll_flush(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartproxy',
|
||||
version: '25.11.10',
|
||||
version: '25.11.14',
|
||||
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.'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user