Skip to content

Commit

Permalink
feat(client): change ProxyConfig to allow HTTPS proxies
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seanmonstar committed Jan 3, 2017
1 parent 0701d89 commit 14a4f1c
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 46 deletions.
123 changes: 101 additions & 22 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<H, S>(pub H, pub u16, pub S)
where H: Into<Cow<'static, str>>,
S: SslClient<<HttpConnector as NetworkConnector>::Stream> + Send + Sync + 'static;

/// A Client to use additional features with Requests.
///
Expand All @@ -97,7 +94,7 @@ pub struct Client {
redirect_policy: RedirectPolicy,
read_timeout: Option<Duration>,
write_timeout: Option<Duration>,
proxy: Option<(Cow<'static, str>, u16)>
proxy: Option<(Scheme, Cow<'static, str>, u16)>
}

impl fmt::Debug for Client {
Expand All @@ -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<H>(host: H, port: u16) -> Client
where H: Into<Cow<'static, str>> {
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<H, S>(proxy_config: ProxyConfig<H, S>) -> Client
where H: Into<Cow<'static, str>>,
S: SslClient<<HttpConnector as NetworkConnector>::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<C, S>(proxy_config: ProxyConfig<C, S>) -> Client
where C: NetworkConnector + Send + Sync + 'static,
C::Stream: NetworkStream + Send + Clone,
S: SslClient<C::Stream> + 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
}

Expand Down Expand Up @@ -450,6 +456,47 @@ impl<'a> IntoUrl for &'a String {
}
}

/// Proxy server configuration with a custom connector and TLS wrapper.
pub struct ProxyConfig<C, S>
where C: NetworkConnector + Send + Sync + 'static,
C::Stream: NetworkStream + Clone + Send,
S: SslClient<C::Stream> + Send + Sync + 'static {
scheme: Scheme,
host: Cow<'static, str>,
port: u16,
pool_config: Option<pool::Config>,
connector: C,
ssl: S,
}

impl<C, S> ProxyConfig<C, S>
where C: NetworkConnector + Send + Sync + 'static,
C::Stream: NetworkStream + Clone + Send,
S: SslClient<C::Stream> + Send + Sync + 'static {

/// Create a new `ProxyConfig`.
#[inline]
pub fn new<H: Into<Cow<'static, str>>>(scheme: &str, host: H, port: u16, connector: C, ssl: S) -> ProxyConfig<C, S> {
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<pool::Config>) {
self.pool_config = pool_config;
}
}

/// Behavior regarding how to handle redirects within a Client.
#[derive(Copy)]
pub enum RedirectPolicy {
Expand Down Expand Up @@ -499,13 +546,45 @@ 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<str> 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;
use header::Server;
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;
Expand Down Expand Up @@ -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();

Expand All @@ -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();

Expand Down
18 changes: 1 addition & 17 deletions src/client/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<C: NetworkConnector> {
Expand Down Expand Up @@ -45,23 +46,6 @@ fn key<T: Into<Scheme>>(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<DefaultConnector> {
/// Creates a `Pool` with a `DefaultConnector`.
#[inline]
Expand Down
14 changes: 7 additions & 7 deletions src/client/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<HttpConnector, ::net::Openssl> {
pub fn tunnel(proxy: (Scheme, Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::Openssl> {
Proxy {
connector: HttpConnector,
proxy: proxy,
Expand All @@ -16,7 +17,7 @@ pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::Op
}

#[cfg(feature = "security-framework")]
pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::ClientWrapper> {
pub fn tunnel(proxy: (Scheme, Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::ClientWrapper> {
Proxy {
connector: HttpConnector,
proxy: proxy,
Expand All @@ -25,7 +26,7 @@ pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::Cl
}

#[cfg(not(any(feature = "openssl", feature = "security-framework")))]
pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, self::no_ssl::Plaintext> {
pub fn tunnel(proxy: (Scheme, Cow<'static, str>, u16)) -> Proxy<HttpConnector, self::no_ssl::Plaintext> {
Proxy {
connector: HttpConnector,
proxy: proxy,
Expand All @@ -39,11 +40,10 @@ where C: NetworkConnector + Send + Sync + 'static,
C::Stream: NetworkStream + Send + Clone,
S: SslClient<C::Stream> {
pub connector: C,
pub proxy: (Cow<'static, str>, u16),
pub proxy: (Scheme, Cow<'static, str>, u16),
pub ssl: S,
}


impl<C, S> NetworkConnector for Proxy<C, S>
where C: NetworkConnector + Send + Sync + 'static,
C::Stream: NetworkStream + Send + Clone,
Expand All @@ -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));
Expand Down

0 comments on commit 14a4f1c

Please sign in to comment.