1use crate::config::HttpConfig;
7use crate::error::{EngineError, NetworkErrorKind, Result};
8use governor::{DefaultDirectRateLimiter, Quota, RateLimiter};
9use parking_lot::RwLock as ParkingRwLock;
10use reqwest::Client;
11use std::num::NonZeroU32;
12use std::sync::atomic::{AtomicU64, Ordering};
13use std::sync::Arc;
14use std::time::{Duration, Instant};
15use tokio::sync::RwLock;
16
17pub struct ConnectionPool {
19 client: Client,
21 download_limiter: ParkingRwLock<Option<Arc<DefaultDirectRateLimiter>>>,
23 upload_limiter: ParkingRwLock<Option<Arc<DefaultDirectRateLimiter>>>,
25 total_downloaded: AtomicU64,
27 total_uploaded: AtomicU64,
29 active_connections: AtomicU64,
31 stats: RwLock<ConnectionStats>,
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct ConnectionStats {
38 pub connections_created: u64,
40 pub successful_requests: u64,
42 pub failed_requests: u64,
44 pub retried_requests: u64,
46 pub avg_response_time_ms: f64,
48 pub last_error: Option<String>,
50}
51
52impl ConnectionPool {
53 pub fn new(config: &HttpConfig) -> Result<Self> {
55 let mut builder = Client::builder()
56 .connect_timeout(Duration::from_secs(config.connect_timeout))
57 .read_timeout(Duration::from_secs(config.read_timeout))
58 .redirect(reqwest::redirect::Policy::limited(config.max_redirects))
59 .danger_accept_invalid_certs(config.accept_invalid_certs)
60 .pool_max_idle_per_host(32)
61 .pool_idle_timeout(Duration::from_secs(90))
62 .gzip(false)
66 .brotli(false);
67
68 if let Some(ref proxy_url) = config.proxy_url {
70 let proxy = reqwest::Proxy::all(proxy_url)
71 .map_err(|e| EngineError::Internal(format!("Invalid proxy URL: {}", e)))?;
72 builder = builder.proxy(proxy);
73 }
74
75 let client = builder
76 .build()
77 .map_err(|e| EngineError::Internal(format!("Failed to create HTTP client: {}", e)))?;
78
79 Ok(Self {
80 client,
81 download_limiter: ParkingRwLock::new(None),
82 upload_limiter: ParkingRwLock::new(None),
83 total_downloaded: AtomicU64::new(0),
84 total_uploaded: AtomicU64::new(0),
85 active_connections: AtomicU64::new(0),
86 stats: RwLock::new(ConnectionStats::default()),
87 })
88 }
89
90 pub fn with_limits(
92 config: &HttpConfig,
93 download_limit: Option<u64>,
94 upload_limit: Option<u64>,
95 ) -> Result<Self> {
96 let pool = Self::new(config)?;
97 pool.set_download_limit(download_limit);
98 pool.set_upload_limit(upload_limit);
99
100 Ok(pool)
101 }
102
103 pub fn client(&self) -> &Client {
105 &self.client
106 }
107
108 pub fn set_download_limit(&self, limit: Option<u64>) {
110 *self.download_limiter.write() = limit.and_then(build_rate_limiter);
111 }
112
113 pub fn set_upload_limit(&self, limit: Option<u64>) {
115 *self.upload_limiter.write() = limit.and_then(build_rate_limiter);
116 }
117
118 pub async fn acquire_download(&self, bytes: u64) {
120 let limiter = self.download_limiter.read().clone();
121 if let Some(limiter) = limiter {
122 for chunk in limiter_chunks(bytes) {
123 let _ = limiter.until_n_ready(chunk).await;
124 }
125 }
126 }
127
128 pub async fn acquire_upload(&self, bytes: u64) {
130 let limiter = self.upload_limiter.read().clone();
131 if let Some(limiter) = limiter {
132 for chunk in limiter_chunks(bytes) {
133 let _ = limiter.until_n_ready(chunk).await;
134 }
135 }
136 }
137
138 pub fn record_download(&self, bytes: u64) {
140 self.total_downloaded.fetch_add(bytes, Ordering::Relaxed);
141 }
142
143 pub fn record_upload(&self, bytes: u64) {
145 self.total_uploaded.fetch_add(bytes, Ordering::Relaxed);
146 }
147
148 pub fn total_downloaded(&self) -> u64 {
150 self.total_downloaded.load(Ordering::Relaxed)
151 }
152
153 pub fn total_uploaded(&self) -> u64 {
155 self.total_uploaded.load(Ordering::Relaxed)
156 }
157
158 pub fn connection_started(&self) {
160 self.active_connections.fetch_add(1, Ordering::Relaxed);
161 }
162
163 pub fn connection_finished(&self) {
165 self.active_connections.fetch_sub(1, Ordering::Relaxed);
166 }
167
168 pub fn active_connections(&self) -> u64 {
170 self.active_connections.load(Ordering::Relaxed)
171 }
172
173 pub async fn record_success(&self, response_time_ms: f64) {
175 let mut stats = self.stats.write().await;
176 stats.successful_requests += 1;
177
178 let alpha = 0.2;
180 stats.avg_response_time_ms =
181 alpha * response_time_ms + (1.0 - alpha) * stats.avg_response_time_ms;
182 }
183
184 pub async fn record_failure(&self, error: &str) {
186 let mut stats = self.stats.write().await;
187 stats.failed_requests += 1;
188 stats.last_error = Some(error.to_string());
189 }
190
191 pub async fn record_retry(&self) {
193 let mut stats = self.stats.write().await;
194 stats.retried_requests += 1;
195 }
196
197 pub async fn stats(&self) -> ConnectionStats {
199 self.stats.read().await.clone()
200 }
201}
202
203fn build_rate_limiter(limit: u64) -> Option<Arc<DefaultDirectRateLimiter>> {
204 let clamped = limit.min(u32::MAX as u64) as u32;
205 NonZeroU32::new(clamped).map(|n| Arc::new(RateLimiter::direct(Quota::per_second(n))))
206}
207
208fn limiter_chunks(bytes: u64) -> Vec<NonZeroU32> {
209 const CHUNK_SIZE: u64 = 16 * 1024;
210
211 if bytes == 0 {
212 return Vec::new();
213 }
214
215 let full_chunks = bytes / CHUNK_SIZE;
216 let remainder = bytes % CHUNK_SIZE;
217 let mut chunks = Vec::with_capacity(full_chunks as usize + usize::from(remainder > 0));
218
219 for _ in 0..full_chunks {
220 chunks.push(NonZeroU32::new(CHUNK_SIZE as u32).expect("chunk size is non-zero"));
221 }
222
223 if remainder > 0 {
224 chunks.push(NonZeroU32::new(remainder as u32).expect("remainder is non-zero"));
225 }
226
227 chunks
228}
229
230#[cfg(test)]
231mod limiter_tests {
232 use super::limiter_chunks;
233
234 #[test]
235 fn limiter_chunks_is_empty_for_zero_bytes() {
236 assert!(limiter_chunks(0).is_empty());
237 }
238
239 #[test]
240 fn limiter_chunks_preserves_exact_byte_count() {
241 let chunks = limiter_chunks(16 * 1024 + 17);
242 let total: u64 = chunks.into_iter().map(|chunk| chunk.get() as u64).sum();
243 assert_eq!(total, 16 * 1024 + 17);
244 }
245
246 #[test]
247 fn limiter_chunks_does_not_over_throttle_small_reads() {
248 let chunks = limiter_chunks(1);
249 assert_eq!(chunks.len(), 1);
250 assert_eq!(chunks[0].get(), 1);
251 }
252}
253
254#[derive(Debug, Clone)]
256pub struct RetryPolicy {
257 pub max_attempts: u32,
259 pub initial_delay_ms: u64,
261 pub max_delay_ms: u64,
263 pub jitter_factor: f64,
265}
266
267impl Default for RetryPolicy {
268 fn default() -> Self {
269 Self {
270 max_attempts: 3,
271 initial_delay_ms: 1000,
272 max_delay_ms: 30000,
273 jitter_factor: 0.25,
274 }
275 }
276}
277
278impl RetryPolicy {
279 pub fn new(max_attempts: u32, initial_delay_ms: u64, max_delay_ms: u64) -> Self {
281 Self {
282 max_attempts,
283 initial_delay_ms,
284 max_delay_ms,
285 jitter_factor: 0.25,
286 }
287 }
288
289 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
291 let base = self.initial_delay_ms * 2u64.pow(attempt.min(10));
293 let capped = base.min(self.max_delay_ms);
294
295 let jitter = (rand::random::<f64>() - 0.5) * 2.0 * self.jitter_factor;
297 let with_jitter = (capped as f64 * (1.0 + jitter)) as u64;
298
299 Duration::from_millis(with_jitter)
300 }
301
302 pub fn should_retry(&self, attempt: u32, error: &EngineError) -> bool {
304 if attempt >= self.max_attempts {
305 return false;
306 }
307
308 error.is_retryable()
309 }
310}
311
312pub async fn with_retry<F, T, Fut>(
314 pool: &ConnectionPool,
315 policy: &RetryPolicy,
316 operation: F,
317) -> Result<T>
318where
319 F: Fn() -> Fut,
320 Fut: std::future::Future<Output = Result<T>>,
321{
322 let mut last_error = None;
323
324 for attempt in 0..policy.max_attempts {
325 let start = Instant::now();
326
327 match operation().await {
328 Ok(result) => {
329 let elapsed = start.elapsed().as_millis() as f64;
330 pool.record_success(elapsed).await;
331 return Ok(result);
332 }
333 Err(e) => {
334 let _elapsed = start.elapsed().as_millis() as f64;
335 pool.record_failure(&e.to_string()).await;
336
337 if policy.should_retry(attempt, &e) {
338 pool.record_retry().await;
339 let delay = policy.delay_for_attempt(attempt);
340 tracing::debug!(
341 "Request failed (attempt {}), retrying in {:?}: {}",
342 attempt + 1,
343 delay,
344 e
345 );
346 tokio::time::sleep(delay).await;
347 last_error = Some(e);
348 } else {
349 return Err(e);
350 }
351 }
352 }
353 }
354
355 Err(last_error
356 .unwrap_or_else(|| EngineError::network(NetworkErrorKind::Other, "Max retries exceeded")))
357}
358
359#[derive(Debug)]
361pub struct SpeedCalculator {
362 window_size: usize,
364 measurements: Vec<(u64, Instant)>,
366 total_bytes: u64,
368}
369
370impl SpeedCalculator {
371 pub fn new(window_size: usize) -> Self {
373 Self {
374 window_size,
375 measurements: Vec::with_capacity(window_size),
376 total_bytes: 0,
377 }
378 }
379
380 pub fn add_bytes(&mut self, bytes: u64) {
382 let now = Instant::now();
383 self.total_bytes += bytes;
384
385 if self.measurements.len() >= self.window_size {
386 self.measurements.remove(0);
387 }
388 self.measurements.push((bytes, now));
389 }
390
391 pub fn speed(&self) -> u64 {
393 if self.measurements.len() < 2 {
394 return 0;
395 }
396
397 let first = &self.measurements[0];
398 let last = &self.measurements[self.measurements.len() - 1];
399
400 let elapsed = last.1.duration_since(first.1).as_secs_f64();
401 if elapsed <= 0.0 {
402 return 0;
403 }
404
405 let bytes: u64 = self.measurements.iter().map(|(b, _)| *b).sum();
406 (bytes as f64 / elapsed) as u64
407 }
408
409 pub fn total(&self) -> u64 {
411 self.total_bytes
412 }
413
414 pub fn reset(&mut self) {
416 self.measurements.clear();
417 self.total_bytes = 0;
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn test_retry_delay() {
427 let policy = RetryPolicy::new(3, 1000, 30000);
428
429 let delay0 = policy.delay_for_attempt(0);
431 assert!(delay0.as_millis() >= 750 && delay0.as_millis() <= 1250);
432
433 let delay1 = policy.delay_for_attempt(1);
435 assert!(delay1.as_millis() >= 1500 && delay1.as_millis() <= 2500);
436
437 let delay2 = policy.delay_for_attempt(2);
439 assert!(delay2.as_millis() >= 3000 && delay2.as_millis() <= 5000);
440 }
441
442 #[test]
443 fn test_speed_calculator() {
444 let mut calc = SpeedCalculator::new(10);
445
446 calc.add_bytes(1000);
448 std::thread::sleep(Duration::from_millis(100));
449 calc.add_bytes(1000);
450 std::thread::sleep(Duration::from_millis(100));
451 calc.add_bytes(1000);
452
453 let speed = calc.speed();
456 assert!(speed > 0);
457
458 assert_eq!(calc.total(), 3000);
459 }
460
461 #[test]
462 fn test_retry_policy_defaults() {
463 let policy = RetryPolicy::default();
464 assert_eq!(policy.max_attempts, 3);
465 assert_eq!(policy.initial_delay_ms, 1000);
466 assert_eq!(policy.max_delay_ms, 30000);
467 }
468}