use std::sync::Arc;
use std::time::{Duration, Instant};
use url::Url;
use crate::impersonate::{ImpersonateClient, Profile};
use crate::proxy::{ProxyOutcome, ProxyRouter};
const CANARY_URL: &str = "https://tls.peet.ws/api/clean";
const CANARY_TIMEOUT: Duration = Duration::from_secs(10);
pub fn spawn(router: Arc<ProxyRouter>, proxies: Vec<Url>, interval: Duration) {
if proxies.is_empty() || interval.is_zero() {
return;
}
tokio::spawn(async move {
let client = match ImpersonateClient::new(Profile::Chrome131Stable) {
Ok(c) => c,
Err(e) => {
tracing::warn!(?e, "proxy health: client init failed; aborting");
return;
}
};
let canary = match Url::parse(CANARY_URL) {
Ok(u) => u,
Err(_) => return,
};
let mut tick = tokio::time::interval(interval);
tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
tick.tick().await;
loop {
for proxy in &proxies {
let started = Instant::now();
let res = tokio::time::timeout(
CANARY_TIMEOUT,
client.get_via(
&canary,
Some(proxy),
crate::discovery::assets::SecFetchDest::Document,
),
)
.await;
let outcome = match res {
Ok(Ok(_)) => ProxyOutcome::Success {
latency_ms: started.elapsed().as_secs_f64() * 1_000.0,
},
Ok(Err(_)) => ProxyOutcome::ConnectFailed,
Err(_) => ProxyOutcome::Timeout,
};
router.record_outcome(proxy, outcome);
}
tick.tick().await;
}
});
}