From 14a4f1c2f735efe7b638e9078710ca32dc1e360a Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Tue, 3 Jan 2017 11:00:47 -0800 Subject: [PATCH] feat(client): change ProxyConfig to allow HTTPS proxies Previously, you could only connect to an HTTP proxy (you could still use that proxy to connect to any HTTP or HTTPS URL), but if the proxy itself was protected by TLS, you could not connect to it. This change to ProxyConfig now allows specifying any kind of `NetworkConnector` to use when connecting to a proxy. BREAKING CHANGE: Usage of `with_proxy_config` will need to change to provide a network connector. For the same functionality, a `hyper::net::HttpConnector` can be easily created and passed. --- src/client/mod.rs | 123 ++++++++++++++++++++++++++++++++++++-------- src/client/pool.rs | 18 +------ src/client/proxy.rs | 14 ++--- 3 files changed, 109 insertions(+), 46 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index fef3e13257..14a1fb401a 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -68,10 +68,11 @@ use url::ParseError as UrlError; use header::{Headers, Header, HeaderFormat}; use header::{ContentLength, Host, Location}; use method::Method; -use net::{HttpConnector, NetworkConnector, NetworkStream, SslClient}; +use net::{NetworkConnector, NetworkStream, SslClient}; use Error; use self::proxy::{Proxy, tunnel}; +use self::scheme::Scheme; pub use self::pool::Pool; pub use self::request::Request; pub use self::response::Response; @@ -84,10 +85,6 @@ pub mod response; use http::Protocol; use http::h1::Http11Protocol; -/// Proxy server configuration with a custom TLS wrapper. -pub struct ProxyConfig(pub H, pub u16, pub S) -where H: Into>, - S: SslClient<::Stream> + Send + Sync + 'static; /// A Client to use additional features with Requests. /// @@ -97,7 +94,7 @@ pub struct Client { redirect_policy: RedirectPolicy, read_timeout: Option, write_timeout: Option, - proxy: Option<(Cow<'static, str>, u16)> + proxy: Option<(Scheme, Cow<'static, str>, u16)> } impl fmt::Debug for Client { @@ -123,27 +120,36 @@ impl Client { Client::with_connector(Pool::new(config)) } + /// Create a Client with an HTTP proxy to a (host, port). pub fn with_http_proxy(host: H, port: u16) -> Client where H: Into> { let host = host.into(); - let proxy = tunnel((host.clone(), port)); + let proxy = tunnel((Scheme::Http, host.clone(), port)); let mut client = Client::with_connector(Pool::with_connector(Default::default(), proxy)); - client.proxy = Some((host, port)); + client.proxy = Some((Scheme::Http, host, port)); client } - pub fn with_proxy_config(proxy_config: ProxyConfig) -> Client - where H: Into>, - S: SslClient<::Stream> + Send + Sync + 'static { - let host = proxy_config.0.into(); - let port = proxy_config.1; + /// Create a Client using a proxy with a custom connector and SSL client. + pub fn with_proxy_config(proxy_config: ProxyConfig) -> Client + where C: NetworkConnector + Send + Sync + 'static, + C::Stream: NetworkStream + Send + Clone, + S: SslClient + Send + Sync + 'static { + + let scheme = proxy_config.scheme; + let host = proxy_config.host; + let port = proxy_config.port; let proxy = Proxy { - connector: HttpConnector, - proxy: (host.clone(), port), - ssl: proxy_config.2 + proxy: (scheme.clone(), host.clone(), port), + connector: proxy_config.connector, + ssl: proxy_config.ssl, }; - let mut client = Client::with_connector(Pool::with_connector(Default::default(), proxy)); - client.proxy = Some((host, port)); + + let mut client = match proxy_config.pool_config { + Some(pool_config) => Client::with_connector(Pool::with_connector(pool_config, proxy)), + None => Client::with_connector(proxy), + }; + client.proxy = Some((scheme, host, port)); client } @@ -450,6 +456,47 @@ impl<'a> IntoUrl for &'a String { } } +/// Proxy server configuration with a custom connector and TLS wrapper. +pub struct ProxyConfig +where C: NetworkConnector + Send + Sync + 'static, + C::Stream: NetworkStream + Clone + Send, + S: SslClient + Send + Sync + 'static { + scheme: Scheme, + host: Cow<'static, str>, + port: u16, + pool_config: Option, + connector: C, + ssl: S, +} + +impl ProxyConfig +where C: NetworkConnector + Send + Sync + 'static, + C::Stream: NetworkStream + Clone + Send, + S: SslClient + Send + Sync + 'static { + + /// Create a new `ProxyConfig`. + #[inline] + pub fn new>>(scheme: &str, host: H, port: u16, connector: C, ssl: S) -> ProxyConfig { + ProxyConfig { + scheme: scheme.into(), + host: host.into(), + port: port, + pool_config: Some(pool::Config::default()), + connector: connector, + ssl: ssl, + } + } + + /// Change the `pool::Config` for the proxy. + /// + /// Passing `None` disables the `Pool`. + /// + /// The default is enabled, with the default `pool::Config`. + pub fn set_pool_config(&mut self, pool_config: Option) { + self.pool_config = pool_config; + } +} + /// Behavior regarding how to handle redirects within a Client. #[derive(Copy)] pub enum RedirectPolicy { @@ -499,6 +546,37 @@ fn get_host_and_port(url: &Url) -> ::Result<(&str, u16)> { Ok((host, port)) } +mod scheme { + + #[derive(Clone, PartialEq, Eq, Debug, Hash)] + pub enum Scheme { + Http, + Https, + Other(String) + } + + impl<'a> From<&'a str> for Scheme { + fn from(s: &'a str) -> Scheme { + match s { + "http" => Scheme::Http, + "https" => Scheme::Https, + s => Scheme::Other(String::from(s)) + } + } + } + + impl AsRef for Scheme { + fn as_ref(&self) -> &str { + match *self { + Scheme::Http => "http", + Scheme::Https => "https", + Scheme::Other(ref s) => s, + } + } + } + +} + #[cfg(test)] mod tests { use std::io::Read; @@ -506,6 +584,7 @@ mod tests { use http::h1::Http11Message; use mock::{MockStream, MockSsl}; use super::{Client, RedirectPolicy}; + use super::scheme::Scheme; use super::proxy::Proxy; use super::pool::Pool; use url::Url; @@ -537,11 +616,11 @@ mod tests { }); let tunnel = Proxy { connector: ProxyConnector, - proxy: ("example.proxy".into(), 8008), + proxy: (Scheme::Http, "example.proxy".into(), 8008), ssl: MockSsl, }; let mut client = Client::with_connector(Pool::with_connector(Default::default(), tunnel)); - client.proxy = Some(("example.proxy".into(), 8008)); + client.proxy = Some((Scheme::Http, "example.proxy".into(), 8008)); let mut dump = vec![]; client.get("http://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap(); @@ -566,11 +645,11 @@ mod tests { }); let tunnel = Proxy { connector: ProxyConnector, - proxy: ("example.proxy".into(), 8008), + proxy: (Scheme::Http, "example.proxy".into(), 8008), ssl: MockSsl, }; let mut client = Client::with_connector(Pool::with_connector(Default::default(), tunnel)); - client.proxy = Some(("example.proxy".into(), 8008)); + client.proxy = Some((Scheme::Http, "example.proxy".into(), 8008)); let mut dump = vec![]; client.get("https://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap(); diff --git a/src/client/pool.rs b/src/client/pool.rs index ecf6db22e6..ca80cd2401 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -10,6 +10,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::time::Duration; use net::{NetworkConnector, NetworkStream, DefaultConnector}; +use client::scheme::Scheme; /// The `NetworkConnector` that behaves as a connection pool used by hyper's `Client`. pub struct Pool { @@ -45,23 +46,6 @@ fn key>(host: &str, port: u16, scheme: T) -> Key { (host.to_owned(), port, scheme.into()) } -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -enum Scheme { - Http, - Https, - Other(String) -} - -impl<'a> From<&'a str> for Scheme { - fn from(s: &'a str) -> Scheme { - match s { - "http" => Scheme::Http, - "https" => Scheme::Https, - s => Scheme::Other(String::from(s)) - } - } -} - impl Pool { /// Creates a `Pool` with a `DefaultConnector`. #[inline] diff --git a/src/client/proxy.rs b/src/client/proxy.rs index ace8d2b456..7d4f9722d2 100644 --- a/src/client/proxy.rs +++ b/src/client/proxy.rs @@ -3,11 +3,12 @@ use std::io; use std::net::{SocketAddr, Shutdown}; use std::time::Duration; +use client::scheme::Scheme; use method::Method; use net::{NetworkConnector, HttpConnector, NetworkStream, SslClient}; #[cfg(all(feature = "openssl", not(feature = "security-framework")))] -pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy { +pub fn tunnel(proxy: (Scheme, Cow<'static, str>, u16)) -> Proxy { Proxy { connector: HttpConnector, proxy: proxy, @@ -16,7 +17,7 @@ pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy, u16)) -> Proxy { +pub fn tunnel(proxy: (Scheme, Cow<'static, str>, u16)) -> Proxy { Proxy { connector: HttpConnector, proxy: proxy, @@ -25,7 +26,7 @@ pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy, u16)) -> Proxy { +pub fn tunnel(proxy: (Scheme, Cow<'static, str>, u16)) -> Proxy { Proxy { connector: HttpConnector, proxy: proxy, @@ -39,11 +40,10 @@ where C: NetworkConnector + Send + Sync + 'static, C::Stream: NetworkStream + Send + Clone, S: SslClient { pub connector: C, - pub proxy: (Cow<'static, str>, u16), + pub proxy: (Scheme, Cow<'static, str>, u16), pub ssl: S, } - impl NetworkConnector for Proxy where C: NetworkConnector + Send + Sync + 'static, C::Stream: NetworkStream + Send + Clone, @@ -57,11 +57,11 @@ where C: NetworkConnector + Send + Sync + 'static, trace!("{:?} proxy for '{}://{}:{}'", self.proxy, scheme, host, port); match scheme { "http" => { - self.connector.connect(self.proxy.0.as_ref(), self.proxy.1, "http") + self.connector.connect(self.proxy.1.as_ref(), self.proxy.2, self.proxy.0.as_ref()) .map(Proxied::Normal) }, "https" => { - let mut stream = try!(self.connector.connect(self.proxy.0.as_ref(), self.proxy.1, "http")); + let mut stream = try!(self.connector.connect(self.proxy.1.as_ref(), self.proxy.2, self.proxy.0.as_ref())); trace!("{:?} CONNECT {}:{}", self.proxy, host, port); try!(write!(&mut stream, "{method} {host}:{port} {version}\r\nHost: {host}:{port}\r\n\r\n", method=Method::Connect, host=host, port=port, version=Http11));