| @@ -26,6 +26,7 @@ use hyper::client::ResponseFuture; | |||||||
| use mime; | use mime; | ||||||
| #[cfg(feature = "default-tls")] | #[cfg(feature = "default-tls")] | ||||||
| use native_tls::TlsConnector; | use native_tls::TlsConnector; | ||||||
|  | use tokio::{clock, timer::Delay}; | ||||||
|  |  | ||||||
|  |  | ||||||
| use super::request::{Request, RequestBuilder}; | use super::request::{Request, RequestBuilder}; | ||||||
| @@ -212,14 +213,15 @@ impl ClientBuilder { | |||||||
|  |  | ||||||
|         Ok(Client { |         Ok(Client { | ||||||
|             inner: Arc::new(ClientRef { |             inner: Arc::new(ClientRef { | ||||||
|  |                 cookie_store, | ||||||
|                 gzip: config.gzip, |                 gzip: config.gzip, | ||||||
|                 hyper: hyper_client, |                 hyper: hyper_client, | ||||||
|                 headers: config.headers, |                 headers: config.headers, | ||||||
|                 redirect_policy: config.redirect_policy, |                 redirect_policy: config.redirect_policy, | ||||||
|                 referer: config.referer, |                 referer: config.referer, | ||||||
|  |                 request_timeout: config.timeout, | ||||||
|                 proxies, |                 proxies, | ||||||
|                 proxies_maybe_http_auth, |                 proxies_maybe_http_auth, | ||||||
|                 cookie_store, |  | ||||||
|             }), |             }), | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| @@ -341,16 +343,20 @@ impl ClientBuilder { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Currently not used, so hide from docs. |     /// Enables a request timeout. | ||||||
|     #[doc(hidden)] |     /// | ||||||
|  |     /// The timeout is applied from the when the request starts connecting | ||||||
|  |     /// until the response headers are received. Bodies are not affected. | ||||||
|  |     /// | ||||||
|  |     /// Default is no timeout. | ||||||
|     pub fn timeout(mut self, timeout: Duration) -> ClientBuilder { |     pub fn timeout(mut self, timeout: Duration) -> ClientBuilder { | ||||||
|         self.config.timeout = Some(timeout); |         self.config.timeout = Some(timeout); | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Sets the maximum idle connection per host allowed in the pool. |     /// Sets the maximum idle connection per host allowed in the pool. | ||||||
|     // |     /// | ||||||
|     // Default is usize::MAX (no limit). |     /// Default is usize::MAX (no limit). | ||||||
|     pub fn max_idle_per_host(mut self, max: usize) -> ClientBuilder { |     pub fn max_idle_per_host(mut self, max: usize) -> ClientBuilder { | ||||||
|         self.config.max_idle_per_host = max; |         self.config.max_idle_per_host = max; | ||||||
|         self |         self | ||||||
| @@ -585,6 +591,10 @@ impl Client { | |||||||
|  |  | ||||||
|         let in_flight = self.inner.hyper.request(req); |         let in_flight = self.inner.hyper.request(req); | ||||||
|  |  | ||||||
|  |         let timeout = self.inner.request_timeout.map(|dur| { | ||||||
|  |             Delay::new(clock::now() + dur) | ||||||
|  |         }); | ||||||
|  |  | ||||||
|         Pending { |         Pending { | ||||||
|             inner: PendingInner::Request(PendingRequest { |             inner: PendingInner::Request(PendingRequest { | ||||||
|                 method: method, |                 method: method, | ||||||
| @@ -597,6 +607,7 @@ impl Client { | |||||||
|                 client: self.inner.clone(), |                 client: self.inner.clone(), | ||||||
|  |  | ||||||
|                 in_flight: in_flight, |                 in_flight: in_flight, | ||||||
|  |                 timeout, | ||||||
|             }), |             }), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -654,17 +665,18 @@ impl fmt::Debug for ClientBuilder { | |||||||
| } | } | ||||||
|  |  | ||||||
| struct ClientRef { | struct ClientRef { | ||||||
|  |     cookie_store: Option<RwLock<cookie::CookieStore>>, | ||||||
|     gzip: bool, |     gzip: bool, | ||||||
|     headers: HeaderMap, |     headers: HeaderMap, | ||||||
|     hyper: HyperClient, |     hyper: HyperClient, | ||||||
|     redirect_policy: RedirectPolicy, |     redirect_policy: RedirectPolicy, | ||||||
|     referer: bool, |     referer: bool, | ||||||
|  |     request_timeout: Option<Duration>, | ||||||
|     proxies: Arc<Vec<Proxy>>, |     proxies: Arc<Vec<Proxy>>, | ||||||
|     proxies_maybe_http_auth: bool, |     proxies_maybe_http_auth: bool, | ||||||
|     cookie_store: Option<RwLock<cookie::CookieStore>>, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| pub struct Pending { | pub(super) struct Pending { | ||||||
|     inner: PendingInner, |     inner: PendingInner, | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -684,6 +696,7 @@ struct PendingRequest { | |||||||
|     client: Arc<ClientRef>, |     client: Arc<ClientRef>, | ||||||
|  |  | ||||||
|     in_flight: ResponseFuture, |     in_flight: ResponseFuture, | ||||||
|  |     timeout: Option<Delay>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Pending { | impl Pending { | ||||||
| @@ -711,6 +724,12 @@ impl Future for PendingRequest { | |||||||
|     type Error = ::Error; |     type Error = ::Error; | ||||||
|  |  | ||||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { |     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||||
|  |         if let Some(ref mut delay) = self.timeout { | ||||||
|  |             if let Async::Ready(()) = try_!(delay.poll(), &self.url) { | ||||||
|  |                 return Err(::error::timedout(Some(self.url.clone()))); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         loop { |         loop { | ||||||
|             let res = match try_!(self.in_flight.poll(), &self.url) { |             let res = match try_!(self.in_flight.poll(), &self.url) { | ||||||
|                 Async::Ready(res) => res, |                 Async::Ready(res) => res, | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								src/error.rs
									
									
									
									
									
								
							| @@ -150,7 +150,8 @@ impl Error { | |||||||
|             Kind::RedirectLoop | |             Kind::RedirectLoop | | ||||||
|             Kind::ClientError(_) | |             Kind::ClientError(_) | | ||||||
|             Kind::ServerError(_) | |             Kind::ServerError(_) | | ||||||
|             Kind::UnknownProxyScheme => None, |             Kind::UnknownProxyScheme | | ||||||
|  |             Kind::Timer => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -275,6 +276,7 @@ impl fmt::Display for Error { | |||||||
|                 fmt::Display::fmt(code, f) |                 fmt::Display::fmt(code, f) | ||||||
|             } |             } | ||||||
|             Kind::UnknownProxyScheme => f.write_str("Unknown proxy scheme"), |             Kind::UnknownProxyScheme => f.write_str("Unknown proxy scheme"), | ||||||
|  |             Kind::Timer => f.write_str("timer unavailable"), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -303,6 +305,7 @@ impl StdError for Error { | |||||||
|             Kind::ClientError(_) => "Client Error", |             Kind::ClientError(_) => "Client Error", | ||||||
|             Kind::ServerError(_) => "Server Error", |             Kind::ServerError(_) => "Server Error", | ||||||
|             Kind::UnknownProxyScheme => "Unknown proxy scheme", |             Kind::UnknownProxyScheme => "Unknown proxy scheme", | ||||||
|  |             Kind::Timer => "timer unavailable", | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -329,7 +332,8 @@ impl StdError for Error { | |||||||
|             Kind::RedirectLoop | |             Kind::RedirectLoop | | ||||||
|             Kind::ClientError(_) | |             Kind::ClientError(_) | | ||||||
|             Kind::ServerError(_) | |             Kind::ServerError(_) | | ||||||
|             Kind::UnknownProxyScheme => None, |             Kind::UnknownProxyScheme | | ||||||
|  |             Kind::Timer => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -357,6 +361,7 @@ pub(crate) enum Kind { | |||||||
|     ClientError(StatusCode), |     ClientError(StatusCode), | ||||||
|     ServerError(StatusCode), |     ServerError(StatusCode), | ||||||
|     UnknownProxyScheme, |     UnknownProxyScheme, | ||||||
|  |     Timer, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -433,6 +438,12 @@ where T: Into<Kind> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl From<::tokio::timer::Error> for Kind { | ||||||
|  |     fn from(_err: ::tokio::timer::Error) -> Kind { | ||||||
|  |         Kind::Timer | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| fn io_timeout() -> io::Error { | fn io_timeout() -> io::Error { | ||||||
|     io::Error::new(io::ErrorKind::TimedOut, "timed out") |     io::Error::new(io::ErrorKind::TimedOut, "timed out") | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,24 +7,27 @@ extern crate tokio; | |||||||
| #[macro_use] | #[macro_use] | ||||||
| mod support; | mod support; | ||||||
|  |  | ||||||
| use reqwest::async::Client; |  | ||||||
| use reqwest::async::multipart::{Form, Part}; |  | ||||||
| use futures::{Future, Stream}; |  | ||||||
| use std::io::Write; | use std::io::Write; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
|  | use futures::{Future, Stream}; | ||||||
|  | use tokio::runtime::current_thread::Runtime; | ||||||
|  |  | ||||||
|  | use reqwest::async::Client; | ||||||
|  | use reqwest::async::multipart::{Form, Part}; | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn async_test_gzip_response() { | fn gzip_response() { | ||||||
|     test_gzip(10_000, 4096); |     gzip_case(10_000, 4096); | ||||||
| } | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn async_test_gzip_single_byte_chunks() { | fn gzip_single_byte_chunks() { | ||||||
|     test_gzip(10, 1); |     gzip_case(10, 1); | ||||||
| } | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn async_test_multipart() { | fn multipart() { | ||||||
|     let _ = env_logger::try_init(); |     let _ = env_logger::try_init(); | ||||||
|  |  | ||||||
|     let stream = futures::stream::once::<_, hyper::Error>(Ok(hyper::Chunk::from("part1 part2".to_owned()))); |     let stream = futures::stream::once::<_, hyper::Error>(Ok(hyper::Chunk::from("part1 part2".to_owned()))); | ||||||
| @@ -78,7 +81,7 @@ fn async_test_multipart() { | |||||||
|  |  | ||||||
|     let url = format!("http://{}/multipart/1", server.addr()); |     let url = format!("http://{}/multipart/1", server.addr()); | ||||||
|  |  | ||||||
|     let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); |     let mut rt = Runtime::new().expect("new rt"); | ||||||
|  |  | ||||||
|     let client = Client::new(); |     let client = Client::new(); | ||||||
|  |  | ||||||
| @@ -95,7 +98,47 @@ fn async_test_multipart() { | |||||||
|     rt.block_on(res_future).unwrap(); |     rt.block_on(res_future).unwrap(); | ||||||
| } | } | ||||||
|  |  | ||||||
| fn test_gzip(response_size: usize, chunk_size: usize) { | #[test] | ||||||
|  | fn request_timeout() { | ||||||
|  |     let _ = env_logger::try_init(); | ||||||
|  |  | ||||||
|  |     let server = server! { | ||||||
|  |         request: b"\ | ||||||
|  |             GET /slow HTTP/1.1\r\n\ | ||||||
|  |             user-agent: $USERAGENT\r\n\ | ||||||
|  |             accept: */*\r\n\ | ||||||
|  |             accept-encoding: gzip\r\n\ | ||||||
|  |             host: $HOST\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             ", | ||||||
|  |         response: b"\ | ||||||
|  |             HTTP/1.1 200 OK\r\n\ | ||||||
|  |             Content-Length: 5\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             Hello\ | ||||||
|  |             ", | ||||||
|  |         read_timeout: Duration::from_secs(2) | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let mut rt = Runtime::new().expect("new rt"); | ||||||
|  |  | ||||||
|  |     let client = Client::builder() | ||||||
|  |         .timeout(Duration::from_millis(500)) | ||||||
|  |         .build() | ||||||
|  |         .unwrap(); | ||||||
|  |  | ||||||
|  |     let url = format!("http://{}/slow", server.addr()); | ||||||
|  |     let fut = client | ||||||
|  |         .get(&url) | ||||||
|  |         .send(); | ||||||
|  |  | ||||||
|  |     let err = rt.block_on(fut).unwrap_err(); | ||||||
|  |  | ||||||
|  |     assert!(err.is_timeout()); | ||||||
|  |     assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn gzip_case(response_size: usize, chunk_size: usize) { | ||||||
|     let content: String = (0..response_size).into_iter().map(|i| format!("test {}", i)).collect(); |     let content: String = (0..response_size).into_iter().map(|i| format!("test {}", i)).collect(); | ||||||
|     let mut encoder = ::libflate::gzip::Encoder::new(Vec::new()).unwrap(); |     let mut encoder = ::libflate::gzip::Encoder::new(Vec::new()).unwrap(); | ||||||
|     match encoder.write(content.as_bytes()) { |     match encoder.write(content.as_bytes()) { | ||||||
| @@ -128,7 +171,7 @@ fn test_gzip(response_size: usize, chunk_size: usize) { | |||||||
|         response: response |         response: response | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let mut rt = tokio::runtime::current_thread::Runtime::new().expect("new rt"); |     let mut rt = Runtime::new().expect("new rt"); | ||||||
|  |  | ||||||
|     let client = Client::new(); |     let client = Client::new(); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user