1use reqwest::Client;
4use std::sync::OnceLock;
5use std::time::Duration;
6use tracing::debug;
7
8static GLOBAL_POOL: OnceLock<Client> = OnceLock::new();
10
11const DEFAULT_MAX_IDLE_CONNECTIONS: usize = 100;
13const DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST: usize = 32;
14const DEFAULT_POOL_IDLE_TIMEOUT_SECS: u64 = 90;
15const DEFAULT_REQUEST_TIMEOUT_SECS: u64 = 30;
16const DEFAULT_CONNECT_TIMEOUT_SECS: u64 = 10;
17
18#[derive(Debug, Clone)]
20pub struct PoolConfig {
21 pub max_idle_connections: Option<usize>,
23 pub max_idle_connections_per_host: usize,
25 pub pool_idle_timeout: Duration,
27 pub request_timeout: Duration,
29 pub connect_timeout: Duration,
31 pub user_agent: Option<String>,
33}
34
35impl Default for PoolConfig {
36 fn default() -> Self {
37 Self {
38 max_idle_connections: Some(DEFAULT_MAX_IDLE_CONNECTIONS),
39 max_idle_connections_per_host: DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST,
40 pool_idle_timeout: Duration::from_secs(DEFAULT_POOL_IDLE_TIMEOUT_SECS),
41 request_timeout: Duration::from_secs(DEFAULT_REQUEST_TIMEOUT_SECS),
42 connect_timeout: Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS),
43 user_agent: None,
44 }
45 }
46}
47
48impl PoolConfig {
49 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub fn with_max_idle_connections(mut self, max: Option<usize>) -> Self {
56 self.max_idle_connections = max;
57 self
58 }
59
60 pub fn with_max_idle_connections_per_host(mut self, max: usize) -> Self {
62 self.max_idle_connections_per_host = max;
63 self
64 }
65
66 pub fn with_pool_idle_timeout(mut self, timeout: Duration) -> Self {
68 self.pool_idle_timeout = timeout;
69 self
70 }
71
72 pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
74 self.request_timeout = timeout;
75 self
76 }
77
78 pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
80 self.connect_timeout = timeout;
81 self
82 }
83
84 pub fn with_user_agent(mut self, user_agent: String) -> Self {
86 self.user_agent = Some(user_agent);
87 self
88 }
89}
90
91pub fn init_global_pool(config: PoolConfig) -> bool {
99 let client = create_pooled_client(config);
100
101 match GLOBAL_POOL.set(client) {
102 Ok(()) => {
103 debug!("Initialized global TACT HTTP connection pool");
104 true
105 }
106 Err(_) => {
107 debug!("Global TACT HTTP connection pool already initialized");
108 false
109 }
110 }
111}
112
113pub fn get_global_pool() -> &'static Client {
118 GLOBAL_POOL.get_or_init(|| {
119 debug!("Creating default global TACT HTTP connection pool");
120 create_pooled_client(PoolConfig::default())
121 })
122}
123
124pub fn create_pooled_client(config: PoolConfig) -> Client {
126 debug!(
127 "Creating HTTP client with pool settings: max_idle={:?}, max_per_host={}, idle_timeout={:?}",
128 config.max_idle_connections, config.max_idle_connections_per_host, config.pool_idle_timeout
129 );
130
131 let mut builder = Client::builder()
132 .pool_max_idle_per_host(config.max_idle_connections_per_host)
133 .pool_idle_timeout(config.pool_idle_timeout)
134 .timeout(config.request_timeout)
135 .connect_timeout(config.connect_timeout)
136 .use_rustls_tls() .tcp_keepalive(Duration::from_secs(60)); if let Some(max_idle) = config.max_idle_connections {
141 builder = builder.pool_max_idle_per_host(max_idle);
142 }
143
144 if let Some(user_agent) = config.user_agent {
145 builder = builder.user_agent(user_agent);
146 }
147
148 builder.build().expect("Failed to create HTTP client")
149}
150
151pub fn get_pool_stats() -> PoolStats {
156 PoolStats {
158 active_connections: None,
159 idle_connections: None,
160 total_connections: None,
161 }
162}
163
164#[derive(Debug, Clone)]
166pub struct PoolStats {
167 pub active_connections: Option<usize>,
169 pub idle_connections: Option<usize>,
171 pub total_connections: Option<usize>,
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_pool_config_builder() {
181 let config = PoolConfig::new()
182 .with_max_idle_connections(Some(50))
183 .with_max_idle_connections_per_host(20)
184 .with_pool_idle_timeout(Duration::from_secs(60))
185 .with_request_timeout(Duration::from_secs(45))
186 .with_connect_timeout(Duration::from_secs(15))
187 .with_user_agent("Test/1.0".to_string());
188
189 assert_eq!(config.max_idle_connections, Some(50));
190 assert_eq!(config.max_idle_connections_per_host, 20);
191 assert_eq!(config.pool_idle_timeout, Duration::from_secs(60));
192 assert_eq!(config.request_timeout, Duration::from_secs(45));
193 assert_eq!(config.connect_timeout, Duration::from_secs(15));
194 assert_eq!(config.user_agent, Some("Test/1.0".to_string()));
195 }
196
197 #[test]
198 fn test_create_pooled_client() {
199 let config = PoolConfig::default();
200 let client = create_pooled_client(config);
201
202 assert!(std::ptr::addr_of!(client) as usize != 0);
204 }
205
206 #[test]
207 fn test_global_pool_initialization() {
208 let _config = PoolConfig::default().with_user_agent("TestPool/1.0".to_string());
211
212 let _pool = get_global_pool();
214
215 let _stats = get_pool_stats();
217 }
218
219 #[test]
220 fn test_pool_config_defaults() {
221 let config = PoolConfig::default();
222 assert_eq!(
223 config.max_idle_connections,
224 Some(DEFAULT_MAX_IDLE_CONNECTIONS)
225 );
226 assert_eq!(
227 config.max_idle_connections_per_host,
228 DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST
229 );
230 assert_eq!(
231 config.pool_idle_timeout,
232 Duration::from_secs(DEFAULT_POOL_IDLE_TIMEOUT_SECS)
233 );
234 assert_eq!(
235 config.request_timeout,
236 Duration::from_secs(DEFAULT_REQUEST_TIMEOUT_SECS)
237 );
238 assert_eq!(
239 config.connect_timeout,
240 Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS)
241 );
242 assert!(config.user_agent.is_none());
243 }
244}