feat(lib): switch to non-blocking (asynchronous) IO
BREAKING CHANGE: This breaks a lot of the Client and Server APIs. Check the documentation for how Handlers can be used for asynchronous events.
This commit is contained in:
		
							
								
								
									
										219
									
								
								src/client/connect.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								src/client/connect.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | ||||
| use std::collections::hash_map::{HashMap, Entry}; | ||||
| use std::hash::Hash; | ||||
| use std::fmt; | ||||
| use std::io; | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| use rotor::mio::tcp::TcpStream; | ||||
| use url::Url; | ||||
|  | ||||
| use net::{HttpStream, HttpsStream, Transport, SslClient}; | ||||
| use super::dns::Dns; | ||||
| use super::Registration; | ||||
|  | ||||
| /// A connector creates a Transport to a remote address.. | ||||
| pub trait Connect { | ||||
|     /// Type of Transport to create | ||||
|     type Output: Transport; | ||||
|     /// The key used to determine if an existing socket can be used. | ||||
|     type Key: Eq + Hash + Clone; | ||||
|     /// Returns the key based off the Url. | ||||
|     fn key(&self, &Url) -> Option<Self::Key>; | ||||
|     /// Connect to a remote address. | ||||
|     fn connect(&mut self, &Url) -> io::Result<Self::Key>; | ||||
|     /// Returns a connected socket and associated host. | ||||
|     fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)>; | ||||
|     #[doc(hidden)] | ||||
|     fn register(&mut self, Registration); | ||||
| } | ||||
|  | ||||
| type Scheme = String; | ||||
| type Port = u16; | ||||
|  | ||||
| /// A connector for the `http` scheme. | ||||
| pub struct HttpConnector { | ||||
|     dns: Option<Dns>, | ||||
|     threads: usize, | ||||
|     resolving: HashMap<String, Vec<(&'static str, String, u16)>>, | ||||
| } | ||||
|  | ||||
| impl HttpConnector { | ||||
|     /// Set the number of resolver threads. | ||||
|     /// | ||||
|     /// Default is 4. | ||||
|     pub fn threads(mut self, threads: usize) -> HttpConnector { | ||||
|         debug_assert!(self.dns.is_none(), "setting threads after Dns is created does nothing"); | ||||
|         self.threads = threads; | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for HttpConnector { | ||||
|     fn default() -> HttpConnector { | ||||
|         HttpConnector { | ||||
|             dns: None, | ||||
|             threads: 4, | ||||
|             resolving: HashMap::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for HttpConnector { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("HttpConnector") | ||||
|             .field("threads", &self.threads) | ||||
|             .field("resolving", &self.resolving) | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Connect for HttpConnector { | ||||
|     type Output = HttpStream; | ||||
|     type Key = (&'static str, String, u16); | ||||
|  | ||||
|     fn key(&self, url: &Url) -> Option<Self::Key> { | ||||
|         if url.scheme() == "http" { | ||||
|             Some(( | ||||
|                 "http", | ||||
|                 url.host_str().expect("http scheme must have host").to_owned(), | ||||
|                 url.port().unwrap_or(80), | ||||
|             )) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn connect(&mut self, url: &Url) -> io::Result<Self::Key> { | ||||
|         debug!("Http::connect({:?})", url); | ||||
|         if let Some(key) = self.key(url) { | ||||
|             let host = url.host_str().expect("http scheme must have a host"); | ||||
|             self.dns.as_ref().expect("dns workers lost").resolve(host); | ||||
|             self.resolving.entry(host.to_owned()).or_insert(Vec::new()).push(key.clone()); | ||||
|             Ok(key) | ||||
|         } else { | ||||
|             Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http")) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn connected(&mut self) -> Option<(Self::Key, io::Result<HttpStream>)> { | ||||
|         let (host, addr) = match self.dns.as_ref().expect("dns workers lost").resolved() { | ||||
|             Ok(res) => res, | ||||
|             Err(_) => return None | ||||
|         }; | ||||
|         debug!("Http::resolved <- ({:?}, {:?})", host, addr); | ||||
|         match self.resolving.entry(host) { | ||||
|             Entry::Occupied(mut entry) => { | ||||
|                 let resolved = entry.get_mut().remove(0); | ||||
|                 if entry.get().is_empty() { | ||||
|                     entry.remove(); | ||||
|                 } | ||||
|                 let port = resolved.2; | ||||
|                 match addr { | ||||
|                     Ok(addr) => { | ||||
|                         Some((resolved, TcpStream::connect(&SocketAddr::new(addr, port)) | ||||
|                             .map(HttpStream))) | ||||
|                     }, | ||||
|                     Err(e) => Some((resolved, Err(e))) | ||||
|                 } | ||||
|             } | ||||
|             _ => { | ||||
|                 trace!("^--  resolved but not in hashmap?"); | ||||
|                 return None | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn register(&mut self, reg: Registration) { | ||||
|         self.dns = Some(Dns::new(reg.notify, 4)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A connector that can protect HTTP streams using SSL. | ||||
| #[derive(Debug, Default)] | ||||
| pub struct HttpsConnector<S: SslClient> { | ||||
|     http: HttpConnector, | ||||
|     ssl: S | ||||
| } | ||||
|  | ||||
| impl<S: SslClient> HttpsConnector<S> { | ||||
|     /// Create a new connector using the provided SSL implementation. | ||||
|     pub fn new(s: S) -> HttpsConnector<S> { | ||||
|         HttpsConnector { | ||||
|             http: HttpConnector::default(), | ||||
|             ssl: s, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S: SslClient> Connect for HttpsConnector<S> { | ||||
|     type Output = HttpsStream<S::Stream>; | ||||
|     type Key = (&'static str, String, u16); | ||||
|  | ||||
|     fn key(&self, url: &Url) -> Option<Self::Key> { | ||||
|         let scheme = match url.scheme() { | ||||
|             "http" => "http", | ||||
|             "https" => "https", | ||||
|             _ => return None | ||||
|         }; | ||||
|         Some(( | ||||
|             scheme, | ||||
|             url.host_str().expect("http scheme must have host").to_owned(), | ||||
|             url.port_or_known_default().expect("http scheme must have a port"), | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     fn connect(&mut self, url: &Url) -> io::Result<Self::Key> { | ||||
|         debug!("Https::connect({:?})", url); | ||||
|         if let Some(key) = self.key(url) { | ||||
|             let host = url.host_str().expect("http scheme must have a host"); | ||||
|             self.http.dns.as_ref().expect("dns workers lost").resolve(host); | ||||
|             self.http.resolving.entry(host.to_owned()).or_insert(Vec::new()).push(key.clone()); | ||||
|             Ok(key) | ||||
|         } else { | ||||
|             Err(io::Error::new(io::ErrorKind::InvalidInput, "scheme must be http or https")) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn connected(&mut self) -> Option<(Self::Key, io::Result<Self::Output>)> { | ||||
|         self.http.connected().map(|(key, res)| { | ||||
|             let res = res.and_then(|http| { | ||||
|                 if key.0 == "https" { | ||||
|                     self.ssl.wrap_client(http, &key.1) | ||||
|                         .map(HttpsStream::Https) | ||||
|                         .map_err(|e| match e { | ||||
|                             ::Error::Io(e) => e, | ||||
|                             e => io::Error::new(io::ErrorKind::Other, e) | ||||
|                         }) | ||||
|                 } else { | ||||
|                     Ok(HttpsStream::Http(http)) | ||||
|                 } | ||||
|             }); | ||||
|             (key, res) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     fn register(&mut self, reg: Registration) { | ||||
|         self.http.register(reg); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(not(any(feature = "openssl", feature = "security-framework")))] | ||||
| #[doc(hidden)] | ||||
| pub type DefaultConnector = HttpConnector; | ||||
|  | ||||
| #[cfg(all(feature = "openssl", not(feature = "security-framework")))] | ||||
| #[doc(hidden)] | ||||
| pub type DefaultConnector = HttpsConnector<::net::Openssl>; | ||||
|  | ||||
| #[cfg(feature = "security-framework")] | ||||
| #[doc(hidden)] | ||||
| pub type DefaultConnector = HttpsConnector<::net::SecureTransportClient>; | ||||
|  | ||||
| #[doc(hidden)] | ||||
| pub type DefaultTransport = <DefaultConnector as Connect>::Output; | ||||
|  | ||||
| fn _assert_defaults() { | ||||
|     fn _assert<T, U>() where T: Connect<Output=U>, U: Transport {} | ||||
|  | ||||
|     _assert::<DefaultConnector, DefaultTransport>(); | ||||
| } | ||||
							
								
								
									
										84
									
								
								src/client/dns.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/client/dns.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| use std::io; | ||||
| use std::net::{IpAddr, ToSocketAddrs}; | ||||
| use std::thread; | ||||
|  | ||||
| use ::spmc; | ||||
|  | ||||
| use http::channel; | ||||
|  | ||||
| pub struct Dns { | ||||
|     tx: spmc::Sender<String>, | ||||
|     rx: channel::Receiver<Answer>, | ||||
| } | ||||
|  | ||||
| pub type Answer = (String, io::Result<IpAddr>); | ||||
|  | ||||
| impl Dns { | ||||
|     pub fn new(notify: (channel::Sender<Answer>, channel::Receiver<Answer>), threads: usize) -> Dns { | ||||
|         let (tx, rx) = spmc::channel(); | ||||
|         for _ in 0..threads { | ||||
|             work(rx.clone(), notify.0.clone()); | ||||
|         } | ||||
|         Dns { | ||||
|             tx: tx, | ||||
|             rx: notify.1, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn resolve<T: Into<String>>(&self, hostname: T) { | ||||
|         self.tx.send(hostname.into()).expect("Workers all died horribly"); | ||||
|     } | ||||
|  | ||||
|     pub fn resolved(&self) -> Result<Answer, channel::TryRecvError> { | ||||
|         self.rx.try_recv() | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn work(rx: spmc::Receiver<String>, notify: channel::Sender<Answer>) { | ||||
|     thread::spawn(move || { | ||||
|         let mut worker = Worker::new(rx, notify); | ||||
|         let rx = worker.rx.as_ref().expect("Worker lost rx"); | ||||
|         let notify = worker.notify.as_ref().expect("Worker lost notify"); | ||||
|         while let Ok(host) = rx.recv() { | ||||
|             debug!("resolve {:?}", host); | ||||
|             let res = match (&*host, 80).to_socket_addrs().map(|mut i| i.next()) { | ||||
|                 Ok(Some(addr)) => (host, Ok(addr.ip())), | ||||
|                 Ok(None) => (host, Err(io::Error::new(io::ErrorKind::Other, "no addresses found"))), | ||||
|                 Err(e) => (host, Err(e)) | ||||
|             }; | ||||
|  | ||||
|             if let Err(_) = notify.send(res) { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         worker.shutdown = true; | ||||
|     }); | ||||
| } | ||||
|  | ||||
| struct Worker { | ||||
|     rx: Option<spmc::Receiver<String>>, | ||||
|     notify: Option<channel::Sender<Answer>>, | ||||
|     shutdown: bool, | ||||
| } | ||||
|  | ||||
| impl Worker { | ||||
|     fn new(rx: spmc::Receiver<String>, notify: channel::Sender<Answer>) -> Worker { | ||||
|         Worker { | ||||
|             rx: Some(rx), | ||||
|             notify: Some(notify), | ||||
|             shutdown: false, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Drop for Worker { | ||||
|     fn drop(&mut self) { | ||||
|         if !self.shutdown { | ||||
|             trace!("Worker.drop panicked, restarting"); | ||||
|             work(self.rx.take().expect("Worker lost rx"), | ||||
|                 self.notify.take().expect("Worker lost notify")); | ||||
|         } else { | ||||
|             trace!("Worker.drop shutdown, closing"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1048
									
								
								src/client/mod.rs
									
									
									
									
									
								
							
							
						
						
									
										1048
									
								
								src/client/mod.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,240 +0,0 @@ | ||||
| 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()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,177 +1,52 @@ | ||||
| //! Client Requests | ||||
| use std::marker::PhantomData; | ||||
| use std::io::{self, Write}; | ||||
|  | ||||
| use std::time::Duration; | ||||
|  | ||||
| use url::Url; | ||||
|  | ||||
| use method::Method; | ||||
| use header::Headers; | ||||
| use header::Host; | ||||
| use net::{NetworkStream, NetworkConnector, DefaultConnector, Fresh, Streaming}; | ||||
| use version; | ||||
| use client::{Response, get_host_and_port}; | ||||
| use http::RequestHead; | ||||
| use method::Method; | ||||
| use uri::RequestUri; | ||||
| use version::HttpVersion; | ||||
|  | ||||
| use http::{HttpMessage, RequestHead}; | ||||
| use http::h1::Http11Message; | ||||
|  | ||||
|  | ||||
| /// A client request to a remote server. | ||||
| /// The W type tracks the state of the request, Fresh vs Streaming. | ||||
| pub struct Request<W> { | ||||
|     /// The target URI for this request. | ||||
|     pub url: Url, | ||||
|  | ||||
|     /// The HTTP version of this request. | ||||
|     pub version: version::HttpVersion, | ||||
|  | ||||
|     message: Box<HttpMessage>, | ||||
|     headers: Headers, | ||||
|     method: Method, | ||||
|  | ||||
|     _marker: PhantomData<W>, | ||||
| #[derive(Debug)] | ||||
| pub struct Request<'a> { | ||||
|     head: &'a mut RequestHead | ||||
| } | ||||
|  | ||||
| impl<W> Request<W> { | ||||
| impl<'a> Request<'a> { | ||||
|     /// Read the Request Url. | ||||
|     #[inline] | ||||
|     pub fn uri(&self) -> &RequestUri { &self.head.subject.1 } | ||||
|  | ||||
|     /// Readthe Request Version. | ||||
|     #[inline] | ||||
|     pub fn version(&self) -> &HttpVersion { &self.head.version } | ||||
|  | ||||
|     /// Read the Request headers. | ||||
|     #[inline] | ||||
|     pub fn headers(&self) -> &Headers { &self.headers } | ||||
|     pub fn headers(&self) -> &Headers { &self.head.headers } | ||||
|  | ||||
|     /// Read the Request method. | ||||
|     #[inline] | ||||
|     pub fn method(&self) -> Method { self.method.clone() } | ||||
|     pub fn method(&self) -> &Method { &self.head.subject.0 } | ||||
|  | ||||
|     /// Set the write timeout. | ||||
|     /// Set the Method of this request. | ||||
|     #[inline] | ||||
|     pub fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()> { | ||||
|         self.message.set_write_timeout(dur) | ||||
|     } | ||||
|  | ||||
|     /// Set the read timeout. | ||||
|     #[inline] | ||||
|     pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> { | ||||
|         self.message.set_read_timeout(dur) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Request<Fresh> { | ||||
|     /// Create a new `Request<Fresh>` that will use the given `HttpMessage` for its communication | ||||
|     /// with the server. This implies that the given `HttpMessage` instance has already been | ||||
|     /// properly initialized by the caller (e.g. a TCP connection's already established). | ||||
|     pub fn with_message(method: Method, url: Url, message: Box<HttpMessage>) | ||||
|             -> ::Result<Request<Fresh>> { | ||||
|         let mut headers = Headers::new(); | ||||
|         { | ||||
|             let (host, port) = try!(get_host_and_port(&url)); | ||||
|             headers.set(Host { | ||||
|                 hostname: host.to_owned(), | ||||
|                 port: Some(port), | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         Ok(Request::with_headers_and_message(method, url, headers, message)) | ||||
|     } | ||||
|  | ||||
|     #[doc(hidden)] | ||||
|     pub fn with_headers_and_message(method: Method, url: Url, headers: Headers,  message: Box<HttpMessage>) | ||||
|                 -> Request<Fresh> { | ||||
|         Request { | ||||
|             method: method, | ||||
|             headers: headers, | ||||
|             url: url, | ||||
|             version: version::HttpVersion::Http11, | ||||
|             message: message, | ||||
|             _marker: PhantomData, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Create a new client request. | ||||
|     pub fn new(method: Method, url: Url) -> ::Result<Request<Fresh>> { | ||||
|         let conn = DefaultConnector::default(); | ||||
|         Request::with_connector(method, url, &conn) | ||||
|     } | ||||
|  | ||||
|     /// Create a new client request with a specific underlying NetworkStream. | ||||
|     pub fn with_connector<C, S>(method: Method, url: Url, connector: &C) | ||||
|         -> ::Result<Request<Fresh>> where | ||||
|         C: NetworkConnector<Stream=S>, | ||||
|         S: Into<Box<NetworkStream + Send>> { | ||||
|         let stream = { | ||||
|             let (host, port) = try!(get_host_and_port(&url)); | ||||
|             try!(connector.connect(host, port, url.scheme())).into() | ||||
|         }; | ||||
|  | ||||
|         Request::with_message(method, url, Box::new(Http11Message::with_stream(stream))) | ||||
|     } | ||||
|  | ||||
|     /// Consume a Fresh Request, writing the headers and method, | ||||
|     /// returning a Streaming Request. | ||||
|     pub fn start(mut self) -> ::Result<Request<Streaming>> { | ||||
|         let head = match self.message.set_outgoing(RequestHead { | ||||
|             headers: self.headers, | ||||
|             method: self.method, | ||||
|             url: self.url, | ||||
|         }) { | ||||
|             Ok(head) => head, | ||||
|             Err(e) => { | ||||
|                 let _ = self.message.close_connection(); | ||||
|                 return Err(From::from(e)); | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         Ok(Request { | ||||
|             method: head.method, | ||||
|             headers: head.headers, | ||||
|             url: head.url, | ||||
|             version: self.version, | ||||
|             message: self.message, | ||||
|             _marker: PhantomData, | ||||
|         }) | ||||
|     } | ||||
|     pub fn set_method(&mut self, method: Method) { self.head.subject.0 = method; } | ||||
|  | ||||
|     /// Get a mutable reference to the Request headers. | ||||
|     #[inline] | ||||
|     pub fn headers_mut(&mut self) -> &mut Headers { &mut self.headers } | ||||
|     pub fn headers_mut(&mut self) -> &mut Headers { &mut self.head.headers } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| impl Request<Streaming> { | ||||
|     /// Completes writing the request, and returns a response to read from. | ||||
|     /// | ||||
|     /// Consumes the Request. | ||||
|     pub fn send(self) -> ::Result<Response> { | ||||
|         Response::with_message(self.url, self.message) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Write for Request<Streaming> { | ||||
|     #[inline] | ||||
|     fn write(&mut self, msg: &[u8]) -> io::Result<usize> { | ||||
|         match self.message.write(msg) { | ||||
|             Ok(n) => Ok(n), | ||||
|             Err(e) => { | ||||
|                 let _ = self.message.close_connection(); | ||||
|                 Err(e) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         match self.message.flush() { | ||||
|             Ok(r) => Ok(r), | ||||
|             Err(e) => { | ||||
|                 let _ = self.message.close_connection(); | ||||
|                 Err(e) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| pub fn new(head: &mut RequestHead) -> Request { | ||||
|     Request { head: head } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     /* | ||||
|     use std::io::Write; | ||||
|     use std::str::from_utf8; | ||||
|     use url::Url; | ||||
| @@ -311,4 +186,5 @@ mod tests { | ||||
|             .get_ref().downcast_ref::<MockStream>().unwrap() | ||||
|             .is_closed); | ||||
|     } | ||||
|     */ | ||||
| } | ||||
|   | ||||
| @@ -1,82 +1,57 @@ | ||||
| //! Client Responses | ||||
| use std::io::{self, Read}; | ||||
|  | ||||
| use url::Url; | ||||
|  | ||||
| use header; | ||||
| use net::NetworkStream; | ||||
| use http::{self, RawStatus, ResponseHead, HttpMessage}; | ||||
| use http::h1::Http11Message; | ||||
| //use net::NetworkStream; | ||||
| use http::{self, RawStatus}; | ||||
| use status; | ||||
| use version; | ||||
|  | ||||
| pub fn new(incoming: http::ResponseHead) -> Response { | ||||
|     trace!("Response::new"); | ||||
|     let status = status::StatusCode::from_u16(incoming.subject.0); | ||||
|     debug!("version={:?}, status={:?}", incoming.version, status); | ||||
|     debug!("headers={:?}", incoming.headers); | ||||
|  | ||||
|     Response { | ||||
|         status: status, | ||||
|         version: incoming.version, | ||||
|         headers: incoming.headers, | ||||
|         status_raw: incoming.subject, | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| /// A response for a client request to a remote server. | ||||
| #[derive(Debug)] | ||||
| pub struct Response { | ||||
|     /// The status from the server. | ||||
|     pub status: status::StatusCode, | ||||
|     /// The headers from the server. | ||||
|     pub headers: header::Headers, | ||||
|     /// The HTTP version of this response from the server. | ||||
|     pub version: version::HttpVersion, | ||||
|     /// The final URL of this response. | ||||
|     pub url: Url, | ||||
|     status: status::StatusCode, | ||||
|     headers: header::Headers, | ||||
|     version: version::HttpVersion, | ||||
|     status_raw: RawStatus, | ||||
|     message: Box<HttpMessage>, | ||||
| } | ||||
|  | ||||
| impl Response { | ||||
|     /// Get the headers from the server. | ||||
|     #[inline] | ||||
|     pub fn headers(&self) -> &header::Headers { &self.headers } | ||||
|  | ||||
|     /// Creates a new response from a server. | ||||
|     pub fn new(url: Url, stream: Box<NetworkStream + Send>) -> ::Result<Response> { | ||||
|         trace!("Response::new"); | ||||
|         Response::with_message(url, Box::new(Http11Message::with_stream(stream))) | ||||
|     } | ||||
|  | ||||
|     /// Creates a new response received from the server on the given `HttpMessage`. | ||||
|     pub fn with_message(url: Url, mut message: Box<HttpMessage>) -> ::Result<Response> { | ||||
|         trace!("Response::with_message"); | ||||
|         let ResponseHead { headers, raw_status, version } = match message.get_incoming() { | ||||
|             Ok(head) => head, | ||||
|             Err(e) => { | ||||
|                 let _ = message.close_connection(); | ||||
|                 return Err(From::from(e)); | ||||
|             } | ||||
|         }; | ||||
|         let status = status::StatusCode::from_u16(raw_status.0); | ||||
|         debug!("version={:?}, status={:?}", version, status); | ||||
|         debug!("headers={:?}", headers); | ||||
|  | ||||
|         Ok(Response { | ||||
|             status: status, | ||||
|             version: version, | ||||
|             headers: headers, | ||||
|             url: url, | ||||
|             status_raw: raw_status, | ||||
|             message: message, | ||||
|         }) | ||||
|     } | ||||
|     /// Get the status from the server. | ||||
|     #[inline] | ||||
|     pub fn status(&self) -> &status::StatusCode { &self.status } | ||||
|  | ||||
|     /// Get the raw status code and reason. | ||||
|     #[inline] | ||||
|     pub fn status_raw(&self) -> &RawStatus { | ||||
|         &self.status_raw | ||||
|     } | ||||
| } | ||||
|     pub fn status_raw(&self) -> &RawStatus { &self.status_raw } | ||||
|  | ||||
| impl Read for Response { | ||||
|     /// Get the final URL of this response. | ||||
|     #[inline] | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         match self.message.read(buf) { | ||||
|             Err(e) => { | ||||
|                 let _ = self.message.close_connection(); | ||||
|                 Err(e) | ||||
|             } | ||||
|             r => r | ||||
|         } | ||||
|     } | ||||
|     //pub fn url(&self) -> &Url { &self.url } | ||||
|  | ||||
|     /// Get the HTTP version of this response from the server. | ||||
|     #[inline] | ||||
|     pub fn version(&self) -> &version::HttpVersion { &self.version } | ||||
| } | ||||
|  | ||||
| /* | ||||
| impl Drop for Response { | ||||
|     fn drop(&mut self) { | ||||
|         // if not drained, theres old bits in the Reader. we can't reuse this, | ||||
| @@ -94,9 +69,11 @@ impl Drop for Response { | ||||
|         } | ||||
|     } | ||||
| } | ||||
| */ | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     /* | ||||
|     use std::io::{self, Read}; | ||||
|  | ||||
|     use url::Url; | ||||
| @@ -230,4 +207,5 @@ mod tests { | ||||
|  | ||||
|         assert!(Response::new(url, Box::new(stream)).is_err()); | ||||
|     } | ||||
|     */ | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user