reqwest-proxy-pool 0.4.0

proxy pool middleware for reqwest
Documentation
//! 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
    }
}