use std::time::Duration;
use anyhow::Result;
use reqwest::Client;
use tracing::debug;
use crate::domain::{CrawlError, CrawlerConfig};
pub fn create_rate_limited_client(delay_ms: u64) -> Result<Client> {
let pool_size = std::cmp::max(3, num_cpus::get() - 1);
let client = Client::builder()
.pool_max_idle_per_host(pool_size)
.pool_idle_timeout(Duration::from_secs(60))
.timeout(Duration::from_secs(30))
.connect_timeout(Duration::from_secs(10))
.gzip(true)
.brotli(true)
.user_agent("rust-scraper/0.3.0 (Web Crawler)")
.build()?;
debug!(
"Created rate-limited HTTP client with pool_size={} delay_ms={}",
pool_size, delay_ms
);
Ok(client)
}
pub async fn fetch_url(url: &str, config: &CrawlerConfig) -> Result<String, CrawlError> {
debug!("Fetching URL: {}", url);
let client = create_rate_limited_client(config.delay_ms)
.map_err(|e| CrawlError::Internal(format!("Failed to create HTTP client: {}", e)))?;
let response = client
.get(url)
.timeout(Duration::from_secs(config.timeout_secs))
.send()
.await
.map_err(|e| CrawlError::Network {
message: e.to_string(),
status_code: e.status().map(|s| s.as_u16()),
})?;
if !response.status().is_success() {
return Err(CrawlError::Network {
message: format!("HTTP error: {}", response.status()),
status_code: Some(response.status().as_u16()),
});
}
let text = response.text().await.map_err(|e| CrawlError::Network {
message: e.to_string(),
status_code: None,
})?;
Ok(text)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_rate_limited_client() {
let client = create_rate_limited_client(500);
assert!(client.is_ok());
}
#[test]
fn test_create_rate_limited_client_zero_delay() {
let client = create_rate_limited_client(0);
assert!(client.is_ok());
}
}