use crate::errors::{Error, Result};
#[cfg(not(target_arch = "wasm32"))]
use std::env;
#[cfg(all(
not(target_arch = "wasm32"),
not(any(feature = "tls-native-roots", feature = "tls-webpki-roots"))
))]
compile_error!(
"Either feature \"tls-native-roots\" or \"tls-webpki-roots\" must be enabled for HTTP client support."
);
static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
#[cfg(not(target_arch = "wasm32"))]
pub(super) fn client() -> Result<reqwest::Client> {
use std::time::Duration;
let client = reqwest::Client::builder()
.use_rustls_tls()
.user_agent(USER_AGENT)
.connect_timeout(Duration::from_secs(10))
.read_timeout(Duration::from_secs(60));
let client = configure_proxy(client)?;
let client = client.tls_built_in_root_certs(false);
#[cfg(feature = "tls-native-roots")]
let client = client.tls_built_in_native_certs(true);
#[cfg(all(feature = "tls-webpki-roots", not(feature = "tls-native-roots")))]
let client = client.tls_built_in_webpki_certs(true);
Ok(client.build()?)
}
#[cfg(not(target_arch = "wasm32"))]
fn configure_proxy(mut client: reqwest::ClientBuilder) -> Result<reqwest::ClientBuilder> {
if let Ok(proxy_url) = env::var("HTTP_PROXY").or_else(|_| env::var("http_proxy")) {
match reqwest::Proxy::http(&proxy_url) {
Ok(proxy) => {
client = client.proxy(proxy);
}
Err(e) => {
return Err(Error::Server(format!(
"Invalid HTTP_PROXY '{proxy_url}': {e}"
)));
}
}
}
if let Ok(proxy_url) = env::var("HTTPS_PROXY").or_else(|_| env::var("https_proxy")) {
match reqwest::Proxy::https(&proxy_url) {
Ok(proxy) => {
client = client.proxy(proxy);
}
Err(e) => {
return Err(Error::Server(format!(
"Invalid HTTPS_PROXY '{proxy_url}': {e}"
)));
}
}
}
Ok(client)
}
#[cfg(target_arch = "wasm32")]
pub(super) fn client() -> Result<reqwest::Client> {
let client = reqwest::Client::builder().user_agent(USER_AGENT);
Ok(client.build()?)
}
#[cfg(test)]
mod tests {
use super::client;
#[cfg(not(target_arch = "wasm32"))]
#[test]
fn test_client_proxy_configurations() {
let test_scenario = |setup: fn(), description: &str, should_succeed: bool| {
setup();
let client = client();
if should_succeed {
assert!(client.is_ok(), "{}", description);
} else {
assert!(client.is_err(), "{}", description);
}
std::env::remove_var("HTTP_PROXY");
std::env::remove_var("http_proxy");
std::env::remove_var("HTTPS_PROXY");
std::env::remove_var("https_proxy");
};
test_scenario(
|| {
std::env::remove_var("HTTP_PROXY");
std::env::remove_var("http_proxy");
std::env::remove_var("HTTPS_PROXY");
std::env::remove_var("https_proxy");
},
"Client should build without proxy settings",
true,
);
test_scenario(
|| {
std::env::set_var("HTTP_PROXY", "http://proxy.example.com:8080");
},
"Client should build with HTTP_PROXY set",
true,
);
test_scenario(
|| {
std::env::set_var("HTTPS_PROXY", "http://proxy.example.com:8443");
},
"Client should build with HTTPS_PROXY set",
true,
);
test_scenario(
|| {
std::env::set_var("HTTP_PROXY", "http://http-proxy.example.com:8080");
std::env::set_var("HTTPS_PROXY", "http://https-proxy.example.com:8443");
},
"Client should build with both proxies set",
true,
);
test_scenario(
|| {
std::env::set_var("http_proxy", "http://proxy.example.com:8080");
std::env::set_var("https_proxy", "http://proxy.example.com:8443");
},
"Client should build with lowercase proxy vars",
true,
);
test_scenario(
|| {
std::env::set_var("HTTPS_PROXY", "http://user:password@proxy.example.com:8443");
},
"Client should build with authenticated proxy",
true,
);
test_scenario(
|| {
std::env::set_var("HTTP_PROXY", "http://[invalid-bracket-usage");
},
"Client should fail with invalid proxy URL",
false,
);
test_scenario(
|| {
std::env::set_var("HTTP_PROXY", "http://uppercase.example.com:8080");
std::env::set_var("http_proxy", "http://lowercase.example.com:8080");
},
"Client should build with precedence rules",
true,
);
test_scenario(
|| {
std::env::set_var("HTTP_PROXY", "http://http-upper.example.com:8080");
std::env::set_var("https_proxy", "http://https-lower.example.com:8443");
},
"Client should build with mixed case proxy vars",
true,
);
test_scenario(
|| {
std::env::set_var("HTTP_PROXY", "");
},
"Client should fail with empty proxy value",
false,
);
test_scenario(
|| {
std::env::set_var("HTTPS_PROXY", "socks5://proxy.example.com:1080");
},
"Client should build with SOCKS proxy",
true,
);
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen_test::wasm_bindgen_test]
fn test_client_wasm() {
let client = client();
assert!(client.is_ok(), "WASM client should build");
}
}