Skip to content

Commit d4ee699

Browse files
chewiseanmonstar
authored andcommitted
feat(client): add resolve timeout to HttpConnector (#1994)
The recently-added connect timeout does not cover resolving hostnames, which could also stall on an OS-level timeout if there are issues reaching the DNS server.
1 parent da16ed6 commit d4ee699

File tree

1 file changed

+41
-4
lines changed

1 file changed

+41
-4
lines changed

src/client/connect/http.rs

+41-4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use super::dns::{self, GaiResolver, Resolve, TokioThreadpoolGaiResolver};
2929
pub struct HttpConnector<R = GaiResolver> {
3030
enforce_http: bool,
3131
handle: Option<Handle>,
32+
resolve_timeout: Option<Duration>,
3233
connect_timeout: Option<Duration>,
3334
happy_eyeballs_timeout: Option<Duration>,
3435
keep_alive_timeout: Option<Duration>,
@@ -121,6 +122,7 @@ impl<R> HttpConnector<R> {
121122
HttpConnector {
122123
enforce_http: true,
123124
handle: None,
125+
resolve_timeout: None,
124126
connect_timeout: None,
125127
happy_eyeballs_timeout: Some(Duration::from_millis(300)),
126128
keep_alive_timeout: None,
@@ -189,6 +191,17 @@ impl<R> HttpConnector<R> {
189191
self.local_address = addr;
190192
}
191193

194+
/// Set timeout for hostname resolution.
195+
///
196+
/// If `None`, then no timeout is applied by the connector, making it
197+
/// subject to the timeout imposed by the operating system.
198+
///
199+
/// Default is `None`.
200+
#[inline]
201+
pub fn set_resolve_timeout(&mut self, dur: Option<Duration>) {
202+
self.resolve_timeout = dur;
203+
}
204+
192205
/// Set the connect timeout.
193206
///
194207
/// If a domain resolves to multiple IP addresses, the timeout will be
@@ -272,6 +285,7 @@ where
272285
HttpConnecting {
273286
state: State::Lazy(self.resolver.clone(), host.into(), self.local_address),
274287
handle: self.handle.clone(),
288+
resolve_timeout: self.resolve_timeout,
275289
connect_timeout: self.connect_timeout,
276290
happy_eyeballs_timeout: self.happy_eyeballs_timeout,
277291
keep_alive_timeout: self.keep_alive_timeout,
@@ -299,6 +313,7 @@ fn invalid_url<R: Resolve>(err: InvalidUrl, handle: &Option<Handle>) -> HttpConn
299313
keep_alive_timeout: None,
300314
nodelay: false,
301315
port: 0,
316+
resolve_timeout: None,
302317
connect_timeout: None,
303318
happy_eyeballs_timeout: None,
304319
reuse_address: false,
@@ -334,6 +349,7 @@ impl StdError for InvalidUrl {
334349
pub struct HttpConnecting<R: Resolve = GaiResolver> {
335350
state: State<R>,
336351
handle: Option<Handle>,
352+
resolve_timeout: Option<Duration>,
337353
connect_timeout: Option<Duration>,
338354
happy_eyeballs_timeout: Option<Duration>,
339355
keep_alive_timeout: Option<Duration>,
@@ -346,11 +362,16 @@ pub struct HttpConnecting<R: Resolve = GaiResolver> {
346362

347363
enum State<R: Resolve> {
348364
Lazy(R, String, Option<IpAddr>),
349-
Resolving(R::Future, Option<IpAddr>),
365+
Resolving(ResolvingFuture<R>, Option<IpAddr>),
350366
Connecting(ConnectingTcp),
351367
Error(Option<io::Error>),
352368
}
353369

370+
enum ResolvingFuture<R: Resolve> {
371+
Timed(Timeout<R::Future>),
372+
Untimed(R::Future),
373+
}
374+
354375
impl<R: Resolve> Future for HttpConnecting<R> {
355376
type Item = (TcpStream, Connected);
356377
type Error = io::Error;
@@ -367,11 +388,27 @@ impl<R: Resolve> Future for HttpConnecting<R> {
367388
local_addr, addrs, self.connect_timeout, self.happy_eyeballs_timeout, self.reuse_address));
368389
} else {
369390
let name = dns::Name::new(mem::replace(host, String::new()));
370-
state = State::Resolving(resolver.resolve(name), local_addr);
391+
let future = resolver.resolve(name);
392+
state = if let Some(timeout) = self.resolve_timeout {
393+
State::Resolving(ResolvingFuture::Timed(Timeout::new(future, timeout)), local_addr)
394+
} else {
395+
State::Resolving(ResolvingFuture::Untimed(future), local_addr)
396+
}
371397
}
372398
},
373-
State::Resolving(ref mut future, local_addr) => {
374-
match future.poll()? {
399+
State::Resolving(ref mut rfuture, local_addr) => {
400+
let res: Async<R::Addrs> = match rfuture {
401+
ResolvingFuture::Timed(future) => match future.poll() {
402+
Ok(res) => res,
403+
Err(err) => if err.is_inner() {
404+
return Err(err.into_inner().unwrap())
405+
} else {
406+
return Err(io::Error::new(io::ErrorKind::TimedOut, err.description()))
407+
},
408+
},
409+
ResolvingFuture::Untimed(future) => future.poll()?,
410+
};
411+
match res {
375412
Async::NotReady => return Ok(Async::NotReady),
376413
Async::Ready(addrs) => {
377414
let port = self.port;

0 commit comments

Comments
 (0)