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
52#[allow(clippy::panic)]
59fn build_client<F>(configure: F) -> Client
60where
61 F: Fn(ClientBuilder) -> ClientBuilder,
62{
63 try_build_client(configure).unwrap_or_else(|e| {
64 panic!(
65 "failed to build HTTP client: {e}. \
66 This usually indicates a TLS configuration issue or system resource exhaustion. \
67 Ensure the system has valid TLS certificates and sufficient resources."
68 )
69 })
70}
71
72pub fn create_default_client() -> Client {
74 create_client_with_timeout(DEFAULT_TIMEOUT)
75}
76
77pub fn create_client_with_timeout(timeout: Duration) -> Client {
79 build_client(|builder| builder.timeout(timeout).connect_timeout(SHORT_TIMEOUT))
80}
81
82pub fn create_client_with_timeouts(connect_timeout: Duration, request_timeout: Duration) -> Client {
84 build_client(|builder| {
85 builder
86 .timeout(request_timeout)
87 .connect_timeout(connect_timeout)
88 })
89}
90
91pub fn create_client_with_user_agent(user_agent: &str) -> Client {
93 build_client(|builder| builder.user_agent(user_agent).timeout(DEFAULT_TIMEOUT))
94}
95
96pub fn create_streaming_client() -> Client {
98 build_client(|builder| {
99 builder
100 .connect_timeout(SHORT_TIMEOUT)
101 .tcp_keepalive(Some(Duration::from_secs(60)))
102 })
103}
104
105pub fn get_or_create_default_client(existing: Option<Client>) -> Client {
107 existing.unwrap_or_else(create_default_client)
108}