1use reqwest::{Client, ClientBuilder};
4use std::time::Duration;
5
6pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
7pub const SHORT_TIMEOUT: Duration = Duration::from_secs(5);
8pub const LONG_TIMEOUT: Duration = Duration::from_secs(300);
9
10fn apply_platform_proxy_policy(builder: ClientBuilder) -> ClientBuilder {
11 #[cfg(target_os = "macos")]
12 {
13 builder.no_proxy()
15 }
16 #[cfg(not(target_os = "macos"))]
17 {
18 builder
19 }
20}
21
22pub fn try_build_client<F>(configure: F) -> Result<Client, reqwest::Error>
27where
28 F: Fn(ClientBuilder) -> ClientBuilder,
29{
30 let primary_builder = configure(apply_platform_proxy_policy(ClientBuilder::new()));
31 match primary_builder.build() {
32 Ok(client) => Ok(client),
33 Err(primary_err) => {
34 let fallback_builder = apply_platform_proxy_policy(ClientBuilder::new())
35 .timeout(DEFAULT_TIMEOUT)
36 .connect_timeout(SHORT_TIMEOUT);
37 match fallback_builder.build() {
38 Ok(client) => Ok(client),
39 Err(fallback_err) => {
40 tracing::error!(
41 primary_error = %primary_err,
42 fallback_error = %fallback_err,
43 "HTTP client creation failed with both primary and fallback configurations"
44 );
45 Err(fallback_err)
46 }
47 }
48 }
49 }
50}
51
52fn build_client<F>(configure: F) -> Client
60where
61 F: Fn(ClientBuilder) -> ClientBuilder,
62{
63 try_build_client(configure).unwrap_or_else(|e| {
64 tracing::error!(
65 error = %e,
66 "HTTP client build failed (primary + fallback); falling back to bare default client. \
67 This usually indicates a TLS configuration issue or system resource exhaustion."
68 );
69 Client::new()
70 })
71}
72
73pub fn create_default_client() -> Client {
75 create_client_with_timeout(DEFAULT_TIMEOUT)
76}
77
78pub fn create_client_with_timeout(timeout: Duration) -> Client {
80 build_client(|builder| builder.timeout(timeout).connect_timeout(SHORT_TIMEOUT))
81}
82
83pub fn create_client_with_timeouts(connect_timeout: Duration, request_timeout: Duration) -> Client {
85 build_client(|builder| builder.timeout(request_timeout).connect_timeout(connect_timeout))
86}
87
88pub fn create_client_with_user_agent(user_agent: &str) -> Client {
90 build_client(|builder| builder.user_agent(user_agent).timeout(DEFAULT_TIMEOUT))
91}
92
93pub fn create_streaming_client() -> Client {
95 build_client(|builder| {
96 builder
97 .connect_timeout(SHORT_TIMEOUT)
98 .tcp_keepalive(Some(Duration::from_secs(60)))
99 })
100}
101
102pub fn get_or_create_default_client(existing: Option<Client>) -> Client {
104 existing.unwrap_or_else(create_default_client)
105}