Skip to main content

aster/network/
proxy.rs

1//! 代理配置和支持
2//!
3//! 支持 HTTP/HTTPS/SOCKS 代理
4
5use serde::{Deserialize, Serialize};
6use std::env;
7use url::Url;
8
9/// 代理配置
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct ProxyConfig {
12    /// HTTP 代理 URL
13    #[serde(default)]
14    pub http: Option<String>,
15    /// HTTPS 代理 URL
16    #[serde(default)]
17    pub https: Option<String>,
18    /// SOCKS 代理 URL
19    #[serde(default)]
20    pub socks: Option<String>,
21    /// 绕过代理的域名列表
22    #[serde(default)]
23    pub no_proxy: Vec<String>,
24    /// 代理认证用户名
25    #[serde(default)]
26    pub username: Option<String>,
27    /// 代理认证密码
28    #[serde(default)]
29    pub password: Option<String>,
30    /// 是否使用系统代理设置
31    #[serde(default = "default_use_system_proxy")]
32    pub use_system_proxy: bool,
33}
34
35fn default_use_system_proxy() -> bool {
36    true
37}
38
39/// 代理 Agent 选项
40#[derive(Debug, Clone, Default, Serialize, Deserialize)]
41pub struct ProxyAgentOptions {
42    /// 连接超时(毫秒)
43    #[serde(default)]
44    pub timeout: Option<u64>,
45    /// 保持连接
46    #[serde(default = "default_keep_alive")]
47    pub keep_alive: bool,
48    /// 最大 socket 数量
49    #[serde(default)]
50    pub max_sockets: Option<usize>,
51    /// 最大空闲 socket 数量
52    #[serde(default)]
53    pub max_free_sockets: Option<usize>,
54    /// SSL/TLS 验证
55    #[serde(default = "default_reject_unauthorized")]
56    pub reject_unauthorized: bool,
57}
58
59fn default_keep_alive() -> bool {
60    true
61}
62
63fn default_reject_unauthorized() -> bool {
64    true
65}
66
67/// 解析后的代理 URL
68#[derive(Debug, Clone)]
69pub struct ParsedProxyUrl {
70    /// 代理 URL(不含认证信息)
71    pub url: String,
72    /// 用户名
73    pub username: Option<String>,
74    /// 密码
75    pub password: Option<String>,
76}
77
78/// 代理信息
79#[derive(Debug, Clone)]
80pub struct ProxyInfo {
81    /// 是否启用代理
82    pub enabled: bool,
83    /// 代理 URL
84    pub proxy_url: Option<String>,
85    /// 是否被绕过
86    pub bypassed: bool,
87}
88
89/// 从环境变量读取代理配置
90pub fn get_proxy_from_env() -> ProxyConfig {
91    let no_proxy = env::var("NO_PROXY")
92        .or_else(|_| env::var("no_proxy"))
93        .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
94        .unwrap_or_default();
95
96    ProxyConfig {
97        http: env::var("HTTP_PROXY")
98            .or_else(|_| env::var("http_proxy"))
99            .ok(),
100        https: env::var("HTTPS_PROXY")
101            .or_else(|_| env::var("https_proxy"))
102            .ok(),
103        socks: env::var("ALL_PROXY")
104            .or_else(|_| env::var("all_proxy"))
105            .or_else(|_| env::var("SOCKS_PROXY"))
106            .or_else(|_| env::var("socks_proxy"))
107            .ok(),
108        no_proxy,
109        username: None,
110        password: None,
111        use_system_proxy: true,
112    }
113}
114
115/// 解析代理 URL,提取认证信息
116pub fn parse_proxy_url(proxy_url: &str) -> ParsedProxyUrl {
117    match Url::parse(proxy_url) {
118        Ok(mut url) => {
119            let username = if url.username().is_empty() {
120                None
121            } else {
122                Some(url.username().to_string())
123            };
124            let password = url.password().map(|s| s.to_string());
125
126            // 移除认证信息
127            let _ = url.set_username("");
128            let _ = url.set_password(None);
129
130            ParsedProxyUrl {
131                url: url.to_string(),
132                username,
133                password,
134            }
135        }
136        Err(_) => ParsedProxyUrl {
137            url: proxy_url.to_string(),
138            username: None,
139            password: None,
140        },
141    }
142}
143
144/// 检查 URL 是否应该绕过代理
145pub fn should_bypass_proxy(target_url: &str, no_proxy: &[String]) -> bool {
146    if no_proxy.is_empty() {
147        return false;
148    }
149
150    let hostname = match Url::parse(target_url) {
151        Ok(url) => match url.host_str() {
152            Some(h) => h.to_string(),
153            None => return false,
154        },
155        Err(_) => return false,
156    };
157
158    for pattern in no_proxy {
159        if pattern.is_empty() {
160            continue;
161        }
162
163        // 特殊值 "*" 表示绕过所有
164        if pattern == "*" {
165            return true;
166        }
167
168        // 完全匹配
169        if hostname == *pattern {
170            return true;
171        }
172
173        // 通配符匹配 (*.example.com)
174        if let Some(domain) = pattern.strip_prefix("*.") {
175            if hostname.ends_with(domain) {
176                return true;
177            }
178        }
179
180        // 后缀匹配 (.example.com)
181        if pattern.starts_with('.') && hostname.ends_with(pattern) {
182            return true;
183        }
184    }
185
186    false
187}
188
189/// 获取目标 URL 的代理 URL
190pub fn get_proxy_for_url(target_url: &str, config: &ProxyConfig) -> Option<String> {
191    // 检查是否绕过代理
192    if should_bypass_proxy(target_url, &config.no_proxy) {
193        return None;
194    }
195
196    let is_https = target_url.starts_with("https://");
197
198    // SOCKS 代理优先
199    if let Some(ref socks) = config.socks {
200        return Some(socks.clone());
201    }
202
203    // 根据目标协议选择代理
204    if is_https {
205        config.https.clone().or_else(|| config.http.clone())
206    } else {
207        config.http.clone().or_else(|| config.https.clone())
208    }
209}
210
211/// 获取代理信息(用于调试)
212pub fn get_proxy_info(target_url: &str, config: Option<&ProxyConfig>) -> ProxyInfo {
213    let effective_config = match config {
214        Some(c) => c.clone(),
215        None => get_proxy_from_env(),
216    };
217
218    let bypassed = should_bypass_proxy(target_url, &effective_config.no_proxy);
219
220    if bypassed {
221        return ProxyInfo {
222            enabled: false,
223            proxy_url: None,
224            bypassed: true,
225        };
226    }
227
228    let proxy_url = get_proxy_for_url(target_url, &effective_config);
229
230    ProxyInfo {
231        enabled: proxy_url.is_some(),
232        proxy_url,
233        bypassed: false,
234    }
235}
236
237/// 构建带认证的代理 URL
238pub fn build_proxy_url_with_auth(
239    proxy_url: &str,
240    username: Option<&str>,
241    password: Option<&str>,
242) -> String {
243    if username.is_none() || password.is_none() {
244        return proxy_url.to_string();
245    }
246
247    match Url::parse(proxy_url) {
248        Ok(mut url) => {
249            if let Some(u) = username {
250                let _ = url.set_username(u);
251            }
252            if let Some(p) = password {
253                let _ = url.set_password(Some(p));
254            }
255            url.to_string()
256        }
257        Err(_) => proxy_url.to_string(),
258    }
259}
260
261/// 获取 reqwest 代理配置
262pub fn get_reqwest_proxy(config: Option<&ProxyConfig>) -> Option<reqwest::Proxy> {
263    let effective_config = match config {
264        Some(c) => c.clone(),
265        None => get_proxy_from_env(),
266    };
267
268    // 优先使用 HTTPS 代理
269    let proxy_url = effective_config
270        .https
271        .or(effective_config.http)
272        .or(effective_config.socks)?;
273
274    let parsed = parse_proxy_url(&proxy_url);
275    let final_url = build_proxy_url_with_auth(
276        &parsed.url,
277        effective_config
278            .username
279            .as_deref()
280            .or(parsed.username.as_deref()),
281        effective_config
282            .password
283            .as_deref()
284            .or(parsed.password.as_deref()),
285    );
286
287    reqwest::Proxy::all(&final_url).ok()
288}