mangofetch_core/core/
http_client.rs1use std::sync::LazyLock;
2use std::sync::RwLock;
3
4use crate::models::settings::ProxySettings;
5
6static GLOBAL_PROXY: LazyLock<RwLock<ProxySettings>> =
7 LazyLock::new(|| RwLock::new(ProxySettings::default()));
8
9pub fn init_proxy(proxy: ProxySettings) {
10 if let Ok(mut guard) = GLOBAL_PROXY.write() {
11 *guard = proxy;
12 }
13}
14
15pub fn get_proxy_snapshot() -> ProxySettings {
16 GLOBAL_PROXY.read().map(|g| g.clone()).unwrap_or_default()
17}
18
19pub fn proxy_url() -> Option<String> {
20 let proxy = get_proxy_snapshot();
21 if !proxy.enabled || proxy.host.is_empty() {
22 return None;
23 }
24 let scheme = match proxy.proxy_type.as_str() {
25 "socks5" => "socks5",
26 "https" => "https",
27 _ => "http",
28 };
29 if !proxy.username.is_empty() {
30 Some(format!(
31 "{}://{}:{}@{}:{}",
32 scheme, proxy.username, proxy.password, proxy.host, proxy.port
33 ))
34 } else {
35 Some(format!("{}://{}:{}", scheme, proxy.host, proxy.port))
36 }
37}
38
39pub fn apply_proxy(
40 builder: reqwest::ClientBuilder,
41 proxy: &ProxySettings,
42) -> reqwest::ClientBuilder {
43 if !proxy.enabled || proxy.host.is_empty() {
44 return builder;
45 }
46 let scheme = match proxy.proxy_type.as_str() {
47 "socks5" => "socks5",
48 "https" => "https",
49 _ => "http",
50 };
51 let proxy_url = if !proxy.username.is_empty() {
52 format!(
53 "{}://{}:{}@{}:{}",
54 scheme, proxy.username, proxy.password, proxy.host, proxy.port
55 )
56 } else {
57 format!("{}://{}:{}", scheme, proxy.host, proxy.port)
58 };
59 match reqwest::Proxy::all(&proxy_url) {
60 Ok(p) => builder.proxy(p),
61 Err(e) => {
62 tracing::warn!("Invalid proxy URL: {}", e);
63 builder
64 }
65 }
66}
67
68pub fn apply_global_proxy(builder: reqwest::ClientBuilder) -> reqwest::ClientBuilder {
69 let proxy = get_proxy_snapshot();
70 apply_proxy(builder, &proxy)
71}
72
73pub fn inject_ua_header(headers: &mut reqwest::header::HeaderMap, opts_ua: Option<&str>) {
74 if let Some(ua) = opts_ua {
75 if let Ok(v) = reqwest::header::HeaderValue::from_str(ua) {
76 headers.insert(reqwest::header::USER_AGENT, v);
77 }
78 }
79}
80
81pub fn ua_header_map(opts_ua: Option<&str>) -> Option<reqwest::header::HeaderMap> {
82 let ua = opts_ua?;
83 let value = reqwest::header::HeaderValue::from_str(ua).ok()?;
84 let mut headers = reqwest::header::HeaderMap::new();
85 headers.insert(reqwest::header::USER_AGENT, value);
86 Some(headers)
87}
88
89pub async fn download_with_progress<F>(url: &str, mut on_progress: F) -> anyhow::Result<Vec<u8>>
90where
91 F: FnMut(f32) + Send,
92{
93 let client = apply_global_proxy(reqwest::Client::builder())
94 .timeout(std::time::Duration::from_secs(300))
95 .build()?;
96
97 let response = client.get(url).send().await?;
98 if !response.status().is_success() {
99 return Err(anyhow::anyhow!("HTTP error: {}", response.status()));
100 }
101
102 let total_size = response.content_length().unwrap_or(0);
103 let mut downloaded: u64 = 0;
104 let mut buffer = Vec::with_capacity(total_size as usize);
105
106 use futures::StreamExt;
107 let mut stream = response.bytes_stream();
108
109 while let Some(chunk_result) = stream.next().await {
110 let chunk = chunk_result?;
111 buffer.extend_from_slice(&chunk);
112 downloaded += chunk.len() as u64;
113
114 if total_size > 0 {
115 let percent = (downloaded as f32 / total_size as f32) * 100.0;
116 on_progress(percent);
117 }
118 }
119
120 Ok(buffer)
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::models::settings::ProxySettings;
127
128 #[test]
129 fn test_proxy_url_generation() {
130 init_proxy(ProxySettings {
132 enabled: false,
133 proxy_type: "http".into(),
134 host: "127.0.0.1".into(),
135 port: 8080,
136 username: "".into(),
137 password: "".into(),
138 });
139 assert_eq!(proxy_url(), None);
140
141 init_proxy(ProxySettings {
143 enabled: true,
144 proxy_type: "http".into(),
145 host: "".into(),
146 port: 8080,
147 username: "".into(),
148 password: "".into(),
149 });
150 assert_eq!(proxy_url(), None);
151
152 init_proxy(ProxySettings {
154 enabled: true,
155 proxy_type: "http".into(),
156 host: "127.0.0.1".into(),
157 port: 8080,
158 username: "".into(),
159 password: "".into(),
160 });
161 assert_eq!(proxy_url(), Some("http://127.0.0.1:8080".into()));
162
163 init_proxy(ProxySettings {
165 enabled: true,
166 proxy_type: "socks5".into(),
167 host: "127.0.0.1".into(),
168 port: 1080,
169 username: "user".into(),
170 password: "password".into(),
171 });
172 assert_eq!(
173 proxy_url(),
174 Some("socks5://user:password@127.0.0.1:1080".into())
175 );
176
177 init_proxy(ProxySettings {
179 enabled: true,
180 proxy_type: "https".into(),
181 host: "127.0.0.1".into(),
182 port: 443,
183 username: "".into(),
184 password: "".into(),
185 });
186 assert_eq!(proxy_url(), Some("https://127.0.0.1:443".into()));
187
188 init_proxy(ProxySettings {
190 enabled: true,
191 proxy_type: "unknown".into(),
192 host: "127.0.0.1".into(),
193 port: 8080,
194 username: "".into(),
195 password: "".into(),
196 });
197 assert_eq!(proxy_url(), Some("http://127.0.0.1:8080".into()));
198 }
199}