diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 6bd44ef..ecb540b 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -772,7 +772,7 @@ impl Client { } pub(super) fn execute_request(&self, req: Request) -> Pending { - let (method, url, mut headers, body) = req.pieces(); + let (method, url, mut headers, body, timeout) = req.pieces(); // insert default headers in the request headers // without overwriting already appended headers. @@ -816,15 +816,15 @@ impl Client { .body(body.into_stream()) .expect("valid request parts"); + let timeout = timeout + .or(self.inner.request_timeout) + .map(|dur| tokio::time::delay_for(dur)); + + *req.headers_mut() = headers.clone(); let in_flight = self.inner.hyper.request(req); - let timeout = self - .inner - .request_timeout - .map(|dur| tokio::time::delay_for(dur)); - Pending { inner: PendingInner::Request(PendingRequest { method, diff --git a/src/async_impl/request.rs b/src/async_impl/request.rs index 43f5a44..47364f7 100644 --- a/src/async_impl/request.rs +++ b/src/async_impl/request.rs @@ -2,6 +2,7 @@ use std::convert::TryFrom; use std::fmt; use std::future::Future; use std::io::Write; +use std::time::Duration; use base64; use base64::write::EncoderWriter as Base64Encoder; @@ -23,6 +24,7 @@ pub struct Request { url: Url, headers: HeaderMap, body: Option, + timeout: Option, } /// A builder to construct the properties of a `Request`. @@ -40,6 +42,7 @@ impl Request { url, headers: HeaderMap::new(), body: None, + timeout: None } } @@ -91,6 +94,18 @@ impl Request { &mut self.body } + /// Get the timeout. + #[inline] + pub fn timeout(&self) -> Option<&Duration> { + self.timeout.as_ref() + } + + /// Get a mutable reference to the timeout. + #[inline] + pub fn timeout_mut(&mut self) -> &mut Option { + &mut self.timeout + } + /// Attempt to clone the request. /// /// `None` is returned if the request can not be cloned, i.e. if the body is a stream. @@ -100,13 +115,14 @@ impl Request { None => None, }; let mut req = Request::new(self.method().clone(), self.url().clone()); + *req.timeout_mut() = self.timeout().cloned(); *req.headers_mut() = self.headers().clone(); req.body = body; Some(req) } - pub(super) fn pieces(self) -> (Method, Url, HeaderMap, Option) { - (self.method, self.url, self.headers, self.body) + pub(super) fn pieces(self) -> (Method, Url, HeaderMap, Option, Option) { + (self.method, self.url, self.headers, self.body, self.timeout) } } @@ -199,6 +215,18 @@ impl RequestBuilder { self } + /// Enables a request timeout. + /// + /// The timeout is applied from the when the request starts connecting + /// until the response body has finished. It affects only this request + /// and overrides the timeout configured using `ClientBuilder::timeout()`. + pub fn timeout(mut self, timeout: Duration) -> RequestBuilder { + if let Ok(ref mut req) = self.request { + *req.timeout_mut() = Some(timeout); + } + self + } + /// Sends a multipart/form-data body. /// /// ``` diff --git a/tests/timeouts.rs b/tests/timeouts.rs index 2a68d9b..10663d4 100644 --- a/tests/timeouts.rs +++ b/tests/timeouts.rs @@ -4,7 +4,7 @@ use support::*; use std::time::Duration; #[tokio::test] -async fn request_timeout() { +async fn client_timeout() { let _ = env_logger::try_init(); let server = server::http(move |_req| { @@ -30,6 +30,34 @@ async fn request_timeout() { assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } +#[tokio::test] +async fn request_timeout() { + let _ = env_logger::try_init(); + + let server = server::http(move |_req| { + async { + // delay returning the response + tokio::time::delay_for(Duration::from_secs(2)).await; + http::Response::default() + } + }); + + let client = reqwest::Client::builder().build().unwrap(); + + let url = format!("http://{}/slow", server.addr()); + + let res = client + .get(&url) + .timeout(Duration::from_millis(500)) + .send() + .await; + + let err = res.unwrap_err(); + + assert!(err.is_timeout()); + assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); +} + #[tokio::test] async fn response_timeout() { let _ = env_logger::try_init();