use reqwest::{
header::{HeaderMap, HeaderValue},
Client,
};
use serde::{Deserialize, Serialize};
use tracing::info;
use crate::{
error::{
result::{HttpClientResult, HttpClientResultHelper},
HttpClientError,
},
http_client_error,
tls::build_tls_client,
Oauth2LoginConfig, ProxyParams,
};
#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)]
pub struct HttpClientConfig {
#[serde(default)]
#[serde(skip_serializing_if = "not")]
pub accept_invalid_certs: bool,
pub server_url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub verified_cert: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub access_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_client_pkcs12_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_client_pkcs12_password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_client_pem_cert_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_client_pem_key_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub database_secret: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub oauth2_conf: Option<Oauth2LoginConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub proxy_params: Option<ProxyParams>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cipher_suites: Option<String>,
}
impl Default for HttpClientConfig {
fn default() -> Self {
Self {
accept_invalid_certs: false,
server_url: "http://127.0.0.1:9998".to_owned(),
verified_cert: None,
access_token: None,
database_secret: None,
ssl_client_pkcs12_path: None,
ssl_client_pkcs12_password: None,
ssl_client_pem_cert_path: None,
ssl_client_pem_key_path: None,
oauth2_conf: None,
proxy_params: None,
cipher_suites: None,
}
}
}
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn not(b: &bool) -> bool {
!*b
}
#[derive(Clone, Debug)]
pub struct HttpClient {
pub server_url: String,
pub client: Client,
}
impl HttpClient {
pub fn instantiate(http_conf: &HttpClientConfig) -> Result<Self, HttpClientError> {
let pem_cert_set = http_conf.ssl_client_pem_cert_path.is_some();
let pem_key_set = http_conf.ssl_client_pem_key_path.is_some();
let pkcs12_set = http_conf.ssl_client_pkcs12_path.is_some();
let pkcs12_pwd_set = http_conf.ssl_client_pkcs12_password.is_some();
if (pem_cert_set || pem_key_set) && (pkcs12_set || pkcs12_pwd_set) {
return Err(HttpClientError::Default(
"Invalid configuration: cannot use both PKCS#12 and PEM client authentication"
.to_owned(),
));
}
if pem_cert_set ^ pem_key_set {
return Err(HttpClientError::Default(
"Invalid configuration: both PEM certificate and key paths must be provided"
.to_owned(),
));
}
if pkcs12_set && !pkcs12_pwd_set {
return Err(HttpClientError::Default(
"Invalid configuration: PKCS#12 password must be provided with PKCS#12 path"
.to_owned(),
));
}
let server_url = http_conf.server_url.strip_suffix('/').map_or_else(
|| http_conf.server_url.clone(),
std::string::ToString::to_string,
);
info!("Using server URL: {}", server_url);
let mut headers = HeaderMap::new();
if let Some(bearer_token) = http_conf.access_token.clone() {
headers.insert(
"Authorization",
HeaderValue::from_str(format!("Bearer {bearer_token}").as_str())?,
);
}
if let Some(database_secret) = http_conf.database_secret.clone() {
headers.insert("DatabaseSecret", HeaderValue::from_str(&database_secret)?);
}
let mut builder = build_tls_client(http_conf)?;
if let Some(proxy_params) = &http_conf.proxy_params {
builder = configure_proxy(builder, proxy_params)?;
}
Ok(Self {
server_url,
client: builder
.default_headers(headers)
.build()
.context("Reqwest client builder")?,
})
}
}
fn configure_proxy(
mut client_builder: reqwest::ClientBuilder,
proxy_params: &ProxyParams,
) -> HttpClientResult<reqwest::ClientBuilder> {
let mut proxy = reqwest::Proxy::all(proxy_params.url.clone()).map_err(|e| {
http_client_error!("Failed to configure the HTTPS proxy for HTTP client: {e}")
})?;
if let Some(ref username) = proxy_params.basic_auth_username {
if let Some(ref password) = proxy_params.basic_auth_password {
proxy = proxy.basic_auth(username, password);
}
} else if let Some(custom_auth_header) = &proxy_params.custom_auth_header {
proxy = proxy.custom_http_auth(HeaderValue::from_str(custom_auth_header).map_err(|e| {
http_client_error!("Failed to set custom HTTP auth header for HTTP client: {e}")
})?);
}
if !proxy_params.exclusion_list.is_empty() {
proxy = proxy.no_proxy(reqwest::NoProxy::from_string(
&proxy_params.exclusion_list.join(","),
));
}
info!("Overriding reqwest builder with proxy: {:?}", proxy);
client_builder = client_builder.proxy(proxy);
Ok(client_builder)
}