diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index d009509..de8df38 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -334,7 +334,7 @@ impl Client { reusable }); - if proxy::is_proxied(&self.inner.proxies, &uri) { + if proxy::is_proxied(&self.inner.proxies, &url) { if uri.scheme() == Some("http") { req.set_proxy(true); } @@ -453,7 +453,7 @@ impl Future for Pending { if let Some(Some(ref body)) = self.body { req.set_body(body.clone()); } - if proxy::is_proxied(&self.client.proxies, &uri) { + if proxy::is_proxied(&self.client.proxies, &self.url) { if uri.scheme() == Some("http") { req.set_proxy(true); } diff --git a/src/connect.rs b/src/connect.rs index 1ca16e2..486a920 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -47,7 +47,7 @@ impl Service for Connector { fn call(&self, uri: Uri) -> Self::Future { for prox in self.proxies.iter() { - if let Some(puri) = proxy::proxies(prox, &uri) { + if let Some(puri) = proxy::intercept(prox, &uri) { if uri.scheme() == Some("https") { let host = uri.host().unwrap().to_owned(); let port = uri.port().unwrap_or(443); diff --git a/src/into_url.rs b/src/into_url.rs index 80de1bd..aee4c2f 100644 --- a/src/into_url.rs +++ b/src/into_url.rs @@ -36,3 +36,7 @@ impl<'a> PolyfillTryInto for &'a String { pub fn to_uri(url: &Url) -> ::hyper::Uri { url.as_str().parse().expect("a parsed Url should always be a valid Uri") } + +pub fn to_url(uri: &::hyper::Uri) -> Url { + uri.as_ref().parse().expect("reqwest Uris should only ever come from Urls") +} diff --git a/src/proxy.rs b/src/proxy.rs index 229dd10..3d74661 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -1,5 +1,8 @@ +use std::fmt; +use std::sync::Arc; + use hyper::Uri; -use {IntoUrl}; +use {into_url, IntoUrl, Url}; /// Configuration of a proxy that a `Client` should pass requests to. /// @@ -30,7 +33,6 @@ use {IntoUrl}; #[derive(Clone, Debug)] pub struct Proxy { intercept: Intercept, - uri: Uri, } impl Proxy { @@ -49,7 +51,8 @@ impl Proxy { /// # fn main() {} /// ``` pub fn http(url: U) -> ::Result { - Proxy::new(Intercept::Http, url) + let uri = ::into_url::to_uri(&try_!(url.into_url())); + Ok(Proxy::new(Intercept::Http(uri))) } /// Proxy all HTTPS traffic to the passed URL. @@ -67,7 +70,8 @@ impl Proxy { /// # fn main() {} /// ``` pub fn https(url: U) -> ::Result { - Proxy::new(Intercept::Https, url) + let uri = ::into_url::to_uri(&try_!(url.into_url())); + Ok(Proxy::new(Intercept::Https(uri))) } /// Proxy **all** traffic to the passed URL. @@ -85,7 +89,33 @@ impl Proxy { /// # fn main() {} /// ``` pub fn all(url: U) -> ::Result { - Proxy::new(Intercept::All, url) + let uri = ::into_url::to_uri(&try_!(url.into_url())); + Ok(Proxy::new(Intercept::All(uri))) + } + + /// Provide a custom function to determine what traffix to proxy to where. + /// + /// # Example + /// + /// ``` + /// # extern crate reqwest; + /// # fn run() -> Result<(), Box<::std::error::Error>> { + /// let target = reqwest::Url::parse("https://my.prox")?; + /// let client = reqwest::Client::builder()? + /// .proxy(reqwest::Proxy::custom(move |url| { + /// if url.host_str() == Some("hyper.rs") { + /// Some(target.clone()) + /// } else { + /// None + /// } + /// })) + /// .build()?; + /// # Ok(()) + /// # } + /// # fn main() {} + pub fn custom(fun: F) -> Proxy + where F: Fn(&Url) -> Option + Send + Sync + 'static { + Proxy::new(Intercept::Custom(Custom(Arc::new(fun)))) } /* @@ -94,41 +124,71 @@ impl Proxy { } */ - fn new(intercept: Intercept, url: U) -> ::Result { - let uri = ::into_url::to_uri(&try_!(url.into_url())); - Ok(Proxy { + fn new(intercept: Intercept) -> Proxy { + Proxy { intercept: intercept, - uri: uri, - }) + } } - fn proxies(&self, uri: &Uri) -> bool { + fn proxies(&self, url: &Url) -> bool { match self.intercept { - Intercept::All => true, - Intercept::Http => uri.scheme() == Some("http"), - Intercept::Https => uri.scheme() == Some("https"), + Intercept::All(..) => true, + Intercept::Http(..) => url.scheme() == "http", + Intercept::Https(..) => url.scheme() == "https", + Intercept::Custom(ref fun) => (fun.0)(url).is_some(), + } + } + + + fn intercept(&self, uri: &Uri) -> Option { + match self.intercept { + Intercept::All(ref u) => Some(u.clone()), + Intercept::Http(ref u) => { + if uri.scheme() == Some("http") { + Some(u.clone()) + } else { + None + } + }, + Intercept::Https(ref u) => { + if uri.scheme() == Some("https") { + Some(u.clone()) + } else { + None + } + }, + Intercept::Custom(ref fun) => { + (fun.0)(&into_url::to_url(uri)) + .map(|u| into_url::to_uri(&u)) + }, } } } #[derive(Clone, Debug)] enum Intercept { - All, - Http, - Https, + All(Uri), + Http(Uri), + Https(Uri), + Custom(Custom), +} + +#[derive(Clone)] +struct Custom(Arc Option + Send + Sync + 'static>); + +impl fmt::Debug for Custom { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("_") + } } // pub(crate) -pub fn proxies(proxy: &Proxy, uri: &Uri) -> Option { - if proxy.proxies(uri) { - Some(proxy.uri.clone()) - } else { - None - } +pub fn intercept(proxy: &Proxy, uri: &Uri) -> Option { + proxy.intercept(uri) } -pub fn is_proxied(proxies: &[Proxy], uri: &Uri) -> bool { +pub fn is_proxied(proxies: &[Proxy], uri: &Url) -> bool { proxies.iter().any(|p| p.proxies(uri)) } @@ -136,39 +196,86 @@ pub fn is_proxied(proxies: &[Proxy], uri: &Uri) -> bool { mod tests { use super::*; + fn uri(s: &str) -> Uri { + s.parse().unwrap() + } + + fn url(s: &str) -> Url { + s.parse().unwrap() + } + #[test] fn test_http() { - let p = Proxy::http("http://example.domain").unwrap(); + let target = "http://example.domain/"; + let p = Proxy::http(target).unwrap(); - let http = "http://hyper.rs".parse().unwrap(); - let other = "https://hyper.rs".parse().unwrap(); + let http = "http://hyper.rs"; + let other = "https://hyper.rs"; - assert!(p.proxies(&http)); - assert!(!p.proxies(&other)); + assert!(p.proxies(&url(http))); + assert_eq!(p.intercept(&uri(http)).unwrap(), target); + assert!(!p.proxies(&url(other))); + assert!(p.intercept(&uri(other)).is_none()); } #[test] fn test_https() { - let p = Proxy::https("http://example.domain").unwrap(); + let target = "http://example.domain/"; + let p = Proxy::https(target).unwrap(); - let http = "http://hyper.rs".parse().unwrap(); - let other = "https://hyper.rs".parse().unwrap(); + let http = "http://hyper.rs"; + let other = "https://hyper.rs"; - assert!(!p.proxies(&http)); - assert!(p.proxies(&other)); + assert!(!p.proxies(&url(http))); + assert!(p.intercept(&uri(http)).is_none()); + assert!(p.proxies(&url(other))); + assert_eq!(p.intercept(&uri(other)).unwrap(), target); } #[test] fn test_all() { - let p = Proxy::all("http://example.domain").unwrap(); + let target = "http://example.domain/"; + let p = Proxy::all(target).unwrap(); - let http = "http://hyper.rs".parse().unwrap(); - let https = "https://hyper.rs".parse().unwrap(); - let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs".parse().unwrap(); + let http = "http://hyper.rs"; + let https = "https://hyper.rs"; + let other = "x-youve-never-heard-of-me-mr-proxy://hyper.rs"; - assert!(p.proxies(&http)); - assert!(p.proxies(&https)); - assert!(p.proxies(&other)); + assert!(p.proxies(&url(http))); + assert!(p.proxies(&url(https))); + assert!(p.proxies(&url(other))); + + assert_eq!(p.intercept(&uri(http)).unwrap(), target); + assert_eq!(p.intercept(&uri(https)).unwrap(), target); + assert_eq!(p.intercept(&uri(other)).unwrap(), target); + } + + + #[test] + fn test_custom() { + let target1 = "http://example.domain/"; + let target2 = "https://example.domain/"; + let p = Proxy::custom(move |url| { + if url.host_str() == Some("hyper.rs") { + target1.parse().ok() + } else if url.scheme() == "http" { + target2.parse().ok() + } else { + None + } + }); + + let http = "http://seanmonstar.com"; + let https = "https://hyper.rs"; + let other = "x-youve-never-heard-of-me-mr-proxy://seanmonstar.com"; + + assert!(p.proxies(&url(http))); + assert!(p.proxies(&url(https))); + assert!(!p.proxies(&url(other))); + + assert_eq!(p.intercept(&uri(http)).unwrap(), target2); + assert_eq!(p.intercept(&uri(https)).unwrap(), target1); + assert!(p.intercept(&uri(other)).is_none()); } #[test]