use std::sync::Mutex;
use serde::{Deserialize, Serialize};
use crate::error::FetchError;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Proxy {
Url(String),
Config {
server: String,
#[serde(default)]
username: Option<String>,
#[serde(default)]
password: Option<String>,
},
}
impl Proxy {
fn key(&self) -> String {
match self {
Self::Url(url) => url.clone(),
Self::Config {
server, username, ..
} => {
let user = username.as_deref().unwrap_or("");
format!("{server}|{user}")
}
}
}
pub fn server(&self) -> &str {
match self {
Self::Url(url) => url.as_str(),
Self::Config { server, .. } => server.as_str(),
}
}
}
impl std::fmt::Display for Proxy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.server())
}
}
pub type RotationStrategy = fn(&[Proxy], usize) -> usize;
pub fn cyclic_rotation(_proxies: &[Proxy], current: usize) -> usize {
current + 1
}
const PROXY_ERROR_INDICATORS: &[&str] = &[
"net::err_proxy",
"net::err_tunnel",
"connection refused",
"connection reset",
"connection timed out",
"failed to connect",
"could not resolve proxy",
];
pub fn is_proxy_error(error: &dyn std::error::Error) -> bool {
let msg = error.to_string().to_lowercase();
PROXY_ERROR_INDICATORS.iter().any(|ind| msg.contains(ind))
}
pub struct ProxyRotator {
proxies: Vec<Proxy>,
strategy: RotationStrategy,
current_index: Mutex<usize>,
}
impl ProxyRotator {
pub fn new(proxies: Vec<Proxy>) -> crate::error::Result<Self> {
Self::with_strategy(proxies, cyclic_rotation)
}
pub fn with_strategy(
proxies: Vec<Proxy>,
strategy: RotationStrategy,
) -> crate::error::Result<Self> {
if proxies.is_empty() {
return Err(FetchError::InvalidProxy(
"at least one proxy must be provided".into(),
));
}
let mut seen = std::collections::HashSet::new();
for p in &proxies {
let key = p.key();
if !seen.insert(key.clone()) {
return Err(FetchError::InvalidProxy(format!("duplicate proxy: {key}")));
}
}
Ok(Self {
proxies,
strategy,
current_index: Mutex::new(0),
})
}
pub fn get_proxy(&self) -> Proxy {
let mut idx = self.current_index.lock().unwrap();
let actual = *idx % self.proxies.len();
let proxy = self.proxies[actual].clone();
*idx = (self.strategy)(&self.proxies, actual);
proxy
}
pub fn proxies(&self) -> &[Proxy] {
&self.proxies
}
pub fn len(&self) -> usize {
self.proxies.len()
}
pub fn is_empty(&self) -> bool {
self.proxies.is_empty()
}
}
impl std::fmt::Debug for ProxyRotator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProxyRotator")
.field("count", &self.proxies.len())
.finish()
}
}