mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
refactor(rust): resolve the http client config through From and TryFrom
HttpClientConfig::resolve becomes From<&HttpSettings> for Resolution and client_builder becomes TryFrom<&HttpClientConfig> for reqwest::ClientBuilder, matching the rustls conversion. The verify decision moves into From<&HttpSettings> for Verify, and the proxy environment rule moves next to its flags as HttpSettings::trusts_proxy_env. The curve and cipher results are read with transpose and a default selection, which removes the tuple destructuring
This commit is contained in:
parent
ffbfe7205f
commit
80dbb2a28a
8 changed files with 78 additions and 72 deletions
|
|
@ -6,7 +6,7 @@ use litellm_host::{
|
|||
host::{Host, HostOp, HostResult},
|
||||
machine::{HostFailure, Machine, MachineStep},
|
||||
};
|
||||
use litellm_http::{HttpClientConfig, HttpClientPool, HttpSettings};
|
||||
use litellm_http::{HttpClientPool, HttpSettings, Resolution};
|
||||
use litellm_llms::{
|
||||
base_llm::ocr::{
|
||||
error::Error as OcrError,
|
||||
|
|
@ -184,7 +184,7 @@ async fn ocr_client_uses_the_injected_http_pool_configuration() {
|
|||
};
|
||||
let client = OcrClient::new(
|
||||
&HttpClientPool::new(Arc::new(PublicDnsResolver)),
|
||||
&HttpClientConfig::resolve(&settings).config,
|
||||
&Resolution::from(&settings).config,
|
||||
UrlPolicy::default(),
|
||||
VertexAuth::default(),
|
||||
)
|
||||
|
|
|
|||
1
litellm-rust/crates/http/AGENTS.md
Normal file
1
litellm-rust/crates/http/AGENTS.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
- https://github.com/BerriAI/litellm-docs/blob/main/docs/guides/security_settings.md
|
||||
|
|
@ -38,84 +38,80 @@ pub struct Resolution {
|
|||
pub unsupported: Vec<Unsupported>,
|
||||
}
|
||||
|
||||
impl HttpClientConfig {
|
||||
pub fn resolve(settings: &HttpSettings) -> Resolution {
|
||||
let (key_exchange_group, unsupported_curve) = match settings
|
||||
.ssl_ecdh_curve
|
||||
.as_deref()
|
||||
.map(str::parse::<KeyExchangeGroup>)
|
||||
{
|
||||
None => (None, None),
|
||||
Some(Ok(group)) => (Some(group), None),
|
||||
Some(Err(unsupported)) => (None, Some(unsupported)),
|
||||
};
|
||||
let ciphers = settings
|
||||
.ssl_security_level
|
||||
.as_deref()
|
||||
.map(CipherSelection::from);
|
||||
let verify = match &settings.ssl_verify {
|
||||
Some(SslVerify::Disabled) => Verify::Disabled,
|
||||
Some(SslVerify::CaBundle(path)) => Verify::CaBundle(path.clone()),
|
||||
impl From<&HttpSettings> for Verify {
|
||||
fn from(settings: &HttpSettings) -> Self {
|
||||
match &settings.ssl_verify {
|
||||
Some(SslVerify::Disabled) => Self::Disabled,
|
||||
Some(SslVerify::CaBundle(path)) => Self::CaBundle(path.clone()),
|
||||
Some(SslVerify::Enabled) | None => settings
|
||||
.ssl_cert_file
|
||||
.clone()
|
||||
.map_or(Verify::BuiltInRoots, Verify::CaBundle),
|
||||
};
|
||||
let (tls12_cipher_suites, unsupported_ciphers) = ciphers
|
||||
.map_or((None, Vec::new()), |ciphers| {
|
||||
(ciphers.tls12_cipher_suites, ciphers.unsupported)
|
||||
});
|
||||
Resolution {
|
||||
config: Self {
|
||||
verify,
|
||||
.map_or(Self::BuiltInRoots, Self::CaBundle),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&HttpSettings> for Resolution {
|
||||
fn from(settings: &HttpSettings) -> Self {
|
||||
let curve = settings
|
||||
.ssl_ecdh_curve
|
||||
.as_deref()
|
||||
.map(str::parse::<KeyExchangeGroup>)
|
||||
.transpose();
|
||||
let ciphers = settings
|
||||
.ssl_security_level
|
||||
.as_deref()
|
||||
.map(CipherSelection::from)
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
config: HttpClientConfig {
|
||||
verify: Verify::from(settings),
|
||||
client_certificate: settings.ssl_certificate.clone(),
|
||||
key_exchange_group,
|
||||
tls12_cipher_suites,
|
||||
key_exchange_group: curve.clone().ok().flatten(),
|
||||
tls12_cipher_suites: ciphers.tls12_cipher_suites,
|
||||
force_ipv4: settings.force_ipv4,
|
||||
http2: settings.http2,
|
||||
user_agent: settings.user_agent.clone(),
|
||||
trust_proxy_env: !settings.ignore_proxy_env
|
||||
|| settings.trust_proxy_env
|
||||
|| settings.http2
|
||||
|| settings.httpx_transport,
|
||||
trust_proxy_env: settings.trusts_proxy_env(),
|
||||
connect_timeout: settings.connect_timeout,
|
||||
tcp_keepalive: settings.tcp_keepalive,
|
||||
pool_idle_timeout: settings.pool_idle_timeout,
|
||||
},
|
||||
unsupported: unsupported_curve
|
||||
.into_iter()
|
||||
.chain(unsupported_ciphers)
|
||||
.collect(),
|
||||
unsupported: curve.err().into_iter().chain(ciphers.unsupported).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn client_builder(&self) -> Result<reqwest::ClientBuilder, Error> {
|
||||
impl TryFrom<&HttpClientConfig> for reqwest::ClientBuilder {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(config: &HttpClientConfig) -> Result<Self, Self::Error> {
|
||||
let base = reqwest::Client::builder()
|
||||
.use_preconfigured_tls(rustls::ClientConfig::try_from(self)?)
|
||||
.connect_timeout(self.connect_timeout)
|
||||
.pool_idle_timeout(self.pool_idle_timeout);
|
||||
let with_keepalive = match self.tcp_keepalive {
|
||||
.use_preconfigured_tls(rustls::ClientConfig::try_from(config)?)
|
||||
.connect_timeout(config.connect_timeout)
|
||||
.pool_idle_timeout(config.pool_idle_timeout);
|
||||
let with_keepalive = match config.tcp_keepalive {
|
||||
None => base,
|
||||
Some(keepalive) => base
|
||||
.tcp_keepalive(keepalive.idle)
|
||||
.tcp_keepalive_interval(keepalive.interval)
|
||||
.tcp_keepalive_retries(keepalive.retries),
|
||||
};
|
||||
let with_address = if self.force_ipv4 {
|
||||
let with_address = if config.force_ipv4 {
|
||||
with_keepalive.local_address(IpAddr::V4(Ipv4Addr::UNSPECIFIED))
|
||||
} else {
|
||||
with_keepalive
|
||||
};
|
||||
let with_protocol = if self.http2 {
|
||||
let with_protocol = if config.http2 {
|
||||
with_address
|
||||
} else {
|
||||
with_address.http1_only()
|
||||
};
|
||||
let with_agent = match &self.user_agent {
|
||||
let with_agent = match &config.user_agent {
|
||||
Some(agent) => with_protocol.user_agent(agent),
|
||||
None => with_protocol,
|
||||
};
|
||||
Ok(if self.trust_proxy_env {
|
||||
Ok(if config.trust_proxy_env {
|
||||
with_agent
|
||||
} else {
|
||||
with_agent.no_proxy()
|
||||
|
|
@ -161,7 +157,7 @@ mod tests {
|
|||
#[case] settings: HttpSettings,
|
||||
#[case] expected: Verify,
|
||||
) {
|
||||
let config = HttpClientConfig::resolve(&settings).config;
|
||||
let config = Resolution::from(&settings).config;
|
||||
assert_eq!(config.verify, expected);
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +168,7 @@ mod tests {
|
|||
..HttpSettings::default()
|
||||
}
|
||||
.with_environment(&|name: &str| (name == "SSL_VERIFY").then(|| "true".to_string()));
|
||||
let config = HttpClientConfig::resolve(&settings).config;
|
||||
let config = Resolution::from(&settings).config;
|
||||
assert_eq!(config.verify, Verify::BuiltInRoots);
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +184,7 @@ mod tests {
|
|||
ssl_ecdh_curve: Some(curve.into()),
|
||||
..HttpSettings::default()
|
||||
};
|
||||
let resolution = HttpClientConfig::resolve(&settings);
|
||||
let resolution = Resolution::from(&settings);
|
||||
assert_eq!(resolution.config.key_exchange_group, expected);
|
||||
assert_eq!(resolution.unsupported, []);
|
||||
}
|
||||
|
|
@ -199,7 +195,7 @@ mod tests {
|
|||
ssl_ecdh_curve: Some("secp521r1".into()),
|
||||
..HttpSettings::default()
|
||||
};
|
||||
let resolution = HttpClientConfig::resolve(&settings);
|
||||
let resolution = Resolution::from(&settings);
|
||||
assert_eq!(resolution.config.key_exchange_group, None);
|
||||
assert_eq!(
|
||||
resolution.unsupported,
|
||||
|
|
@ -213,7 +209,7 @@ mod tests {
|
|||
ssl_security_level: Some("DEFAULT@SECLEVEL=1".into()),
|
||||
..HttpSettings::default()
|
||||
};
|
||||
let resolution = HttpClientConfig::resolve(&settings);
|
||||
let resolution = Resolution::from(&settings);
|
||||
assert_eq!(resolution.config.tls12_cipher_suites, None);
|
||||
assert_eq!(
|
||||
resolution.unsupported,
|
||||
|
|
@ -230,7 +226,7 @@ mod tests {
|
|||
),
|
||||
..HttpSettings::default()
|
||||
};
|
||||
let resolution = HttpClientConfig::resolve(&settings);
|
||||
let resolution = Resolution::from(&settings);
|
||||
assert_eq!(
|
||||
resolution.config.tls12_cipher_suites,
|
||||
Some(vec![
|
||||
|
|
@ -265,7 +261,7 @@ mod tests {
|
|||
pool_idle_timeout: Duration::from_secs(45),
|
||||
..HttpSettings::default()
|
||||
};
|
||||
let config = HttpClientConfig::resolve(&settings).config;
|
||||
let config = Resolution::from(&settings).config;
|
||||
assert_eq!(
|
||||
config,
|
||||
HttpClientConfig {
|
||||
|
|
@ -303,7 +299,7 @@ mod tests {
|
|||
#[case] settings: HttpSettings,
|
||||
#[case] expected: bool,
|
||||
) {
|
||||
let config = HttpClientConfig::resolve(&settings).config;
|
||||
let config = Resolution::from(&settings).config;
|
||||
assert_eq!(config.trust_proxy_env, expected);
|
||||
}
|
||||
|
||||
|
|
@ -312,10 +308,10 @@ mod tests {
|
|||
let path = std::env::temp_dir().join("litellm-http-missing-bundle.pem");
|
||||
let config = HttpClientConfig {
|
||||
verify: Verify::CaBundle(path.clone()),
|
||||
..HttpClientConfig::resolve(&HttpSettings::default()).config
|
||||
..Resolution::from(&HttpSettings::default()).config
|
||||
};
|
||||
assert!(matches!(
|
||||
config.client_builder(),
|
||||
reqwest::ClientBuilder::try_from(&config),
|
||||
Err(Error::Read { path: reported, .. }) if reported == path
|
||||
));
|
||||
}
|
||||
|
|
@ -327,9 +323,9 @@ mod tests {
|
|||
std::fs::write(&path, b"not a certificate").unwrap();
|
||||
let config = HttpClientConfig {
|
||||
verify: Verify::CaBundle(path.clone()),
|
||||
..HttpClientConfig::resolve(&HttpSettings::default()).config
|
||||
..Resolution::from(&HttpSettings::default()).config
|
||||
};
|
||||
let result = config.client_builder().map(drop);
|
||||
let result = reqwest::ClientBuilder::try_from(&config).map(drop);
|
||||
std::fs::remove_file(&path).unwrap();
|
||||
assert!(matches!(
|
||||
result,
|
||||
|
|
|
|||
|
|
@ -67,7 +67,9 @@ impl HttpClientPool {
|
|||
{
|
||||
return Ok(pooled.client.clone());
|
||||
}
|
||||
let client = self.apply(variant, key.0.client_builder()?).build()?;
|
||||
let client = self
|
||||
.apply(variant, reqwest::ClientBuilder::try_from(&key.0)?)
|
||||
.build()?;
|
||||
self.lock().insert(
|
||||
key,
|
||||
PooledClient {
|
||||
|
|
@ -114,7 +116,7 @@ mod tests {
|
|||
};
|
||||
|
||||
use super::*;
|
||||
use crate::{HttpSettings, Verify};
|
||||
use crate::{HttpSettings, Resolution, Verify};
|
||||
|
||||
struct FixedResolver(SocketAddr);
|
||||
|
||||
|
|
@ -132,7 +134,7 @@ mod tests {
|
|||
fn config(user_agent: &str) -> HttpClientConfig {
|
||||
HttpClientConfig {
|
||||
user_agent: Some(user_agent.into()),
|
||||
..HttpClientConfig::resolve(&HttpSettings::default()).config
|
||||
..Resolution::from(&HttpSettings::default()).config
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,10 @@ impl HttpSettings {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn trusts_proxy_env(&self) -> bool {
|
||||
!self.ignore_proxy_env || self.trust_proxy_env || self.http2 || self.httpx_transport
|
||||
}
|
||||
|
||||
pub fn without_missing_files(self, exists: &dyn Fn(&Path) -> bool) -> Self {
|
||||
Self {
|
||||
ssl_verify: match self.ssl_verify {
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ pub enum Unsupported {
|
|||
CipherToken(String),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct CipherSelection {
|
||||
pub(crate) tls12_cipher_suites: Option<Vec<Tls12CipherSuite>>,
|
||||
pub(crate) unsupported: Vec<Unsupported>,
|
||||
|
|
@ -304,10 +305,10 @@ mod tests {
|
|||
use rustls::NamedGroup;
|
||||
|
||||
use super::*;
|
||||
use crate::HttpSettings;
|
||||
use crate::{HttpSettings, Resolution};
|
||||
|
||||
fn config(settings: HttpSettings) -> HttpClientConfig {
|
||||
HttpClientConfig::resolve(&settings).config
|
||||
Resolution::from(&settings).config
|
||||
}
|
||||
|
||||
fn offered_groups(tls: &ClientConfig) -> Vec<NamedGroup> {
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@ impl Resolve for PublicDnsResolver {
|
|||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use litellm_http::HttpSettings;
|
||||
use litellm_http::{HttpSettings, Resolution};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::TcpListener,
|
||||
|
|
@ -445,7 +445,7 @@ mod tests {
|
|||
) -> MediaFetcher {
|
||||
let direct = HttpClientConfig {
|
||||
trust_proxy_env: false,
|
||||
..HttpClientConfig::resolve(&HttpSettings::default()).config
|
||||
..Resolution::from(&HttpSettings::default()).config
|
||||
};
|
||||
MediaFetcher::with_resolution(
|
||||
&HttpClientPool::new(Arc::new(LoopbackDnsResolver(pinned_address))),
|
||||
|
|
@ -636,7 +636,7 @@ mod tests {
|
|||
async fn rejects_url_credentials_before_network_access() {
|
||||
let fetcher = MediaFetcher::new(
|
||||
&HttpClientPool::new(Arc::new(PublicDnsResolver)),
|
||||
&HttpClientConfig::resolve(&HttpSettings::default()).config,
|
||||
&Resolution::from(&HttpSettings::default()).config,
|
||||
UrlPolicy::default(),
|
||||
)
|
||||
.expect("media fetcher builds");
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use std::{
|
|||
sync::{Arc, LazyLock, Mutex, PoisonError},
|
||||
};
|
||||
|
||||
use litellm_http::{HttpClientConfig, HttpClientPool, HttpSettings, SslVerify, Unsupported};
|
||||
use litellm_http::{
|
||||
HttpClientConfig, HttpClientPool, HttpSettings, Resolution, SslVerify, Unsupported,
|
||||
};
|
||||
use litellm_llms::custom_httpx::media::{PublicDnsResolver, UrlPolicy};
|
||||
use pyo3::{prelude::*, types::PyDict};
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ pub(crate) fn call_config(
|
|||
.with_environment(&|name| std::env::var(name).ok());
|
||||
let settings = for_call(configured, call_ssl_verify(kwargs)?, asynchronous)
|
||||
.without_missing_files(&|path: &Path| path.exists());
|
||||
let resolution = HttpClientConfig::resolve(&settings);
|
||||
let resolution = Resolution::from(&settings);
|
||||
for unsupported in unreported(&REPORTED_UNSUPPORTED, resolution.unsupported) {
|
||||
PythonSettings::warn(py, &unsupported.to_string())?;
|
||||
}
|
||||
|
|
@ -251,7 +253,7 @@ user_agent='litellm/9.9.9',
|
|||
Python::initialize();
|
||||
Python::attach(|py| {
|
||||
let settings = settings(&python_settings(py, overrides)).unwrap();
|
||||
let config = HttpClientConfig::resolve(&settings).config;
|
||||
let config = Resolution::from(&settings).config;
|
||||
assert_eq!(config.verify, expected);
|
||||
});
|
||||
}
|
||||
|
|
@ -345,7 +347,7 @@ user_agent='litellm/9.9.9',
|
|||
..HttpSettings::default()
|
||||
};
|
||||
let settings = for_call(opted_out, None, asynchronous);
|
||||
let config = HttpClientConfig::resolve(&settings).config;
|
||||
let config = Resolution::from(&settings).config;
|
||||
assert_eq!(config.trust_proxy_env, expected);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue