| @@ -20,7 +20,20 @@ fn main() { | |||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let client = Client::new(); |     let client = match env::var("HTTP_PROXY") { | ||||||
|  |         Ok(mut proxy) => { | ||||||
|  |             // parse the proxy, message if it doesn't make sense | ||||||
|  |             let mut port = 80; | ||||||
|  |             if let Some(colon) = proxy.rfind(':') { | ||||||
|  |                 port = proxy[colon + 1..].parse().unwrap_or_else(|e| { | ||||||
|  |                     panic!("HTTP_PROXY is malformed: {:?}, port parse error: {}", proxy, e); | ||||||
|  |                 }); | ||||||
|  |                 proxy.truncate(colon); | ||||||
|  |             } | ||||||
|  |             Client::with_http_proxy(proxy, port) | ||||||
|  |         }, | ||||||
|  |         _ => Client::new() | ||||||
|  |     }; | ||||||
|  |  | ||||||
|     let mut res = client.get(&*url) |     let mut res = client.get(&*url) | ||||||
|         .header(Connection::close()) |         .header(Connection::close()) | ||||||
|   | |||||||
| @@ -71,10 +71,12 @@ use method::Method; | |||||||
| use net::{NetworkConnector, NetworkStream}; | use net::{NetworkConnector, NetworkStream}; | ||||||
| use Error; | use Error; | ||||||
|  |  | ||||||
|  | use self::proxy::tunnel; | ||||||
| pub use self::pool::Pool; | pub use self::pool::Pool; | ||||||
| pub use self::request::Request; | pub use self::request::Request; | ||||||
| pub use self::response::Response; | pub use self::response::Response; | ||||||
|  |  | ||||||
|  | mod proxy; | ||||||
| pub mod pool; | pub mod pool; | ||||||
| pub mod request; | pub mod request; | ||||||
| pub mod response; | pub mod response; | ||||||
| @@ -90,7 +92,7 @@ pub struct Client { | |||||||
|     redirect_policy: RedirectPolicy, |     redirect_policy: RedirectPolicy, | ||||||
|     read_timeout: Option<Duration>, |     read_timeout: Option<Duration>, | ||||||
|     write_timeout: Option<Duration>, |     write_timeout: Option<Duration>, | ||||||
|     proxy: Option<(Cow<'static, str>, Cow<'static, str>, u16)> |     proxy: Option<(Cow<'static, str>, u16)> | ||||||
| } | } | ||||||
|  |  | ||||||
| impl fmt::Debug for Client { | impl fmt::Debug for Client { | ||||||
| @@ -116,6 +118,15 @@ impl Client { | |||||||
|         Client::with_connector(Pool::new(config)) |         Client::with_connector(Pool::new(config)) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     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 mut client = Client::with_connector(Pool::with_connector(Default::default(), proxy)); | ||||||
|  |         client.proxy = Some((host, port)); | ||||||
|  |         client | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /// Create a new client with a specific connector. |     /// Create a new client with a specific connector. | ||||||
|     pub fn with_connector<C, S>(connector: C) -> Client |     pub fn with_connector<C, S>(connector: C) -> Client | ||||||
|     where C: NetworkConnector<Stream=S> + Send + Sync + 'static, S: NetworkStream + Send { |     where C: NetworkConnector<Stream=S> + Send + Sync + 'static, S: NetworkStream + Send { | ||||||
| @@ -148,12 +159,6 @@ impl Client { | |||||||
|         self.write_timeout = dur; |         self.write_timeout = dur; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Set a proxy for requests of this Client. |  | ||||||
|     pub fn set_proxy<S, H>(&mut self, scheme: S, host: H, port: u16) |  | ||||||
|     where S: Into<Cow<'static, str>>, H: Into<Cow<'static, str>> { |  | ||||||
|         self.proxy = Some((scheme.into(), host.into(), port)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Build a Get request. |     /// Build a Get request. | ||||||
|     pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder { |     pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder { | ||||||
|         self.request(Method::Get, url) |         self.request(Method::Get, url) | ||||||
| @@ -271,13 +276,12 @@ impl<'a> RequestBuilder<'a> { | |||||||
|  |  | ||||||
|         loop { |         loop { | ||||||
|             let mut req = { |             let mut req = { | ||||||
|                 let (scheme, host, port) = match client.proxy { |                 let (host, port) = try!(get_host_and_port(&url)); | ||||||
|                     Some(ref proxy) => (proxy.0.as_ref(), proxy.1.as_ref(), proxy.2), |                 let mut message = try!(client.protocol.new_message(&host, port, url.scheme())); | ||||||
|                     None => { |                 if url.scheme() == "http" && client.proxy.is_some() { | ||||||
|                         let hp = try!(get_host_and_port(&url)); |                     message.set_proxied(true); | ||||||
|                         (url.scheme(), hp.0, hp.1) |                 } | ||||||
|                     } |  | ||||||
|                 }; |  | ||||||
|                 let mut headers = match headers { |                 let mut headers = match headers { | ||||||
|                     Some(ref headers) => headers.clone(), |                     Some(ref headers) => headers.clone(), | ||||||
|                     None => Headers::new(), |                     None => Headers::new(), | ||||||
| @@ -286,7 +290,6 @@ impl<'a> RequestBuilder<'a> { | |||||||
|                     hostname: host.to_owned(), |                     hostname: host.to_owned(), | ||||||
|                     port: Some(port), |                     port: Some(port), | ||||||
|                 }); |                 }); | ||||||
|                 let message = try!(client.protocol.new_message(&host, port, scheme)); |  | ||||||
|                 Request::with_headers_and_message(method.clone(), url.clone(), headers, message) |                 Request::with_headers_and_message(method.clone(), url.clone(), headers, message) | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
| @@ -460,6 +463,7 @@ impl Default for RedirectPolicy { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| fn get_host_and_port(url: &Url) -> ::Result<(&str, u16)> { | fn get_host_and_port(url: &Url) -> ::Result<(&str, u16)> { | ||||||
|     let host = match url.host_str() { |     let host = match url.host_str() { | ||||||
|         Some(host) => host, |         Some(host) => host, | ||||||
| @@ -479,8 +483,9 @@ mod tests { | |||||||
|     use std::io::Read; |     use std::io::Read; | ||||||
|     use header::Server; |     use header::Server; | ||||||
|     use http::h1::Http11Message; |     use http::h1::Http11Message; | ||||||
|     use mock::{MockStream}; |     use mock::{MockStream, MockSsl}; | ||||||
|     use super::{Client, RedirectPolicy}; |     use super::{Client, RedirectPolicy}; | ||||||
|  |     use super::proxy::Proxy; | ||||||
|     use super::pool::Pool; |     use super::pool::Pool; | ||||||
|     use url::Url; |     use url::Url; | ||||||
|  |  | ||||||
| @@ -505,24 +510,61 @@ mod tests { | |||||||
|     #[test] |     #[test] | ||||||
|     fn test_proxy() { |     fn test_proxy() { | ||||||
|         use super::pool::PooledStream; |         use super::pool::PooledStream; | ||||||
|  |         type MessageStream = PooledStream<super::proxy::Proxied<MockStream, MockStream>>; | ||||||
|         mock_connector!(ProxyConnector { |         mock_connector!(ProxyConnector { | ||||||
|             b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" |             b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" | ||||||
|         }); |         }); | ||||||
|         let mut client = Client::with_connector(Pool::with_connector(Default::default(), ProxyConnector)); |         let tunnel = Proxy { | ||||||
|         client.set_proxy("http", "example.proxy", 8008); |             connector: ProxyConnector, | ||||||
|  |             proxy: ("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)); | ||||||
|         let mut dump = vec![]; |         let mut dump = vec![]; | ||||||
|         client.get("http://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap(); |         client.get("http://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap(); | ||||||
|  |  | ||||||
|         { |         let box_message = client.protocol.new_message("127.0.0.1", 80, "http").unwrap(); | ||||||
|             let box_message = client.protocol.new_message("example.proxy", 8008, "http").unwrap(); |         let message = box_message.downcast::<Http11Message>().unwrap(); | ||||||
|             let message = box_message.downcast::<Http11Message>().unwrap(); |         let stream =  message.into_inner().downcast::<MessageStream>().unwrap().into_inner().into_normal().unwrap();; | ||||||
|             let stream =  message.into_inner().downcast::<PooledStream<MockStream>>().unwrap().into_inner(); |  | ||||||
|             let s = ::std::str::from_utf8(&stream.write).unwrap(); |  | ||||||
|             let request_line = "GET http://127.0.0.1/foo/bar HTTP/1.1\r\n"; |  | ||||||
|             assert_eq!(&s[..request_line.len()], request_line); |  | ||||||
|             assert!(s.contains("Host: example.proxy:8008\r\n")); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|  |         let s = ::std::str::from_utf8(&stream.write).unwrap(); | ||||||
|  |         let request_line = "GET http://127.0.0.1/foo/bar HTTP/1.1\r\n"; | ||||||
|  |         assert!(s.starts_with(request_line), "{:?} doesn't start with {:?}", s, request_line); | ||||||
|  |         assert!(s.contains("Host: 127.0.0.1\r\n")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_proxy_tunnel() { | ||||||
|  |         use super::pool::PooledStream; | ||||||
|  |         type MessageStream = PooledStream<super::proxy::Proxied<MockStream, MockStream>>; | ||||||
|  |  | ||||||
|  |         mock_connector!(ProxyConnector { | ||||||
|  |             b"HTTP/1.1 200 OK\r\n\r\n", | ||||||
|  |             b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" | ||||||
|  |         }); | ||||||
|  |         let tunnel = Proxy { | ||||||
|  |             connector: ProxyConnector, | ||||||
|  |             proxy: ("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)); | ||||||
|  |         let mut dump = vec![]; | ||||||
|  |         client.get("https://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap(); | ||||||
|  |  | ||||||
|  |         let box_message = client.protocol.new_message("127.0.0.1", 443, "https").unwrap(); | ||||||
|  |         let message = box_message.downcast::<Http11Message>().unwrap(); | ||||||
|  |         let stream = message.into_inner().downcast::<MessageStream>().unwrap().into_inner().into_tunneled().unwrap(); | ||||||
|  |  | ||||||
|  |         let s = ::std::str::from_utf8(&stream.write).unwrap(); | ||||||
|  |         let connect_line = "CONNECT 127.0.0.1:443 HTTP/1.1\r\nHost: 127.0.0.1:443\r\n\r\n"; | ||||||
|  |         assert_eq!(&s[..connect_line.len()], connect_line); | ||||||
|  |  | ||||||
|  |         let s = &s[connect_line.len()..]; | ||||||
|  |         let request_line = "GET /foo/bar HTTP/1.1\r\n"; | ||||||
|  |         assert_eq!(&s[..request_line.len()], request_line); | ||||||
|  |         assert!(s.contains("Host: 127.0.0.1\r\n")); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|   | |||||||
| @@ -127,6 +127,7 @@ impl<C: NetworkConnector<Stream=S>, S: NetworkStream + Send> NetworkConnector fo | |||||||
| } | } | ||||||
|  |  | ||||||
| /// A Stream that will try to be returned to the Pool when dropped. | /// A Stream that will try to be returned to the Pool when dropped. | ||||||
|  | #[derive(Debug)] | ||||||
| pub struct PooledStream<S> { | pub struct PooledStream<S> { | ||||||
|     inner: Option<PooledStreamInner<S>>, |     inner: Option<PooledStreamInner<S>>, | ||||||
|     is_closed: bool, |     is_closed: bool, | ||||||
|   | |||||||
							
								
								
									
										240
									
								
								src/client/proxy.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								src/client/proxy.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,240 @@ | |||||||
|  | use std::borrow::Cow; | ||||||
|  | use std::io; | ||||||
|  | use std::net::{SocketAddr, Shutdown}; | ||||||
|  | use std::time::Duration; | ||||||
|  |  | ||||||
|  | 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> { | ||||||
|  |     Proxy { | ||||||
|  |         connector: HttpConnector, | ||||||
|  |         proxy: proxy, | ||||||
|  |         ssl: Default::default() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "security-framework")] | ||||||
|  | pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, ::net::Openssl> { | ||||||
|  |     Proxy { | ||||||
|  |         connector: HttpConnector, | ||||||
|  |         proxy: proxy, | ||||||
|  |         ssl: Default::default() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(not(any(feature = "openssl", feature = "security-framework")))] | ||||||
|  | pub fn tunnel(proxy: (Cow<'static, str>, u16)) -> Proxy<HttpConnector, self::no_ssl::Plaintext> { | ||||||
|  |     Proxy { | ||||||
|  |         connector: HttpConnector, | ||||||
|  |         proxy: proxy, | ||||||
|  |         ssl: self::no_ssl::Plaintext, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct Proxy<C, S> | ||||||
|  | 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 ssl: S, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | impl<C, S> NetworkConnector for Proxy<C, S> | ||||||
|  | where C: NetworkConnector + Send + Sync + 'static, | ||||||
|  |       C::Stream: NetworkStream + Send + Clone, | ||||||
|  |       S: SslClient<C::Stream> { | ||||||
|  |     type Stream = Proxied<C::Stream, S::Stream>; | ||||||
|  |  | ||||||
|  |     fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream> { | ||||||
|  |         use httparse; | ||||||
|  |         use std::io::{Read, Write}; | ||||||
|  |         use ::version::HttpVersion::Http11; | ||||||
|  |         trace!("{:?} proxy for '{}://{}:{}'", self.proxy, scheme, host, port); | ||||||
|  |         match scheme { | ||||||
|  |             "http" => { | ||||||
|  |                 self.connector.connect(self.proxy.0.as_ref(), self.proxy.1, "http") | ||||||
|  |                     .map(Proxied::Normal) | ||||||
|  |             }, | ||||||
|  |             "https" => { | ||||||
|  |                 let mut stream = try!(self.connector.connect(self.proxy.0.as_ref(), self.proxy.1, "http")); | ||||||
|  |                 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)); | ||||||
|  |                 try!(stream.flush()); | ||||||
|  |                 let mut buf = [0; 1024]; | ||||||
|  |                 let mut n = 0; | ||||||
|  |                 while n < buf.len() { | ||||||
|  |                     n += try!(stream.read(&mut buf[n..])); | ||||||
|  |                     let mut headers = [httparse::EMPTY_HEADER; 10]; | ||||||
|  |                     let mut res = httparse::Response::new(&mut headers); | ||||||
|  |                     if try!(res.parse(&buf[..n])).is_complete() { | ||||||
|  |                         let code = res.code.expect("complete parsing lost code"); | ||||||
|  |                         if code >= 200 && code < 300 { | ||||||
|  |                             trace!("CONNECT success = {:?}", code); | ||||||
|  |                             return self.ssl.wrap_client(stream, host) | ||||||
|  |                                 .map(Proxied::Tunneled) | ||||||
|  |                         } else { | ||||||
|  |                             trace!("CONNECT response = {:?}", code); | ||||||
|  |                             return Err(::Error::Status); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 Err(::Error::TooLarge) | ||||||
|  |             }, | ||||||
|  |             _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid scheme").into()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub enum Proxied<T1, T2> { | ||||||
|  |     Normal(T1), | ||||||
|  |     Tunneled(T2) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | impl<T1, T2> Proxied<T1, T2> { | ||||||
|  |     pub fn into_normal(self) -> Result<T1, Self> { | ||||||
|  |         match self { | ||||||
|  |             Proxied::Normal(t1) => Ok(t1), | ||||||
|  |             _ => Err(self) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn into_tunneled(self) -> Result<T2, Self> { | ||||||
|  |         match self { | ||||||
|  |             Proxied::Tunneled(t2) => Ok(t2), | ||||||
|  |             _ => Err(self) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T1: NetworkStream, T2: NetworkStream> io::Read for Proxied<T1, T2> { | ||||||
|  |     #[inline] | ||||||
|  |     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||||
|  |         match *self { | ||||||
|  |             Proxied::Normal(ref mut t) => io::Read::read(t, buf), | ||||||
|  |             Proxied::Tunneled(ref mut t) => io::Read::read(t, buf), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T1: NetworkStream, T2: NetworkStream> io::Write for Proxied<T1, T2> { | ||||||
|  |     #[inline] | ||||||
|  |     fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||||||
|  |         match *self { | ||||||
|  |             Proxied::Normal(ref mut t) => io::Write::write(t, buf), | ||||||
|  |             Proxied::Tunneled(ref mut t) => io::Write::write(t, buf), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn flush(&mut self) -> io::Result<()> { | ||||||
|  |         match *self { | ||||||
|  |             Proxied::Normal(ref mut t) => io::Write::flush(t), | ||||||
|  |             Proxied::Tunneled(ref mut t) => io::Write::flush(t), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T1: NetworkStream, T2: NetworkStream> NetworkStream for Proxied<T1, T2> { | ||||||
|  |     #[inline] | ||||||
|  |     fn peer_addr(&mut self) -> io::Result<SocketAddr> { | ||||||
|  |         match *self { | ||||||
|  |             Proxied::Normal(ref mut s) => s.peer_addr(), | ||||||
|  |             Proxied::Tunneled(ref mut s) => s.peer_addr() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> { | ||||||
|  |         match *self { | ||||||
|  |             Proxied::Normal(ref inner) => inner.set_read_timeout(dur), | ||||||
|  |             Proxied::Tunneled(ref inner) => inner.set_read_timeout(dur) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> { | ||||||
|  |         match *self { | ||||||
|  |             Proxied::Normal(ref inner) => inner.set_write_timeout(dur), | ||||||
|  |             Proxied::Tunneled(ref inner) => inner.set_write_timeout(dur) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn close(&mut self, how: Shutdown) -> io::Result<()> { | ||||||
|  |         match *self { | ||||||
|  |             Proxied::Normal(ref mut s) => s.close(how), | ||||||
|  |             Proxied::Tunneled(ref mut s) => s.close(how) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(not(any(feature = "openssl", feature = "security-framework")))] | ||||||
|  | mod no_ssl { | ||||||
|  |     use std::io; | ||||||
|  |     use std::net::{Shutdown, SocketAddr}; | ||||||
|  |     use std::time::Duration; | ||||||
|  |  | ||||||
|  |     use net::{SslClient, NetworkStream}; | ||||||
|  |  | ||||||
|  |     pub struct Plaintext; | ||||||
|  |  | ||||||
|  |     #[derive(Clone)] | ||||||
|  |     pub enum Void {} | ||||||
|  |  | ||||||
|  |     impl io::Read for Void { | ||||||
|  |         #[inline] | ||||||
|  |         fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> { | ||||||
|  |             match *self {} | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl io::Write for Void { | ||||||
|  |         #[inline] | ||||||
|  |         fn write(&mut self, _buf: &[u8]) -> io::Result<usize> { | ||||||
|  |             match *self {} | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #[inline] | ||||||
|  |         fn flush(&mut self) -> io::Result<()> { | ||||||
|  |             match *self {} | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl NetworkStream for Void { | ||||||
|  |         #[inline] | ||||||
|  |         fn peer_addr(&mut self) -> io::Result<SocketAddr> { | ||||||
|  |             match *self {} | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #[inline] | ||||||
|  |         fn set_read_timeout(&self, _dur: Option<Duration>) -> io::Result<()> { | ||||||
|  |             match *self {} | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #[inline] | ||||||
|  |         fn set_write_timeout(&self, _dur: Option<Duration>) -> io::Result<()> { | ||||||
|  |             match *self {} | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         #[inline] | ||||||
|  |         fn close(&mut self, _how: Shutdown) -> io::Result<()> { | ||||||
|  |             match *self {} | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     impl<T: NetworkStream + Send + Clone> SslClient<T> for Plaintext { | ||||||
|  |         type Stream = Void; | ||||||
|  |  | ||||||
|  |         fn wrap_client(&self, _stream: T, _host: &str) -> ::Result<Self::Stream> { | ||||||
|  |             Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid scheme").into()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -268,16 +268,15 @@ mod tests { | |||||||
|     #[test] |     #[test] | ||||||
|     fn test_proxy() { |     fn test_proxy() { | ||||||
|         let url = Url::parse("http://example.dom").unwrap(); |         let url = Url::parse("http://example.dom").unwrap(); | ||||||
|         let proxy_url = Url::parse("http://pro.xy").unwrap(); |  | ||||||
|         let mut req = Request::with_connector( |         let mut req = Request::with_connector( | ||||||
|             Get, proxy_url, &mut MockConnector |             Get, url, &mut MockConnector | ||||||
|         ).unwrap(); |         ).unwrap(); | ||||||
|         req.url = url; |         req.message.set_proxied(true); | ||||||
|         let bytes = run_request(req); |         let bytes = run_request(req); | ||||||
|         let s = from_utf8(&bytes[..]).unwrap(); |         let s = from_utf8(&bytes[..]).unwrap(); | ||||||
|         let request_line = "GET http://example.dom/ HTTP/1.1"; |         let request_line = "GET http://example.dom/ HTTP/1.1"; | ||||||
|         assert_eq!(&s[..request_line.len()], request_line); |         assert_eq!(&s[..request_line.len()], request_line); | ||||||
|         assert!(s.contains("Host: pro.xy")); |         assert!(s.contains("Host: example.dom")); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ use url::Position as UrlPosition; | |||||||
|  |  | ||||||
| use buffer::BufReader; | use buffer::BufReader; | ||||||
| use Error; | use Error; | ||||||
| use header::{Headers, Host, ContentLength, TransferEncoding}; | use header::{Headers, ContentLength, TransferEncoding}; | ||||||
| use header::Encoding::Chunked; | use header::Encoding::Chunked; | ||||||
| use method::{Method}; | use method::{Method}; | ||||||
| use net::{NetworkConnector, NetworkStream}; | use net::{NetworkConnector, NetworkStream}; | ||||||
| @@ -91,6 +91,7 @@ impl Stream { | |||||||
| /// An implementation of the `HttpMessage` trait for HTTP/1.1. | /// An implementation of the `HttpMessage` trait for HTTP/1.1. | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct Http11Message { | pub struct Http11Message { | ||||||
|  |     is_proxied: bool, | ||||||
|     method: Option<Method>, |     method: Option<Method>, | ||||||
|     stream: Wrapper<Stream>, |     stream: Wrapper<Stream>, | ||||||
| } | } | ||||||
| @@ -131,6 +132,7 @@ impl HttpMessage for Http11Message { | |||||||
|                             io::ErrorKind::Other, |                             io::ErrorKind::Other, | ||||||
|                             ""))); |                             ""))); | ||||||
|         let mut method = None; |         let mut method = None; | ||||||
|  |         let is_proxied = self.is_proxied; | ||||||
|         self.stream.map_in_place(|stream: Stream| -> Stream { |         self.stream.map_in_place(|stream: Stream| -> Stream { | ||||||
|             let stream = match stream { |             let stream = match stream { | ||||||
|                 Stream::Idle(stream) => stream, |                 Stream::Idle(stream) => stream, | ||||||
| @@ -144,17 +146,10 @@ impl HttpMessage for Http11Message { | |||||||
|             let mut stream = BufWriter::new(stream); |             let mut stream = BufWriter::new(stream); | ||||||
|  |  | ||||||
|             { |             { | ||||||
|                 let uri = match head.headers.get::<Host>() { |                 let uri = if is_proxied { | ||||||
|                     Some(host) |                     head.url.as_ref() | ||||||
|                     if Some(&*host.hostname) == head.url.host_str() |                 } else { | ||||||
|                     && host.port == head.url.port_or_known_default() => { |                     &head.url[UrlPosition::BeforePath..UrlPosition::AfterQuery] | ||||||
|                         &head.url[UrlPosition::BeforePath..UrlPosition::AfterQuery] |  | ||||||
|                     }, |  | ||||||
|                     _ => { |  | ||||||
|                         trace!("url and host header dont match, using absolute uri form"); |  | ||||||
|                         head.url.as_ref() |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                 }; |                 }; | ||||||
|  |  | ||||||
|                 let version = version::HttpVersion::Http11; |                 let version = version::HttpVersion::Http11; | ||||||
| @@ -365,6 +360,11 @@ impl HttpMessage for Http11Message { | |||||||
|         try!(self.get_mut().close(Shutdown::Both)); |         try!(self.get_mut().close(Shutdown::Both)); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[inline] | ||||||
|  |     fn set_proxied(&mut self, val: bool) { | ||||||
|  |         self.is_proxied = val; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Http11Message { | impl Http11Message { | ||||||
| @@ -401,6 +401,7 @@ impl Http11Message { | |||||||
|     /// the peer. |     /// the peer. | ||||||
|     pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message { |     pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message { | ||||||
|         Http11Message { |         Http11Message { | ||||||
|  |             is_proxied: false, | ||||||
|             method: None, |             method: None, | ||||||
|             stream: Wrapper::new(Stream::new(stream)), |             stream: Wrapper::new(Stream::new(stream)), | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -70,6 +70,11 @@ pub trait HttpMessage: Write + Read + Send + Any + Typeable + Debug { | |||||||
|     fn close_connection(&mut self) -> ::Result<()>; |     fn close_connection(&mut self) -> ::Result<()>; | ||||||
|     /// Returns whether the incoming message has a body. |     /// Returns whether the incoming message has a body. | ||||||
|     fn has_body(&self) -> bool; |     fn has_body(&self) -> bool; | ||||||
|  |     /// Called when the Client wishes to use a Proxy. | ||||||
|  |     fn set_proxied(&mut self, val: bool) { | ||||||
|  |         // default implementation so as to not be a breaking change. | ||||||
|  |         warn!("default set_proxied({:?})", val); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl HttpMessage { | impl HttpMessage { | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								src/mock.rs
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/mock.rs
									
									
									
									
									
								
							| @@ -12,7 +12,7 @@ use solicit::http::frame::{SettingsFrame, Frame}; | |||||||
| use solicit::http::connection::{HttpConnection, EndStream, DataChunk}; | use solicit::http::connection::{HttpConnection, EndStream, DataChunk}; | ||||||
|  |  | ||||||
| use header::Headers; | use header::Headers; | ||||||
| use net::{NetworkStream, NetworkConnector}; | use net::{NetworkStream, NetworkConnector, SslClient}; | ||||||
|  |  | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| pub struct MockStream { | pub struct MockStream { | ||||||
| @@ -315,3 +315,13 @@ impl NetworkConnector for MockHttp2Connector { | |||||||
|         Ok(self.streams.borrow_mut().remove(0)) |         Ok(self.streams.borrow_mut().remove(0)) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Default)] | ||||||
|  | pub struct MockSsl; | ||||||
|  |  | ||||||
|  | impl<T: NetworkStream + Send + Clone> SslClient<T> for MockSsl { | ||||||
|  |     type Stream = T; | ||||||
|  |     fn wrap_client(&self, stream: T, _host: &str) -> ::Result<T> { | ||||||
|  |         Ok(stream) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										64
									
								
								src/net.rs
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								src/net.rs
									
									
									
									
									
								
							| @@ -6,7 +6,7 @@ use std::net::{SocketAddr, ToSocketAddrs, TcpStream, TcpListener, Shutdown}; | |||||||
| use std::mem; | use std::mem; | ||||||
|  |  | ||||||
| #[cfg(feature = "openssl")] | #[cfg(feature = "openssl")] | ||||||
| pub use self::openssl::Openssl; | pub use self::openssl::{Openssl, OpensslClient}; | ||||||
|  |  | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
| @@ -423,22 +423,22 @@ pub trait Ssl { | |||||||
| } | } | ||||||
|  |  | ||||||
| /// An abstraction to allow any SSL implementation to be used with client-side HttpsStreams. | /// An abstraction to allow any SSL implementation to be used with client-side HttpsStreams. | ||||||
| pub trait SslClient { | pub trait SslClient<T: NetworkStream + Send + Clone = HttpStream> { | ||||||
|     /// The protected stream. |     /// The protected stream. | ||||||
|     type Stream: NetworkStream + Send + Clone; |     type Stream: NetworkStream + Send + Clone; | ||||||
|     /// Wrap a client stream with SSL. |     /// Wrap a client stream with SSL. | ||||||
|     fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream>; |     fn wrap_client(&self, stream: T, host: &str) -> ::Result<Self::Stream>; | ||||||
| } | } | ||||||
|  |  | ||||||
| /// An abstraction to allow any SSL implementation to be used with server-side HttpsStreams. | /// An abstraction to allow any SSL implementation to be used with server-side HttpsStreams. | ||||||
| pub trait SslServer { | pub trait SslServer<T: NetworkStream + Send + Clone = HttpStream> { | ||||||
|     /// The protected stream. |     /// The protected stream. | ||||||
|     type Stream: NetworkStream + Send + Clone; |     type Stream: NetworkStream + Send + Clone; | ||||||
|     /// Wrap a server stream with SSL. |     /// Wrap a server stream with SSL. | ||||||
|     fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream>; |     fn wrap_server(&self, stream: T) -> ::Result<Self::Stream>; | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<S: Ssl> SslClient for S { | impl<S: Ssl> SslClient<HttpStream> for S { | ||||||
|     type Stream = <S as Ssl>::Stream; |     type Stream = <S as Ssl>::Stream; | ||||||
|  |  | ||||||
|     fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> { |     fn wrap_client(&self, stream: HttpStream, host: &str) -> ::Result<Self::Stream> { | ||||||
| @@ -446,7 +446,7 @@ impl<S: Ssl> SslClient for S { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<S: Ssl> SslServer for S { | impl<S: Ssl> SslServer<HttpStream> for S { | ||||||
|     type Stream = <S as Ssl>::Stream; |     type Stream = <S as Ssl>::Stream; | ||||||
|  |  | ||||||
|     fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream> { |     fn wrap_server(&self, stream: HttpStream) -> ::Result<Self::Stream> { | ||||||
| @@ -566,28 +566,35 @@ impl<S: SslServer + Clone> NetworkListener for HttpsListener<S> { | |||||||
|  |  | ||||||
| /// A connector that can protect HTTP streams using SSL. | /// A connector that can protect HTTP streams using SSL. | ||||||
| #[derive(Debug, Default)] | #[derive(Debug, Default)] | ||||||
| pub struct HttpsConnector<S: SslClient> { | pub struct HttpsConnector<S: SslClient, C: NetworkConnector = HttpConnector> { | ||||||
|     ssl: S |     ssl: S, | ||||||
|  |     connector: C, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<S: SslClient> HttpsConnector<S> { | impl<S: SslClient> HttpsConnector<S, HttpConnector> { | ||||||
|     /// Create a new connector using the provided SSL implementation. |     /// Create a new connector using the provided SSL implementation. | ||||||
|     pub fn new(s: S) -> HttpsConnector<S> { |     pub fn new(s: S) -> HttpsConnector<S, HttpConnector> { | ||||||
|         HttpsConnector { ssl: s } |         HttpsConnector::with_connector(s, HttpConnector) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<S: SslClient> NetworkConnector for HttpsConnector<S> { | impl<S: SslClient, C: NetworkConnector> HttpsConnector<S, C> { | ||||||
|  |     /// Create a new connector using the provided SSL implementation. | ||||||
|  |     pub fn with_connector(s: S, connector: C) -> HttpsConnector<S, C> { | ||||||
|  |         HttpsConnector { ssl: s, connector: connector } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<S: SslClient, C: NetworkConnector<Stream=HttpStream>> NetworkConnector for HttpsConnector<S, C> { | ||||||
|     type Stream = HttpsStream<S::Stream>; |     type Stream = HttpsStream<S::Stream>; | ||||||
|  |  | ||||||
|     fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream> { |     fn connect(&self, host: &str, port: u16, scheme: &str) -> ::Result<Self::Stream> { | ||||||
|         let addr = &(host, port); |         let stream = try!(self.connector.connect(host, port, "http")); | ||||||
|         if scheme == "https" { |         if scheme == "https" { | ||||||
|             debug!("https scheme"); |             debug!("https scheme"); | ||||||
|             let stream = HttpStream(try!(TcpStream::connect(addr))); |  | ||||||
|             self.ssl.wrap_client(stream, host).map(HttpsStream::Https) |             self.ssl.wrap_client(stream, host).map(HttpsStream::Https) | ||||||
|         } else { |         } else { | ||||||
|             HttpConnector.connect(host, port, scheme).map(HttpsStream::Http) |             Ok(HttpsStream::Http(stream)) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -638,6 +645,31 @@ mod openssl { | |||||||
|         pub context: Arc<SslContext> |         pub context: Arc<SslContext> | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// A client-specific implementation of OpenSSL. | ||||||
|  |     #[derive(Debug, Clone)] | ||||||
|  |     pub struct OpensslClient(SslContext); | ||||||
|  |  | ||||||
|  |     impl Default for OpensslClient { | ||||||
|  |         fn default() -> OpensslClient { | ||||||
|  |             OpensslClient(SslContext::new(SslMethod::Sslv23).unwrap_or_else(|e| { | ||||||
|  |                 // if we cannot create a SslContext, that's because of a | ||||||
|  |                 // serious problem. just crash. | ||||||
|  |                 panic!("{}", e) | ||||||
|  |             })) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     impl<T: NetworkStream + Send + Clone> super::SslClient<T> for OpensslClient { | ||||||
|  |         type Stream = SslStream<T>; | ||||||
|  |  | ||||||
|  |         fn wrap_client(&self, stream: T, host: &str) -> ::Result<Self::Stream> { | ||||||
|  |             let ssl = try!(Ssl::new(&self.0)); | ||||||
|  |             try!(ssl.set_hostname(host)); | ||||||
|  |             SslStream::connect(ssl, stream).map_err(From::from) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     impl Default for Openssl { |     impl Default for Openssl { | ||||||
|         fn default() -> Openssl { |         fn default() -> Openssl { | ||||||
|             Openssl { |             Openssl { | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ use std::thread; | |||||||
| use time::now_utc; | use time::now_utc; | ||||||
|  |  | ||||||
| use header; | use header; | ||||||
| use http::h1::{CR, LF, LINE_ENDING, HttpWriter}; | use http::h1::{LINE_ENDING, HttpWriter}; | ||||||
| use http::h1::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter}; | use http::h1::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter}; | ||||||
| use status; | use status; | ||||||
| use net::{Fresh, Streaming}; | use net::{Fresh, Streaming}; | ||||||
| @@ -82,8 +82,7 @@ impl<'a, W: Any> Response<'a, W> { | |||||||
|  |  | ||||||
|     fn write_head(&mut self) -> io::Result<Body> { |     fn write_head(&mut self) -> io::Result<Body> { | ||||||
|         debug!("writing head: {:?} {:?}", self.version, self.status); |         debug!("writing head: {:?} {:?}", self.version, self.status); | ||||||
|         try!(write!(&mut self.body, "{} {}{}{}", self.version, self.status, |         try!(write!(&mut self.body, "{} {}\r\n", self.version, self.status)); | ||||||
|             CR as char, LF as char)); |  | ||||||
|  |  | ||||||
|         if !self.headers.has::<header::Date>() { |         if !self.headers.has::<header::Date>() { | ||||||
|             self.headers.set(header::Date(header::HttpDate(now_utc()))); |             self.headers.set(header::Date(header::HttpDate(now_utc()))); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user