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
20 changes: 13 additions & 7 deletions backends/curl/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,21 @@ pub fn populate_request<S, C: EasyCallback, R: MimePartReader + Send + 'static>(
ProxyOptions::Default => {}
ProxyOptions::None => raw.as_mut().set_noproxy("*")?,
ProxyOptions::Custom {
proxy_url_for_http,
proxy_url_for_https,
http,
https,
proxy_bypass,
} => {
raw.as_mut().set_proxy(&**proxy_url_for_http)?;
if proxy_url_for_https.is_some() {
// TODO: curl doesn't have a separate option for HTTPS proxy.
raw.as_mut().set_http_proxy_tunnel(true)?;
raw.as_mut().set_suppress_connect_headers(true)?;
let is_https = url
.get(0..5)
.is_some_and(|proto| proto.eq_ignore_ascii_case("https"));
match (is_https, http, https) {
(false, Some(http), _) => raw.as_mut().set_proxy(&**http)?,
(true, _, Some(https)) => {
raw.as_mut().set_proxy(&**https)?;
raw.as_mut().set_http_proxy_tunnel(true)?;
raw.as_mut().set_suppress_connect_headers(true)?;
}
_ => {}
}

if let Some(proxy_bypass) = proxy_bypass {
Expand Down
68 changes: 30 additions & 38 deletions backends/nsurlsession/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::sync::LazyLock;
use nyquest_interface::client::{CachingBehavior, ClientOptions, ProxyOptions};
use nyquest_interface::{Body, Error as NyquestError, Method, Request, Result as NyquestResult};
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::AllocAnyThread;
use objc2_foundation::{
ns_string, NSCharacterSet, NSCopying, NSData, NSDictionary, NSMutableArray,
Expand Down Expand Up @@ -37,48 +37,40 @@ impl NSUrlSessionClient {
config.setConnectionProxyDictionary(Some(&*NSDictionary::new()));
}
ProxyOptions::Custom {
proxy_url_for_http,
proxy_url_for_https,
http,
https,
proxy_bypass,
} => {
if let Some(http_proxy) = parse_proxy_host_port(&proxy_url_for_http) {
let dict = NSMutableDictionary::from_retained_objects(
&[
ns_string!("HTTPEnable"),
ns_string!("HTTPProxy"),
ns_string!("HTTPPort"),
],
&[
NSNumber::new_i32(1).into_super().into_super(),
http_proxy.0.into_super(),
http_proxy.1.into_super().into_super(),
],
);
let dict: Retained<NSMutableDictionary<NSString>> =
NSMutableDictionary::<_, AnyObject>::new();

if let Some(https_proxy) =
proxy_url_for_https.and_then(|url| parse_proxy_host_port(&url))
{
dict.insert(ns_string!("HTTPSEnable"), &NSNumber::new_i32(1));
dict.insert(ns_string!("HTTPSProxy"), &https_proxy.0);
dict.insert(ns_string!("HTTPSPort"), &https_proxy.1);
}
if let Some(http_proxy) = http.and_then(|url| parse_proxy_host_port(&url)) {
dict.insert(ns_string!("HTTPEnable"), &NSNumber::new_i32(1));
dict.insert(ns_string!("HTTPProxy"), &http_proxy.0);
dict.insert(ns_string!("HTTPPort"), &http_proxy.1);
}

if let Some(bypass) = proxy_bypass {
let bypass_list = NSMutableArray::from_retained_slice(
&bypass
.split([';', ','])
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|s| NSString::from_str(s))
.collect::<Vec<_>>(),
)
.copy();
dict.insert(ns_string!("ExceptionsList"), &bypass_list);
}
if let Some(https_proxy) = https.and_then(|url| parse_proxy_host_port(&url)) {
dict.insert(ns_string!("HTTPSEnable"), &NSNumber::new_i32(1));
dict.insert(ns_string!("HTTPSProxy"), &https_proxy.0);
dict.insert(ns_string!("HTTPSPort"), &https_proxy.1);
}

let dict = Retained::cast_unchecked::<NSDictionary>(dict.into_super());
config.setConnectionProxyDictionary(Some(&dict));
if let Some(bypass) = proxy_bypass {
let bypass_list = NSMutableArray::from_retained_slice(
&bypass
.split([';', ','])
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|s| NSString::from_str(s))
.collect::<Vec<_>>(),
)
.copy();
dict.insert(ns_string!("ExceptionsList"), &bypass_list);
}

let dict = Retained::cast_unchecked::<NSDictionary>(dict.into_super());
config.setConnectionProxyDictionary(Some(&dict));
}
}
if !options.use_cookies {
Expand Down Expand Up @@ -244,7 +236,7 @@ impl NSUrlSessionClient {
fn parse_proxy_host_port(proxy_url: &str) -> Option<(Retained<NSString>, Retained<NSNumber>)> {
let url = NSURLComponents::componentsWithString(&NSString::from_str(proxy_url))?;
let host = url.host()?;
let port = url.port()?;
let port = url.port().unwrap_or_else(|| NSNumber::new_u16(80));
Some((host, port))
}

Expand Down
8 changes: 4 additions & 4 deletions backends/reqwest/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,17 @@ fn build_non_wasm(
builder = builder.no_proxy();
}
ProxyOptions::Custom {
proxy_url_for_http,
proxy_url_for_https,
http,
https,
proxy_bypass,
} => {
use reqwest::{NoProxy, Proxy};

let no_proxy = proxy_bypass.as_deref().and_then(NoProxy::from_string);
if let Ok(proxy_http) = Proxy::http(&**proxy_url_for_http) {
if let Some(Ok(proxy_http)) = http.as_deref().map(Proxy::http) {
builder = builder.proxy(proxy_http.no_proxy(no_proxy.clone()));
}
if let Some(Ok(proxy_https)) = proxy_url_for_https.as_deref().map(Proxy::https) {
if let Some(Ok(proxy_https)) = https.as_deref().map(Proxy::https) {
builder = builder.proxy(proxy_https.no_proxy(no_proxy));
}
}
Expand Down
17 changes: 10 additions & 7 deletions backends/winhttp/src/handle/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl SessionHandle {
ProxyOptions::Default => unsafe {
WinHttpOpen(
user_agent_ptr,
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY,
std::ptr::null(),
std::ptr::null(),
flags,
Expand All @@ -57,14 +57,17 @@ impl SessionHandle {
)
},
ProxyOptions::Custom {
proxy_url_for_http,
proxy_url_for_https,
http,
https,
proxy_bypass,
} => {
let proxy_wide: Vec<u16> = if let Some(proxy_https) = proxy_url_for_https {
format!("http={proxy_url_for_http};https={proxy_https}")
} else {
format!("http={proxy_url_for_http}")
let proxy_wide: Vec<u16> = match (http, https) {
(Some(http_url), Some(https_url)) => {
format!("http={http_url};https={https_url}")
}
(Some(http_url), None) => format!("http={http_url}"),
(None, Some(https_url)) => format!("https={https_url}"),
(None, None) => "".into(),
}
.encode_utf16()
.chain(std::iter::once(0))
Expand Down
64 changes: 54 additions & 10 deletions nyquest-backend-tests/src/fixtures/client_options/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,16 @@ mod tests {

#[test]
#[cfg(not(feature = "winrt"))] // WinRT HttpClient does not support custom proxies
fn test_custom_proxy() {
const PATH: &str = "client_options/custom_proxy";
fn test_custom_http_proxy() {
const PATH: &str = "client_options/custom_http_proxy";
let proxy_fixture_setup = setup_proxy_fixture(PATH);

let assertions = |status: u16, body: String| {
assert_eq!(status, 200);
assert_eq!(body, "proxied");
};

let custom_proxy = CustomProxy::new(proxy_fixture_setup.proxy_url);
let custom_proxy = CustomProxy::http(proxy_fixture_setup.proxy_url);

#[cfg(feature = "blocking")]
{
Expand Down Expand Up @@ -133,20 +133,64 @@ mod tests {
}
}

// TODO: test against an actual HTTPS endpoint.
// TODO: test_custom_all_proxy_for_https, test_custom_https_proxy_for_https
#[test]
#[cfg(not(feature = "winrt"))] // WinRT HttpClient does not support custom proxies
fn test_custom_proxy_https() {
const PATH: &str = "client_options/custom_proxy_https";
fn test_custom_all_proxy_for_http() {
const PATH: &str = "client_options/custom_all_proxy_for_http";
let proxy_fixture_setup = setup_proxy_fixture(PATH);

let assertions = |status: u16, body: String| {
assert_eq!(status, 200);
assert_eq!(body, "proxied");
};

let custom_proxy = CustomProxy::new(proxy_fixture_setup.proxy_url.clone())
.with_https_proxy(proxy_fixture_setup.proxy_url);
let custom_proxy = CustomProxy::http(proxy_fixture_setup.proxy_url.clone())
.with_https(proxy_fixture_setup.proxy_url);

#[cfg(feature = "blocking")]
{
let builder = crate::init_builder_blocking().unwrap();
let client = builder
.custom_proxy(custom_proxy.clone())
.build_blocking()
.unwrap();
let res = client.request(NyquestRequest::get(PATH)).unwrap();
let status = res.status().into();
let body = res.text().unwrap();
assertions(status, body);
}

#[cfg(feature = "async")]
{
let (status, body) = TOKIO_RT.block_on(async {
let builder = crate::init_builder().await.unwrap();
let client = builder
.custom_proxy(custom_proxy)
.build_async()
.await
.unwrap();
let res = client.request(NyquestRequest::get(PATH)).await.unwrap();
let status = res.status().into();
let body = res.text().await.unwrap();
(status, body)
});

assertions(status, body);
}
}

#[test]
fn test_custom_https_proxy_for_http() {
const PATH: &str = "client_options/custom_https_proxy_for_http";
let proxy_fixture_setup = setup_proxy_fixture(PATH);

let assertions = |status: u16, body: String| {
assert_eq!(status, 200);
assert_eq!(body, "direct");
};

let custom_proxy = CustomProxy::https(proxy_fixture_setup.proxy_url);

#[cfg(feature = "blocking")]
{
Expand Down Expand Up @@ -190,8 +234,8 @@ mod tests {
assert_eq!(body, "direct");
};

let custom_proxy = CustomProxy::new(proxy_fixture_setup.proxy_url.clone())
.with_https_proxy(proxy_fixture_setup.proxy_url)
let custom_proxy = CustomProxy::http(proxy_fixture_setup.proxy_url.clone())
.with_https(proxy_fixture_setup.proxy_url)
.with_bypass("localhost.");

#[cfg(feature = "blocking")]
Expand Down
4 changes: 2 additions & 2 deletions nyquest-interface/src/client/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ pub enum ProxyOptions {
/// Use custom proxy settings.
Custom {
/// The proxy URL to use for HTTP requests.
proxy_url_for_http: Cow<'static, str>,
http: Option<Cow<'static, str>>,
/// The proxy URL to use for HTTPS requests.
proxy_url_for_https: Option<Cow<'static, str>>,
https: Option<Cow<'static, str>>,
/// Optional list of host patterns that should bypass the proxy.
proxy_bypass: Option<Cow<'static, str>>,
},
Expand Down
17 changes: 7 additions & 10 deletions src/client/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::time::Duration;

use nyquest_interface::client::{CachingBehavior, ClientOptions, ProxyOptions};

#[cfg(doc)]
use crate::client::CustomProxy;

/// A builder for creating an async or blocking client with custom options.
Expand Down Expand Up @@ -49,21 +50,17 @@ impl ClientBuilder {
self
}

/// Sets custom proxy settings.
/// Sets custom proxy settings from a [`CustomProxy`] configuration.
///
/// This overrides the [`Self::no_proxy`] setting.
///
/// # Note
///
/// The backend may ignore the custom proxy settings if the underlying implementation does not
/// support them (e.g., WinRT backend). Also see [`CustomProxy`].
///
/// This overrides the [`Self::no_proxy`] setting.
/// support them (e.g., WinRT backend) or if the proxy configuration is invalid.
#[inline]
pub fn custom_proxy(mut self, proxy: CustomProxy) -> Self {
self.options.proxy_options = ProxyOptions::Custom {
proxy_url_for_http: proxy.proxy_url_for_http,
proxy_url_for_https: proxy.proxy_url_for_https,
proxy_bypass: proxy.proxy_bypass,
};
pub fn custom_proxy(mut self, proxy: impl super::proxy::IntoProxyOptions) -> Self {
self.options.proxy_options = proxy.into_proxy_options();
self
}

Expand Down
Loading
Loading