diff --git a/src/client/connect/http.rs b/src/client/connect/http.rs index a91026d72f..f431f09c7c 100644 --- a/src/client/connect/http.rs +++ b/src/client/connect/http.rs @@ -29,6 +29,7 @@ use super::dns::{self, GaiResolver, Resolve, TokioThreadpoolGaiResolver}; pub struct HttpConnector { enforce_http: bool, handle: Option, + resolve_timeout: Option, connect_timeout: Option, happy_eyeballs_timeout: Option, keep_alive_timeout: Option, @@ -121,6 +122,7 @@ impl HttpConnector { HttpConnector { enforce_http: true, handle: None, + resolve_timeout: None, connect_timeout: None, happy_eyeballs_timeout: Some(Duration::from_millis(300)), keep_alive_timeout: None, @@ -189,6 +191,17 @@ impl HttpConnector { self.local_address = addr; } + /// Set timeout for hostname resolution. + /// + /// If `None`, then no timeout is applied by the connector, making it + /// subject to the timeout imposed by the operating system. + /// + /// Default is `None`. + #[inline] + pub fn set_resolve_timeout(&mut self, dur: Option) { + self.resolve_timeout = dur; + } + /// Set the connect timeout. /// /// If a domain resolves to multiple IP addresses, the timeout will be @@ -272,6 +285,7 @@ where HttpConnecting { state: State::Lazy(self.resolver.clone(), host.into(), self.local_address), handle: self.handle.clone(), + resolve_timeout: self.resolve_timeout, connect_timeout: self.connect_timeout, happy_eyeballs_timeout: self.happy_eyeballs_timeout, keep_alive_timeout: self.keep_alive_timeout, @@ -299,6 +313,7 @@ fn invalid_url(err: InvalidUrl, handle: &Option) -> HttpConn keep_alive_timeout: None, nodelay: false, port: 0, + resolve_timeout: None, connect_timeout: None, happy_eyeballs_timeout: None, reuse_address: false, @@ -334,6 +349,7 @@ impl StdError for InvalidUrl { pub struct HttpConnecting { state: State, handle: Option, + resolve_timeout: Option, connect_timeout: Option, happy_eyeballs_timeout: Option, keep_alive_timeout: Option, @@ -346,11 +362,16 @@ pub struct HttpConnecting { enum State { Lazy(R, String, Option), - Resolving(R::Future, Option), + Resolving(ResolvingFuture, Option), Connecting(ConnectingTcp), Error(Option), } +enum ResolvingFuture { + Timed(Timeout), + Untimed(R::Future), +} + impl Future for HttpConnecting { type Item = (TcpStream, Connected); type Error = io::Error; @@ -367,11 +388,27 @@ impl Future for HttpConnecting { local_addr, addrs, self.connect_timeout, self.happy_eyeballs_timeout, self.reuse_address)); } else { let name = dns::Name::new(mem::replace(host, String::new())); - state = State::Resolving(resolver.resolve(name), local_addr); + let future = resolver.resolve(name); + state = if let Some(timeout) = self.resolve_timeout { + State::Resolving(ResolvingFuture::Timed(Timeout::new(future, timeout)), local_addr) + } else { + State::Resolving(ResolvingFuture::Untimed(future), local_addr) + } } }, - State::Resolving(ref mut future, local_addr) => { - match future.poll()? { + State::Resolving(ref mut rfuture, local_addr) => { + let res: Async = match rfuture { + ResolvingFuture::Timed(future) => match future.poll() { + Ok(res) => res, + Err(err) => if err.is_inner() { + return Err(err.into_inner().unwrap()) + } else { + return Err(io::Error::new(io::ErrorKind::TimedOut, err.description())) + }, + }, + ResolvingFuture::Untimed(future) => future.poll()?, + }; + match res { Async::NotReady => return Ok(Async::NotReady), Async::Ready(addrs) => { let port = self.port;