1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//! Proxy representation and status.
use governor::{
clock::DefaultClock,
middleware::NoOpMiddleware,
state::{InMemoryState, NotKeyed},
Quota, RateLimiter,
};
use std::num::NonZeroU32;
use std::sync::Arc;
use std::time::{Duration, Instant};
/// Status of a proxy.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ProxyStatus {
/// The proxy has not been tested yet.
Unknown,
/// The proxy is healthy and can be used.
Healthy,
/// The proxy is unhealthy and should not be used.
Unhealthy,
/// The proxy is in half-open state and can be probed with real traffic.
HalfOpen,
}
/// Representation of a proxy server.
#[derive(Debug, Clone)]
pub struct Proxy {
/// The URL of the proxy (e.g. "socks5://127.0.0.1:1080").
pub url: String,
/// The current status of the proxy.
pub status: ProxyStatus,
/// Number of successful requests made through this proxy.
pub success_count: usize,
/// Number of failed requests made through this proxy.
pub failure_count: usize,
/// Time when this proxy was last checked.
pub last_check: Instant,
/// Average response time in seconds, if available.
pub response_time: Option<f64>,
/// When set, this proxy stays unavailable before cooldown expires.
pub cooldown_until: Option<Instant>,
/// Rate limiter to enforce minimum interval between requests.
pub limiter: Arc<RateLimiter<NotKeyed, InMemoryState, DefaultClock, NoOpMiddleware>>,
}
impl Proxy {
/// Create a new proxy with the given URL and rate limit.
pub fn new(url: String, min_request_interval_ms: u64) -> Self {
// Create a rate limiter for this proxy (1 request per interval).
let period = Duration::from_millis(min_request_interval_ms.max(1));
let quota = Quota::with_period(period)
.unwrap_or_else(|| Quota::per_second(NonZeroU32::new(1).unwrap()))
.allow_burst(NonZeroU32::new(1).unwrap());
let limiter = Arc::new(RateLimiter::direct(quota));
Self {
url,
status: ProxyStatus::Unknown,
success_count: 0,
failure_count: 0,
last_check: Instant::now(),
response_time: None,
cooldown_until: None,
limiter,
}
}
/// Convert the proxy URL to a reqwest::Proxy.
pub fn to_reqwest_proxy(&self) -> Result<reqwest::Proxy, reqwest::Error> {
reqwest::Proxy::all(&self.url)
}
/// Calculate the success rate of this proxy.
pub fn success_rate(&self) -> f64 {
let total = self.success_count + self.failure_count;
if total == 0 {
return 0.0;
}
self.success_count as f64 / total as f64
}
}