Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 45 additions & 2 deletions crates/uv-client/src/base_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use http::{
},
};
use itertools::Itertools;
use reqwest::{Client, ClientBuilder, IntoUrl, Proxy, Request, Response, multipart};
use reqwest::{Client, ClientBuilder, IntoUrl, NoProxy, Proxy, Request, Response, multipart};
use reqwest_middleware::{ClientWithMiddleware, Middleware};
use reqwest_retry::policies::ExponentialBackoff;
use reqwest_retry::{
Expand All @@ -29,7 +29,8 @@ use url::ParseError;
use url::Url;

use uv_auth::{AuthMiddleware, Credentials, CredentialsCache, Indexes, PyxTokenStore};
use uv_configuration::{KeyringProviderType, TrustedHost};
use uv_configuration::ProxyUrlKind;
use uv_configuration::{KeyringProviderType, ProxyUrl, TrustedHost};
use uv_fs::Simplified;
use uv_pep508::MarkerEnvironment;
use uv_platform_tags::Platform;
Expand Down Expand Up @@ -84,6 +85,9 @@ pub struct BaseClientBuilder<'a> {
timeout: Duration,
extra_middleware: Option<ExtraMiddleware>,
proxies: Vec<Proxy>,
http_proxy: Option<ProxyUrl>,
https_proxy: Option<ProxyUrl>,
no_proxy: Option<Vec<String>>,
redirect_policy: RedirectPolicy,
/// Whether credentials should be propagated during cross-origin redirects.
///
Expand Down Expand Up @@ -148,6 +152,9 @@ impl Default for BaseClientBuilder<'_> {
timeout: Duration::from_secs(30),
extra_middleware: None,
proxies: vec![],
http_proxy: None,
https_proxy: None,
no_proxy: None,
redirect_policy: RedirectPolicy::default(),
cross_origin_credential_policy: CrossOriginCredentialsPolicy::Secure,
custom_client: None,
Expand Down Expand Up @@ -265,6 +272,24 @@ impl<'a> BaseClientBuilder<'a> {
self
}

#[must_use]
pub fn http_proxy(mut self, http_proxy: Option<ProxyUrl>) -> Self {
self.http_proxy = http_proxy;
self
}

#[must_use]
pub fn https_proxy(mut self, https_proxy: Option<ProxyUrl>) -> Self {
self.https_proxy = https_proxy;
self
}

#[must_use]
pub fn no_proxy(mut self, no_proxy: Option<Vec<String>>) -> Self {
self.no_proxy = no_proxy;
self
}

#[must_use]
pub fn redirect(mut self, policy: RedirectPolicy) -> Self {
self.redirect_policy = policy;
Expand Down Expand Up @@ -526,6 +551,24 @@ impl<'a> BaseClientBuilder<'a> {
for p in &self.proxies {
client_builder = client_builder.proxy(p.clone());
}

let no_proxy = self
.no_proxy
.as_ref()
.and_then(|no_proxy| NoProxy::from_string(&no_proxy.join(",")));

if let Some(http_proxy) = &self.http_proxy {
let proxy = http_proxy
.as_proxy(ProxyUrlKind::Http)
.no_proxy(no_proxy.clone());
client_builder = client_builder.proxy(proxy);
}

if let Some(https_proxy) = &self.https_proxy {
let proxy = https_proxy.as_proxy(ProxyUrlKind::Https).no_proxy(no_proxy);
client_builder = client_builder.proxy(proxy);
}

let client_builder = client_builder;

client_builder
Expand Down
1 change: 1 addition & 0 deletions crates/uv-client/tests/it/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod http_util;
mod proxy;
mod remote_metadata;
mod ssl_certs;
mod user_agent_version;
100 changes: 100 additions & 0 deletions crates/uv-client/tests/it/proxy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! An integration test for proxy support in `uv-client`.

use anyhow::Result;
use wiremock::matchers::{any, method};
use wiremock::{Mock, MockServer, ResponseTemplate};

use uv_client::BaseClientBuilder;
use uv_configuration::ProxyUrl;

#[tokio::test]
async fn http_proxy() -> Result<()> {
// Start a mock server to act as the target.
let target_server = MockServer::start().await;
Mock::given(method("GET"))
.respond_with(ResponseTemplate::new(200))
.mount(&target_server)
.await;

// Start a mock server to act as the proxy.
let proxy_server = MockServer::start().await;
Mock::given(any())
.respond_with(ResponseTemplate::new(200))
.mount(&proxy_server)
.await;

// Create a client with the proxy.
let client = BaseClientBuilder::new(
uv_client::Connectivity::Online,
false,
vec![],
uv_preview::Preview::default(),
std::time::Duration::from_secs(30),
3,
)
.http_proxy(Some(proxy_server.uri().parse::<ProxyUrl>()?))
.build();

// Make a request to the target.
let response = client
.for_host(&target_server.uri().parse()?)
.get(target_server.uri())
.send()
.await?;

assert_eq!(response.status(), 200);

// Assert that the proxy was called.
let received_requests = proxy_server.received_requests().await.unwrap();
assert_eq!(received_requests.len(), 1);

Ok(())
}

#[tokio::test]
async fn no_proxy() -> Result<()> {
// Start a mock server to act as the target.
let target_server = MockServer::start().await;
Mock::given(method("GET"))
.respond_with(ResponseTemplate::new(200))
.mount(&target_server)
.await;

// Start a mock server to act as the proxy.
let proxy_server = MockServer::start().await;
Mock::given(any())
.respond_with(ResponseTemplate::new(200))
.mount(&proxy_server)
.await;

// The host of the target server should be excluded from proxying.
let target_host = target_server.address().ip().to_string();

// Create a client with the proxy.
let client = BaseClientBuilder::new(
uv_client::Connectivity::Online,
false,
vec![],
uv_preview::Preview::default(),
std::time::Duration::from_secs(30),
3,
)
.http_proxy(Some(proxy_server.uri().parse::<ProxyUrl>()?))
.no_proxy(Some(vec![target_host]))
.build();

// Make a request to the target.
let response = client
.for_host(&target_server.uri().parse()?)
.get(target_server.uri())
.send()
.await?;

assert_eq!(response.status(), 200);

// Assert that the proxy was NOT called.
let received_requests = proxy_server.received_requests().await.unwrap();
assert_eq!(received_requests.len(), 0);

Ok(())
}
3 changes: 3 additions & 0 deletions crates/uv-configuration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ clap = { workspace = true, features = ["derive"], optional = true }
either = { workspace = true }
fs-err = { workspace = true }
rayon = { workspace = true }
reqwest = { workspace = true }
rustc-hash = { workspace = true }
same-file = { workspace = true }
schemars = { workspace = true, optional = true }
Expand All @@ -41,6 +42,8 @@ url = { workspace = true }

[dev-dependencies]
anyhow = { workspace = true }
insta = { workspace = true }
serde_json = { workspace = true }

[features]
default = []
2 changes: 2 additions & 0 deletions crates/uv-configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub use name_specifiers::*;
pub use overrides::*;
pub use package_options::*;
pub use project_build_backend::*;
pub use proxy_url::*;
pub use required_version::*;
pub use sources::*;
pub use target_triple::*;
Expand All @@ -40,6 +41,7 @@ mod name_specifiers;
mod overrides;
mod package_options;
mod project_build_backend;
mod proxy_url;
mod required_version;
mod sources;
mod target_triple;
Expand Down
Loading
Loading