use crate::config::Config;
use reqwest::{Client, ClientBuilder};
use std::time::Duration;
pub fn build_client(config: &Config) -> crate::error::Result<Client> {
let timeout_secs = if config.llm_mode { 60 } else { 30 };
let client = ClientBuilder::new()
.danger_accept_invalid_certs(true)
.redirect(reqwest::redirect::Policy::limited(10))
.timeout(Duration::from_secs(timeout_secs))
.connect_timeout(Duration::from_secs(10))
.user_agent(&config.user_agent)
.cookie_store(true)
.pool_max_idle_per_host(config.concurrency)
.pool_idle_timeout(Duration::from_secs(90))
.gzip(true)
.http2_adaptive_window(true) .http2_initial_stream_window_size(Some(2 * 1024 * 1024)) .http2_initial_connection_window_size(Some(4 * 1024 * 1024)) .http2_max_frame_size(Some(16384)) .http2_keep_alive_interval(Some(Duration::from_secs(20))) .http2_keep_alive_timeout(Duration::from_secs(10)) .http2_keep_alive_while_idle(true) .build()?;
Ok(client)
}
pub async fn send_request(
client: &Client,
url: &str,
query_param: Option<(&str, &str)>,
) -> crate::error::Result<HttpResponse> {
let max_retries = 3;
for retry_count in 0..=max_retries {
let mut request = client.get(url);
if let Some((key, value)) = query_param {
request = request.query(&[(key, value)]);
}
let start = std::time::Instant::now();
let response = request.send().await?;
let duration = start.elapsed();
let status = response.status().as_u16();
if status == 429 && retry_count < max_retries {
let backoff_ms = 1000 * 2_u64.pow(retry_count as u32); tracing::warn!("Rate limited (429), retrying in {}ms (attempt {}/{})",
backoff_ms, retry_count + 1, max_retries);
tokio::time::sleep(Duration::from_millis(backoff_ms)).await;
continue;
}
let http_version = format!("{:?}", response.version());
let headers = response
.headers()
.iter()
.map(|(k, v)| {
(
k.as_str().to_lowercase(),
v.to_str().unwrap_or("").to_string(),
)
})
.collect();
let cookies: Vec<String> = response.cookies().map(|c| c.name().to_string()).collect();
let body = response.text().await.unwrap_or_default();
return Ok(HttpResponse {
status_code: status,
headers,
body,
cookies,
response_time_ms: duration.as_millis() as u64,
http_version,
});
}
unreachable!("Retry loop should always return")
}
#[derive(Debug, Clone)]
pub struct HttpResponse {
pub status_code: u16,
pub headers: std::collections::HashMap<String, String>,
pub body: String,
pub cookies: Vec<String>,
pub response_time_ms: u64,
pub http_version: String,
}
pub async fn send_post_request(
client: &Client,
url: &str,
headers: Option<Vec<(String, String)>>,
body: Option<String>,
) -> crate::error::Result<HttpResponse> {
let mut request = client.post(url);
if let Some(hdrs) = headers {
for (key, value) in hdrs {
request = request.header(&key, &value);
}
}
if let Some(body_content) = body {
request = request.body(body_content);
}
let start = std::time::Instant::now();
let response = request.send().await?;
let duration = start.elapsed();
let status = response.status().as_u16();
let http_version = format!("{:?}", response.version());
let headers = response
.headers()
.iter()
.map(|(k, v)| {
(
k.as_str().to_lowercase(),
v.to_str().unwrap_or("").to_string(),
)
})
.collect();
let cookies: Vec<String> = response.cookies().map(|c| c.name().to_string()).collect();
let body = response.text().await.unwrap_or_default();
Ok(HttpResponse {
status_code: status,
headers,
body,
cookies,
response_time_ms: duration.as_millis() as u64,
http_version,
})
}
pub async fn send_custom_request(
client: &Client,
method: &str,
url: &str,
headers: Option<Vec<(String, String)>>,
) -> crate::error::Result<HttpResponse> {
let http_method =
reqwest::Method::from_bytes(method.as_bytes()).unwrap_or(reqwest::Method::GET);
let mut request = client.request(http_method, url);
if let Some(hdrs) = headers {
for (key, value) in hdrs {
request = request.header(&key, &value);
}
}
let start = std::time::Instant::now();
let response = request.send().await?;
let duration = start.elapsed();
let status = response.status().as_u16();
let http_version = format!("{:?}", response.version());
let headers = response
.headers()
.iter()
.map(|(k, v)| {
(
k.as_str().to_lowercase(),
v.to_str().unwrap_or("").to_string(),
)
})
.collect();
let cookies: Vec<String> = response.cookies().map(|c| c.name().to_string()).collect();
let body = response.text().await.unwrap_or_default();
Ok(HttpResponse {
status_code: status,
headers,
body,
cookies,
response_time_ms: duration.as_millis() as u64,
http_version,
})
}