use reqwest::Client;
use std::sync::OnceLock;
use std::time::Duration;
use tracing::debug;
static GLOBAL_POOL: OnceLock<Client> = OnceLock::new();
const DEFAULT_MAX_IDLE_CONNECTIONS: usize = 100;
const DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST: usize = 32;
const DEFAULT_POOL_IDLE_TIMEOUT_SECS: u64 = 90;
const DEFAULT_REQUEST_TIMEOUT_SECS: u64 = 30;
const DEFAULT_CONNECT_TIMEOUT_SECS: u64 = 10;
#[derive(Debug, Clone)]
pub struct PoolConfig {
pub max_idle_connections: Option<usize>,
pub max_idle_connections_per_host: usize,
pub pool_idle_timeout: Duration,
pub request_timeout: Duration,
pub connect_timeout: Duration,
pub user_agent: Option<String>,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
max_idle_connections: Some(DEFAULT_MAX_IDLE_CONNECTIONS),
max_idle_connections_per_host: DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST,
pool_idle_timeout: Duration::from_secs(DEFAULT_POOL_IDLE_TIMEOUT_SECS),
request_timeout: Duration::from_secs(DEFAULT_REQUEST_TIMEOUT_SECS),
connect_timeout: Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS),
user_agent: None,
}
}
}
impl PoolConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_max_idle_connections(mut self, max: Option<usize>) -> Self {
self.max_idle_connections = max;
self
}
pub fn with_max_idle_connections_per_host(mut self, max: usize) -> Self {
self.max_idle_connections_per_host = max;
self
}
pub fn with_pool_idle_timeout(mut self, timeout: Duration) -> Self {
self.pool_idle_timeout = timeout;
self
}
pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
self.request_timeout = timeout;
self
}
pub fn with_connect_timeout(mut self, timeout: Duration) -> Self {
self.connect_timeout = timeout;
self
}
pub fn with_user_agent(mut self, user_agent: String) -> Self {
self.user_agent = Some(user_agent);
self
}
}
pub fn init_global_pool(config: PoolConfig) -> bool {
let client = create_pooled_client(config);
match GLOBAL_POOL.set(client) {
Ok(()) => {
debug!("Initialized global TACT HTTP connection pool");
true
}
Err(_) => {
debug!("Global TACT HTTP connection pool already initialized");
false
}
}
}
pub fn get_global_pool() -> &'static Client {
GLOBAL_POOL.get_or_init(|| {
debug!("Creating default global TACT HTTP connection pool");
create_pooled_client(PoolConfig::default())
})
}
pub fn create_pooled_client(config: PoolConfig) -> Client {
debug!(
"Creating HTTP client with pool settings: max_idle={:?}, max_per_host={}, idle_timeout={:?}",
config.max_idle_connections, config.max_idle_connections_per_host, config.pool_idle_timeout
);
let mut builder = Client::builder()
.pool_max_idle_per_host(config.max_idle_connections_per_host)
.pool_idle_timeout(config.pool_idle_timeout)
.timeout(config.request_timeout)
.connect_timeout(config.connect_timeout)
.use_rustls_tls() .tcp_keepalive(Duration::from_secs(60));
if let Some(max_idle) = config.max_idle_connections {
builder = builder.pool_max_idle_per_host(max_idle);
}
if let Some(user_agent) = config.user_agent {
builder = builder.user_agent(user_agent);
}
builder.build().expect("Failed to create HTTP client")
}
pub fn get_pool_stats() -> PoolStats {
PoolStats {
active_connections: None,
idle_connections: None,
total_connections: None,
}
}
#[derive(Debug, Clone)]
pub struct PoolStats {
pub active_connections: Option<usize>,
pub idle_connections: Option<usize>,
pub total_connections: Option<usize>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_config_builder() {
let config = PoolConfig::new()
.with_max_idle_connections(Some(50))
.with_max_idle_connections_per_host(20)
.with_pool_idle_timeout(Duration::from_secs(60))
.with_request_timeout(Duration::from_secs(45))
.with_connect_timeout(Duration::from_secs(15))
.with_user_agent("Test/1.0".to_string());
assert_eq!(config.max_idle_connections, Some(50));
assert_eq!(config.max_idle_connections_per_host, 20);
assert_eq!(config.pool_idle_timeout, Duration::from_secs(60));
assert_eq!(config.request_timeout, Duration::from_secs(45));
assert_eq!(config.connect_timeout, Duration::from_secs(15));
assert_eq!(config.user_agent, Some("Test/1.0".to_string()));
}
#[test]
fn test_create_pooled_client() {
let config = PoolConfig::default();
let client = create_pooled_client(config);
assert!(std::ptr::addr_of!(client) as usize != 0);
}
#[test]
fn test_global_pool_initialization() {
let _config = PoolConfig::default().with_user_agent("TestPool/1.0".to_string());
let _pool = get_global_pool();
let _stats = get_pool_stats();
}
#[test]
fn test_pool_config_defaults() {
let config = PoolConfig::default();
assert_eq!(
config.max_idle_connections,
Some(DEFAULT_MAX_IDLE_CONNECTIONS)
);
assert_eq!(
config.max_idle_connections_per_host,
DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST
);
assert_eq!(
config.pool_idle_timeout,
Duration::from_secs(DEFAULT_POOL_IDLE_TIMEOUT_SECS)
);
assert_eq!(
config.request_timeout,
Duration::from_secs(DEFAULT_REQUEST_TIMEOUT_SECS)
);
assert_eq!(
config.connect_timeout,
Duration::from_secs(DEFAULT_CONNECT_TIMEOUT_SECS)
);
assert!(config.user_agent.is_none());
}
}