| @@ -71,10 +71,12 @@ use method::Method; | ||||
| use net::{NetworkConnector, NetworkStream}; | ||||
| use Error; | ||||
|  | ||||
| use self::proxy::tunnel; | ||||
| pub use self::pool::Pool; | ||||
| pub use self::request::Request; | ||||
| pub use self::response::Response; | ||||
|  | ||||
| mod proxy; | ||||
| pub mod pool; | ||||
| pub mod request; | ||||
| pub mod response; | ||||
| @@ -90,7 +92,7 @@ pub struct Client { | ||||
|     redirect_policy: RedirectPolicy, | ||||
|     read_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 { | ||||
| @@ -116,6 +118,15 @@ impl Client { | ||||
|         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. | ||||
|     pub fn with_connector<C, S>(connector: C) -> Client | ||||
|     where C: NetworkConnector<Stream=S> + Send + Sync + 'static, S: NetworkStream + Send { | ||||
| @@ -148,12 +159,6 @@ impl Client { | ||||
|         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. | ||||
|     pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder { | ||||
|         self.request(Method::Get, url) | ||||
| @@ -271,13 +276,12 @@ impl<'a> RequestBuilder<'a> { | ||||
|  | ||||
|         loop { | ||||
|             let mut req = { | ||||
|                 let (scheme, host, port) = match client.proxy { | ||||
|                     Some(ref proxy) => (proxy.0.as_ref(), proxy.1.as_ref(), proxy.2), | ||||
|                     None => { | ||||
|                         let hp = try!(get_host_and_port(&url)); | ||||
|                         (url.scheme(), hp.0, hp.1) | ||||
|                     } | ||||
|                 }; | ||||
|                 let (host, port) = try!(get_host_and_port(&url)); | ||||
|                 let mut message = try!(client.protocol.new_message(&host, port, url.scheme())); | ||||
|                 if url.scheme() == "http" && client.proxy.is_some() { | ||||
|                     message.set_proxied(true); | ||||
|                 } | ||||
|  | ||||
|                 let mut headers = match headers { | ||||
|                     Some(ref headers) => headers.clone(), | ||||
|                     None => Headers::new(), | ||||
| @@ -286,7 +290,6 @@ impl<'a> RequestBuilder<'a> { | ||||
|                     hostname: host.to_owned(), | ||||
|                     port: Some(port), | ||||
|                 }); | ||||
|                 let message = try!(client.protocol.new_message(&host, port, scheme)); | ||||
|                 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)> { | ||||
|     let host = match url.host_str() { | ||||
|         Some(host) => host, | ||||
| @@ -479,8 +483,9 @@ mod tests { | ||||
|     use std::io::Read; | ||||
|     use header::Server; | ||||
|     use http::h1::Http11Message; | ||||
|     use mock::{MockStream}; | ||||
|     use mock::{MockStream, MockSsl}; | ||||
|     use super::{Client, RedirectPolicy}; | ||||
|     use super::proxy::Proxy; | ||||
|     use super::pool::Pool; | ||||
|     use url::Url; | ||||
|  | ||||
| @@ -505,24 +510,61 @@ mod tests { | ||||
|     #[test] | ||||
|     fn test_proxy() { | ||||
|         use super::pool::PooledStream; | ||||
|         type MessageStream = PooledStream<super::proxy::Proxied<MockStream, MockStream>>; | ||||
|         mock_connector!(ProxyConnector { | ||||
|             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)); | ||||
|         client.set_proxy("http", "example.proxy", 8008); | ||||
|         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("http://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap(); | ||||
|  | ||||
|         { | ||||
|             let box_message = client.protocol.new_message("example.proxy", 8008, "http").unwrap(); | ||||
|             let message = box_message.downcast::<Http11Message>().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 box_message = client.protocol.new_message("127.0.0.1", 80, "http").unwrap(); | ||||
|         let message = box_message.downcast::<Http11Message>().unwrap(); | ||||
|         let stream =  message.into_inner().downcast::<MessageStream>().unwrap().into_inner().into_normal().unwrap();; | ||||
|  | ||||
|         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] | ||||
|   | ||||
| @@ -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. | ||||
| #[derive(Debug)] | ||||
| pub struct PooledStream<S> { | ||||
|     inner: Option<PooledStreamInner<S>>, | ||||
|     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] | ||||
|     fn test_proxy() { | ||||
|         let url = Url::parse("http://example.dom").unwrap(); | ||||
|         let proxy_url = Url::parse("http://pro.xy").unwrap(); | ||||
|         let mut req = Request::with_connector( | ||||
|             Get, proxy_url, &mut MockConnector | ||||
|             Get, url, &mut MockConnector | ||||
|         ).unwrap(); | ||||
|         req.url = url; | ||||
|         req.message.set_proxied(true); | ||||
|         let bytes = run_request(req); | ||||
|         let s = from_utf8(&bytes[..]).unwrap(); | ||||
|         let request_line = "GET http://example.dom/ HTTP/1.1"; | ||||
|         assert_eq!(&s[..request_line.len()], request_line); | ||||
|         assert!(s.contains("Host: pro.xy")); | ||||
|         assert!(s.contains("Host: example.dom")); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user