Rotating connector (connector)
RotatingProxyConnector is a drop-in [tower_service::Service<Uri>] that routes
every outbound connection through a rotating pooled proxy — so a Rust program
gets pooled, self-healing proxy rotation with no local server and no listening
port. It is gated behind the connector feature (internally “E1”).
Per connection it checks out a healthy proxy from a Pool,
negotiates the tunnel with the shared negotiator, retries a different proxy on
failure (dead ones self-eject via the pool’s existing health thresholds), and
hands hyper the negotiated byte stream.
RotateConfig
#![allow(unused)]
fn main() {
#[derive(Debug, Clone)]
pub struct RotateConfig {
/// Proxies to try (each a different checkout) before returning an error.
pub max_tries: usize,
/// Per-connection negotiation timeout.
pub timeout: Duration,
}
}
RotateConfig::default() is max_tries: 3, timeout: 8s.
Plugging into hyper-util
reqwest 0.13 exposes no custom-connector hook, so the intended drop-in target is
hyper_util::client::legacy::Client, not reqwest::Client. You build the
connector from an already-fed pool with from_pool, then hand it to the client
builder:
#![allow(unused)]
fn main() {
use std::sync::Arc;
use std::time::Duration;
use proxybroker::connector::{RotatingProxyConnector, RotateConfig};
use proxybroker::resolver::Resolver;
use proxybroker::server::{Pool, PoolConfig};
use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor;
// The pool must already be populated — via Pool::spawn(find_stream, ..) or
// Pool::from_proxies(..). The connector wraps an existing pool + resolver.
let resolver = Arc::new(Resolver::new(Duration::from_secs(8))?);
let connector = RotatingProxyConnector::from_pool(
pool, // Arc<Pool>
resolver,
RotateConfig::default(),
);
let client: Client<_, http_body_util::Empty<bytes::Bytes>> =
Client::builder(TokioExecutor::new()).build(connector);
// Every request this client makes now dials through a rotating pooled proxy.
}
from_pool is the honest seam: the pool must already be populated (there is no
hidden find inside the connector). The service is always ready — checkout
happens per call, in Service::call, which runs the retry loop over up to
max_tries proxies. Each failed dial records the error so the pool benches or
ejects the proxy through its normal thresholds; the response type is ProxyConn,
a bare negotiated tunnel wrapped for hyper.
Scope (v1): tunnel-only
The connector gives hyper a transparent byte stream to the target; hyper then
speaks origin-form HTTP over it. That is correct for CONNECT/SOCKS tunnels, where
the stream really reaches the target. Concretely, tunnel_proto prefers
SOCKS5 → SOCKS4 → CONNECT:80, and only falls back to plain-HTTP passthrough when
the target scheme is http.
A plain forward-HTTP proxy (which needs absolute-form requests) is not the intended fit — prefer CONNECT/SOCKS proxies. An HTTPS-only proxy for a given connection is skipped and another is tried.
Deferred: TLS-to-target
For an https:// URL the connector returns the tunnel and the caller layers
its own end-to-end TLS (e.g. a hyper-rustls HttpsConnector wrapping the
connector). Terminate-and-verify TLS to the target is a later feature with its
own consumer. See the deferred backlog.
The Broker::rotating() convenience constructor that was deferred here has now
shipped — see Broker::rotating for the one-call
find → pool → connector pipeline.
Security note
The checker uses a liveness-only AcceptAllVerifier when it probes a proxy’s
TLS — that is fine for deciding whether a proxy is alive, but it accepts any
certificate. The connector deliberately never reuses it for real client
traffic: doing so would be a silent MITM hole. The server’s protocol picker can
return Proto::Https (which upgrades TLS to the target with the accept-all
verifier) and the SMTP-specific Connect25; both are excluded from the
connector’s tunnel_proto, so it only ever hands hyper a plain, un-terminated
byte stream. The caller’s own TLS stack — not the checker’s — validates the
target certificate.
Feature dependencies
The connector feature pulls in server (for Pool),
tower-service, and hyper-util/client-legacy. See
feature flags.