Skip to main content

pipedash_core/infrastructure/
http_client.rs

1use std::sync::Arc;
2use std::time::Duration;
3
4use dashmap::DashMap;
5use reqwest::Client;
6
7use crate::domain::{
8    DomainError,
9    DomainResult,
10};
11
12pub struct HttpClientManager {
13    default_client: Arc<Client>,
14    custom_clients: DashMap<String, Arc<Client>>,
15}
16
17impl HttpClientManager {
18    pub fn new() -> DomainResult<Self> {
19        let default_client = Self::create_optimized_client()?;
20        Ok(Self {
21            default_client: Arc::new(default_client),
22            custom_clients: DashMap::new(),
23        })
24    }
25
26    pub fn default_client(&self) -> Arc<Client> {
27        Arc::clone(&self.default_client)
28    }
29
30    pub fn client_for_url(&self, base_url: &str) -> DomainResult<Arc<Client>> {
31        if let Some(client) = self.custom_clients.get(base_url) {
32            return Ok(Arc::clone(client.value()));
33        }
34
35        let client = Self::create_optimized_client()?;
36        let client = Arc::new(client);
37        self.custom_clients
38            .insert(base_url.to_string(), Arc::clone(&client));
39        Ok(client)
40    }
41
42    fn create_optimized_client() -> DomainResult<Client> {
43        let pool_size = std::env::var("PIPEDASH_HTTP_POOL_SIZE")
44            .ok()
45            .and_then(|s| s.parse().ok())
46            .unwrap_or(10);
47
48        let _ = rustls::crypto::ring::default_provider().install_default();
49
50        Client::builder()
51            .use_rustls_tls()
52            .pool_max_idle_per_host(pool_size)
53            .pool_idle_timeout(Duration::from_secs(90))
54            .tcp_keepalive(Duration::from_secs(60))
55            .timeout(Duration::from_secs(30))
56            .connect_timeout(Duration::from_secs(10))
57            .build()
58            .map_err(|e| DomainError::InternalError(format!("Failed to create HTTP client: {}", e)))
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_http_client_manager_creation() {
68        let manager = HttpClientManager::new().unwrap();
69        let client = manager.default_client();
70        assert!(Arc::strong_count(&client) >= 1);
71    }
72
73    #[test]
74    fn test_client_caching() {
75        let manager = HttpClientManager::new().unwrap();
76
77        let client1 = manager.client_for_url("https://api.github.com").unwrap();
78        let client2 = manager.client_for_url("https://api.github.com").unwrap();
79
80        assert!(Arc::ptr_eq(&client1, &client2));
81    }
82
83    #[test]
84    fn test_different_urls_different_clients() {
85        let manager = HttpClientManager::new().unwrap();
86
87        let client1 = manager.client_for_url("https://api.github.com").unwrap();
88        let client2 = manager.client_for_url("https://gitlab.com").unwrap();
89
90        assert!(!Arc::ptr_eq(&client1, &client2));
91    }
92
93    #[test]
94    fn test_default_client_separate_from_custom() {
95        let manager = HttpClientManager::new().unwrap();
96
97        let default_client = manager.default_client();
98        let custom_client = manager.client_for_url("https://api.github.com").unwrap();
99
100        assert!(!Arc::ptr_eq(&default_client, &custom_client));
101    }
102}