Skip to main content

reqwest_proxy_pool/
proxy.rs

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