use std::time::Duration;
use once_cell::sync::Lazy;
use reqwest::Client;
pub const DEFAULT_TIMEOUT_SECS: u64 = 30;
pub const DEFAULT_USER_AGENT: &str = concat!(
"Terraphim/",
env!("CARGO_PKG_VERSION"),
" (https://github.com/terraphim/terraphim-ai)"
);
const POOL_MAX_IDLE_PER_HOST: usize = 10;
const POOL_IDLE_TIMEOUT_SECS: u64 = 90;
static DEFAULT_CLIENT: Lazy<Client> = Lazy::new(|| {
Client::builder()
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.user_agent(DEFAULT_USER_AGENT)
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
.pool_idle_timeout(Duration::from_secs(POOL_IDLE_TIMEOUT_SECS))
.build()
.expect("Failed to build default HTTP client")
});
static API_CLIENT: Lazy<Client> = Lazy::new(|| {
use reqwest::header::{ACCEPT, CONTENT_TYPE, HeaderMap, HeaderValue};
let mut headers = HeaderMap::new();
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
Client::builder()
.timeout(Duration::from_secs(10))
.user_agent(DEFAULT_USER_AGENT)
.default_headers(headers)
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
.pool_idle_timeout(Duration::from_secs(POOL_IDLE_TIMEOUT_SECS))
.build()
.expect("Failed to build API HTTP client")
});
static SCRAPING_CLIENT: Lazy<Client> = Lazy::new(|| {
use reqwest::header::{ACCEPT, HeaderMap, HeaderValue};
let mut headers = HeaderMap::new();
headers.insert(
ACCEPT,
HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
);
Client::builder()
.timeout(Duration::from_secs(60))
.user_agent("Mozilla/5.0 (compatible; Terraphim/1.0; +https://terraphim.ai)")
.default_headers(headers)
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
.pool_idle_timeout(Duration::from_secs(POOL_IDLE_TIMEOUT_SECS))
.build()
.expect("Failed to build scraping HTTP client")
});
pub fn get_default_client() -> &'static Client {
&DEFAULT_CLIENT
}
pub fn create_client_with_timeout(timeout_secs: u64) -> reqwest::Result<Client> {
Client::builder()
.timeout(Duration::from_secs(timeout_secs))
.user_agent(DEFAULT_USER_AGENT)
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
.pool_idle_timeout(Duration::from_secs(POOL_IDLE_TIMEOUT_SECS))
.build()
}
pub fn get_api_client() -> &'static Client {
&API_CLIENT
}
pub fn create_custom_client(
timeout: Option<Duration>,
default_headers: Option<reqwest::header::HeaderMap>,
proxy: Option<reqwest::Proxy>,
) -> reqwest::Result<Client> {
let mut builder = Client::builder()
.user_agent(DEFAULT_USER_AGENT)
.pool_max_idle_per_host(POOL_MAX_IDLE_PER_HOST)
.pool_idle_timeout(Duration::from_secs(POOL_IDLE_TIMEOUT_SECS));
if let Some(timeout) = timeout {
builder = builder.timeout(timeout);
} else {
builder = builder.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS));
}
if let Some(headers) = default_headers {
builder = builder.default_headers(headers);
}
if let Some(proxy) = proxy {
builder = builder.proxy(proxy);
}
builder.build()
}
pub fn get_scraping_client() -> &'static Client {
&SCRAPING_CLIENT
}
pub fn create_default_client() -> reqwest::Result<Client> {
Ok(get_default_client().clone())
}
pub fn create_api_client() -> reqwest::Result<Client> {
Ok(get_api_client().clone())
}
pub fn create_scraping_client() -> reqwest::Result<Client> {
Ok(get_scraping_client().clone())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_default_client() {
let client = get_default_client();
let client2 = get_default_client();
assert!(
std::ptr::eq(client, client2),
"Should return same client instance"
);
}
#[test]
fn test_get_api_client() {
let client = get_api_client();
let client2 = get_api_client();
assert!(
std::ptr::eq(client, client2),
"Should return same API client instance"
);
}
#[test]
fn test_get_scraping_client() {
let client = get_scraping_client();
let client2 = get_scraping_client();
assert!(
std::ptr::eq(client, client2),
"Should return same scraping client instance"
);
}
#[test]
fn test_create_client_with_timeout() {
let client = create_client_with_timeout(5);
assert!(
client.is_ok(),
"Client with custom timeout should be created"
);
}
#[test]
fn test_create_custom_client_minimal() {
let client = create_custom_client(None, None, None);
assert!(client.is_ok(), "Custom client with no options should work");
}
#[test]
fn test_user_agent_contains_version() {
assert!(DEFAULT_USER_AGENT.contains("Terraphim/"));
assert!(DEFAULT_USER_AGENT.contains("https://github.com/terraphim/terraphim-ai"));
}
#[test]
fn test_backwards_compatibility() {
let _client = create_default_client().unwrap();
let _api_client = create_api_client().unwrap();
let _scraping_client = create_scraping_client().unwrap();
}
}