use std::borrow::Cow;
use std::sync::Arc;
use std::sync::LazyLock;
use std::time::Duration;
use reqwest::Client;
use serde_json;
use crate::core::providers::unified_provider::ProviderError;
use crate::utils::net::http::{HttpClientPoolConfig, create_custom_client_with_config};
pub type HeaderPair = (Cow<'static, str>, Cow<'static, str>);
#[inline]
pub fn header(key: &'static str, value: String) -> HeaderPair {
(Cow::Borrowed(key), Cow::Owned(value))
}
#[inline]
pub fn header_static(key: &'static str, value: &'static str) -> HeaderPair {
(Cow::Borrowed(key), Cow::Borrowed(value))
}
#[inline]
pub fn header_owned(key: String, value: String) -> HeaderPair {
(Cow::Owned(key), Cow::Owned(value))
}
#[inline]
pub fn apply_headers(
mut builder: reqwest::RequestBuilder,
headers: Vec<HeaderPair>,
) -> reqwest::RequestBuilder {
for (key, value) in headers {
builder = builder.header(key.as_ref(), value.as_ref());
}
builder
}
#[derive(Debug, Clone)]
pub enum HttpMethod {
GET,
POST,
PUT,
DELETE,
}
pub struct PoolConfig;
impl PoolConfig {
pub const TIMEOUT_SECS: u64 = 600;
pub const POOL_SIZE: usize = 80;
pub const KEEPALIVE_SECS: u64 = 90;
}
#[inline]
fn pool_http_config() -> HttpClientPoolConfig {
HttpClientPoolConfig {
pool_max_idle_per_host: PoolConfig::POOL_SIZE,
pool_idle_timeout: Duration::from_secs(PoolConfig::KEEPALIVE_SECS),
..HttpClientPoolConfig::default()
}
}
static GLOBAL_CLIENT: LazyLock<Arc<Client>> = LazyLock::new(|| {
let client = create_custom_client_with_config(
Duration::from_secs(PoolConfig::TIMEOUT_SECS),
&pool_http_config(),
)
.unwrap_or_else(|e| {
tracing::error!("Failed to create global HTTP client: {}", e);
Client::new()
});
Arc::new(client)
});
#[inline]
pub fn global_client() -> Arc<Client> {
Arc::clone(&GLOBAL_CLIENT)
}
#[inline]
pub fn streaming_client() -> Arc<Client> {
global_client()
}
#[derive(Debug, Clone)]
pub struct ConnectionPool {
client: Arc<Client>,
}
impl ConnectionPool {
pub fn new() -> Result<Self, ProviderError> {
Ok(Self {
client: global_client(),
})
}
pub fn new_isolated() -> Result<Self, ProviderError> {
let client = create_custom_client_with_config(
Duration::from_secs(PoolConfig::TIMEOUT_SECS),
&pool_http_config(),
)
.map_err(|e| ProviderError::configuration("Failed to create HTTP client", e.to_string()))?;
Ok(Self {
client: Arc::new(client),
})
}
pub fn client(&self) -> &Client {
&self.client
}
}
#[derive(Debug, Clone)]
pub struct GlobalPoolManager {
pool: Arc<ConnectionPool>,
}
impl GlobalPoolManager {
pub fn new() -> Result<Self, ProviderError> {
Ok(Self {
pool: Arc::new(ConnectionPool::new()?),
})
}
pub fn shared() -> Self {
Self {
pool: Arc::new(ConnectionPool {
client: global_client(),
}),
}
}
pub async fn execute_request(
&self,
url: &str,
method: HttpMethod,
headers: Vec<HeaderPair>,
body: Option<serde_json::Value>,
) -> Result<reqwest::Response, ProviderError> {
let client = self.pool.client();
let mut request_builder = match method {
HttpMethod::GET => client.get(url),
HttpMethod::POST => client.post(url),
HttpMethod::PUT => client.put(url),
HttpMethod::DELETE => client.delete(url),
};
for (key, value) in headers {
request_builder = request_builder.header(key.as_ref(), value.as_ref());
}
if let Some(body_data) = body {
request_builder = request_builder
.header("Content-Type", "application/json")
.json(&body_data);
}
request_builder
.send()
.await
.map_err(|e| ProviderError::network("common", e.to_string()))
}
pub fn client(&self) -> &Client {
self.pool.client()
}
}
impl Default for GlobalPoolManager {
fn default() -> Self {
Self::shared()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_pool_creation() {
let pool = ConnectionPool::new();
assert!(pool.is_ok());
}
#[tokio::test]
async fn test_global_manager() {
let manager = GlobalPoolManager::new();
assert!(manager.is_ok());
}
#[tokio::test]
async fn test_global_client_singleton() {
let client1 = global_client();
let client2 = global_client();
assert!(Arc::ptr_eq(&client1, &client2));
}
#[tokio::test]
async fn test_multiple_managers_share_client() {
let manager1 = GlobalPoolManager::new().unwrap();
let manager2 = GlobalPoolManager::new().unwrap();
let manager3 = GlobalPoolManager::shared();
let client1 = manager1.pool.client.clone();
let client2 = manager2.pool.client.clone();
let client3 = manager3.pool.client.clone();
assert!(Arc::ptr_eq(&client1, &client2));
assert!(Arc::ptr_eq(&client2, &client3));
}
#[tokio::test]
async fn test_isolated_pool_is_different() {
let global = global_client();
let isolated = ConnectionPool::new_isolated().unwrap();
assert!(!Arc::ptr_eq(&global, &isolated.client));
}
#[test]
fn test_default_manager() {
let manager = GlobalPoolManager::default();
let _client = manager.client();
}
}