Skip to main content

turbo_cdn/
http_client_manager.rs

1// Licensed under the MIT License
2// Copyright (c) 2025 Hal <hal.long@outlook.com>
3
4//! HTTP client manager using reqwest with rustls
5//!
6//! This module provides a simplified HTTP client manager using only reqwest
7//! for better cross-platform compatibility and easier maintenance.
8
9use crate::config::TurboCdnConfig;
10use crate::error::Result;
11use crate::http_client::HttpClient;
12use std::sync::Arc;
13use std::time::Duration;
14use tracing::info;
15
16/// HTTP client performance metrics
17#[derive(Debug, Clone)]
18pub struct ClientMetrics {
19    pub avg_response_time: Duration,
20    pub success_rate: f64,
21    pub throughput_mbps: f64,
22    pub last_updated: std::time::Instant,
23}
24
25/// HTTP request configuration
26#[derive(Debug, Clone)]
27pub struct RequestConfig {
28    pub url: String,
29    pub method: String,
30    pub headers: Vec<(String, String)>,
31    pub timeout: Duration,
32    pub follow_redirects: bool,
33    pub max_redirects: u32,
34    pub enable_compression: bool,
35    pub enable_http2: bool,
36}
37
38impl Default for RequestConfig {
39    fn default() -> Self {
40        Self {
41            url: String::new(),
42            method: "GET".to_string(),
43            headers: Vec::new(),
44            timeout: Duration::from_secs(30),
45            follow_redirects: true,
46            max_redirects: 10,
47            enable_compression: true,
48            enable_http2: true,
49        }
50    }
51}
52
53/// HTTP response wrapper
54#[derive(Debug)]
55pub struct HttpResponse {
56    pub status: u16,
57    pub headers: Vec<(String, String)>,
58    pub body: Vec<u8>,
59    pub response_time: Duration,
60    pub content_length: Option<u64>,
61    pub supports_ranges: bool,
62}
63
64/// HTTP client manager using reqwest
65#[derive(Debug)]
66pub struct HttpClientManager {
67    #[allow(dead_code)]
68    config: Arc<TurboCdnConfig>,
69    client: HttpClient,
70    metrics: Arc<std::sync::Mutex<ClientMetrics>>,
71}
72
73impl HttpClientManager {
74    /// Create a new HTTP client manager
75    pub fn new(config: Arc<TurboCdnConfig>) -> Result<Self> {
76        let timeout = Duration::from_secs(config.performance.timeout);
77        let client = HttpClient::new(timeout)?;
78
79        let metrics = ClientMetrics {
80            avg_response_time: Duration::from_millis(0),
81            success_rate: 1.0,
82            throughput_mbps: 0.0,
83            last_updated: std::time::Instant::now(),
84        };
85
86        info!("HTTP client manager initialized with reqwest client");
87
88        Ok(Self {
89            config,
90            client,
91            metrics: Arc::new(std::sync::Mutex::new(metrics)),
92        })
93    }
94
95    /// Perform a GET request
96    pub async fn get(&self, url: &str) -> Result<HttpResponse> {
97        let start_time = std::time::Instant::now();
98
99        let response = self.client.get(url).await?;
100        let response_time = start_time.elapsed();
101
102        // Update metrics
103        self.update_metrics(response_time, true);
104
105        Ok(HttpResponse {
106            status: response.status,
107            headers: response.headers.into_iter().collect(),
108            body: response.body,
109            response_time,
110            content_length: None,   // TODO: extract from headers
111            supports_ranges: false, // TODO: check Accept-Ranges header
112        })
113    }
114
115    /// Perform a GET request with custom headers
116    pub async fn get_with_headers(
117        &self,
118        url: &str,
119        headers: &std::collections::HashMap<String, String>,
120    ) -> Result<HttpResponse> {
121        let start_time = std::time::Instant::now();
122
123        let response = self.client.get_with_headers(url, headers).await?;
124        let response_time = start_time.elapsed();
125
126        // Update metrics
127        self.update_metrics(response_time, true);
128
129        Ok(HttpResponse {
130            status: response.status,
131            headers: response.headers.into_iter().collect(),
132            body: response.body,
133            response_time,
134            content_length: None,   // TODO: extract from headers
135            supports_ranges: false, // TODO: check Accept-Ranges header
136        })
137    }
138
139    /// Perform a HEAD request
140    pub async fn head(&self, url: &str) -> Result<HttpResponse> {
141        let start_time = std::time::Instant::now();
142
143        let response = self.client.head(url).await?;
144        let response_time = start_time.elapsed();
145
146        // Update metrics
147        self.update_metrics(response_time, true);
148
149        Ok(HttpResponse {
150            status: response.status,
151            headers: response.headers.into_iter().collect(),
152            body: response.body,
153            response_time,
154            content_length: None,   // TODO: extract from headers
155            supports_ranges: false, // TODO: check Accept-Ranges header
156        })
157    }
158
159    /// Update client metrics
160    fn update_metrics(&self, response_time: Duration, success: bool) {
161        if let Ok(mut metrics) = self.metrics.lock() {
162            // Simple exponential moving average
163            let alpha = 0.1;
164            let new_avg = metrics.avg_response_time.as_millis() as f64 * (1.0 - alpha)
165                + response_time.as_millis() as f64 * alpha;
166            metrics.avg_response_time = Duration::from_millis(new_avg as u64);
167
168            // Update success rate
169            if success {
170                metrics.success_rate = metrics.success_rate * 0.99 + 0.01;
171            } else {
172                metrics.success_rate *= 0.99;
173            }
174
175            metrics.last_updated = std::time::Instant::now();
176        }
177    }
178
179    /// Get current metrics
180    pub fn get_metrics(&self) -> Option<ClientMetrics> {
181        self.metrics.lock().ok().map(|m| m.clone())
182    }
183}