diff --git a/Cargo.toml b/Cargo.toml index 7df90b5..9cac855 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,10 @@ serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.5" tokio = "0.1.7" +tokio-executor = "0.1.4" # a minimum version so trust-dns-resolver compiles tokio-io = "0.1" +tokio-timer = "0.2.6" # a minimum version so trust-dns-resolver compiles +trust-dns-resolver = "0.10" url = "1.2" uuid = { version = "0.7", features = ["v4"] } hyper-rustls = { version = "0.15", optional = true } diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 4bb8a1b..b5d6a86 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -60,7 +60,6 @@ struct Config { identity: Option, #[cfg(feature = "tls")] tls: TLSBackend, - dns_threads: usize, } impl ClientBuilder { @@ -88,7 +87,6 @@ impl ClientBuilder { identity: None, #[cfg(feature = "tls")] tls: TLSBackend::default(), - dns_threads: 4, }, } } @@ -131,7 +129,7 @@ impl ClientBuilder { tls.identity(id); } - Connector::new_default_tls(config.dns_threads, tls, proxies.clone())? + Connector::new_default_tls(tls, proxies.clone())? }, #[cfg(feature = "rustls-tls")] TLSBackend::Rustls => { @@ -188,12 +186,12 @@ impl ClientBuilder { tls.set_single_client_cert(certs, key); } - Connector::new_rustls_tls(config.dns_threads, tls, proxies.clone())? + Connector::new_rustls_tls(tls, proxies.clone())? } } #[cfg(not(feature = "tls"))] - Connector::new(config.dns_threads, proxies.clone()) + Connector::new(proxies.clone()) }; let hyper_client = ::hyper::Client::builder() @@ -327,9 +325,9 @@ impl ClientBuilder { self } - /// Set number of DNS threads. - pub fn dns_threads(mut self, threads: usize) -> ClientBuilder { - self.config.dns_threads = threads; + #[doc(hidden)] + #[deprecated(note = "DNS no longer uses blocking threads")] + pub fn dns_threads(self, _threads: usize) -> ClientBuilder { self } } diff --git a/src/client.rs b/src/client.rs index fd3c64f..8a7a417 100644 --- a/src/client.rs +++ b/src/client.rs @@ -281,13 +281,6 @@ impl ClientBuilder { self } - /// Set the number of threads to use for DNS - /// - /// Default is 4 - pub fn dns_threads(self, threads: usize) -> ClientBuilder { - self.with_inner(|inner| inner.dns_threads(threads)) - } - fn with_inner(mut self, func: F) -> ClientBuilder where F: FnOnce(async_impl::ClientBuilder) -> async_impl::ClientBuilder, diff --git a/src/connect.rs b/src/connect.rs index 4a5a4da..2c95d0b 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -1,6 +1,5 @@ use futures::Future; use http::uri::Scheme; -use hyper::client::{HttpConnector}; use hyper::client::connect::{Connect, Connected, Destination}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -14,8 +13,11 @@ use bytes::BufMut; use std::io; use std::sync::Arc; +use dns::TrustDnsResolver; use Proxy; +type HttpConnector = ::hyper::client::HttpConnector; + pub(crate) struct Connector { proxies: Arc>, @@ -33,8 +35,8 @@ enum Inner { impl Connector { #[cfg(not(feature = "tls"))] - pub(crate) fn new(threads: usize, proxies: Arc>) -> Connector { - let http = HttpConnector::new(threads); + pub(crate) fn new(proxies: Arc>) -> Connector { + let http = http_connector(); Connector { proxies, inner: Inner::Http(http) @@ -42,10 +44,10 @@ impl Connector { } #[cfg(feature = "default-tls")] - pub(crate) fn new_default_tls(threads: usize, tls: TlsConnectorBuilder, proxies: Arc>) -> ::Result { + pub(crate) fn new_default_tls(tls: TlsConnectorBuilder, proxies: Arc>) -> ::Result { let tls = try_!(tls.build()); - let mut http = HttpConnector::new(threads); + let mut http = http_connector(); http.enforce_http(false); let http = ::hyper_tls::HttpsConnector::from((http, tls.clone())); @@ -56,8 +58,8 @@ impl Connector { } #[cfg(feature = "rustls-tls")] - pub(crate) fn new_rustls_tls(threads: usize, tls: rustls::ClientConfig, proxies: Arc>) -> ::Result { - let mut http = HttpConnector::new(threads); + pub(crate) fn new_rustls_tls(tls: rustls::ClientConfig, proxies: Arc>) -> ::Result { + let mut http = http_connector(); http.enforce_http(false); let http = ::hyper_rustls::HttpsConnector::from((http, tls.clone())); @@ -68,6 +70,11 @@ impl Connector { } } +fn http_connector() -> HttpConnector { + let http = HttpConnector::new_with_resolver(TrustDnsResolver::new()); + http +} + impl Connect for Connector { type Transport = Conn; type Error = io::Error; diff --git a/src/dns.rs b/src/dns.rs new file mode 100644 index 0000000..b036072 --- /dev/null +++ b/src/dns.rs @@ -0,0 +1,73 @@ +use std::io; +use std::sync::{Arc, Mutex}; + +use futures::{future, Future}; +use hyper::client::connect::dns as hyper_dns; +use trust_dns_resolver::AsyncResolver; + +// If instead the type were just `AsyncResolver`, it breaks the default recursion limit +// for the compiler to determine if `reqwest::Client` is `Send`. This is because `AsyncResolver` +// has **a lot** of internal generic types that pushes us over the limit. +// +// "Erasing" the internal resolver type saves us from this limit. +type ErasedResolver = Box ::trust_dns_resolver::BackgroundLookupIp + Send + Sync>; + +#[derive(Clone)] +pub(crate) struct TrustDnsResolver { + inner: Arc>>, +} + +impl TrustDnsResolver { + pub(crate) fn new() -> Self { + TrustDnsResolver { + inner: Arc::new(Mutex::new(None)), + } + } +} + +impl hyper_dns::Resolve for TrustDnsResolver { + type Addrs = ::std::vec::IntoIter<::std::net::IpAddr>; + type Future = Box + Send>; + + fn resolve(&self, name: hyper_dns::Name) -> Self::Future { + let inner = self.inner.clone(); + Box::new(future::lazy(move || { + let mut inner = inner.lock().expect("lock resolver"); + if inner.is_none() { + // The `bg` (background) future needs to be spawned onto an executor, + // but a `reqwest::Client` may be constructed before an executor is ready. + // So, the `bg` future cannot be spawned *until* the executor starts to + // `poll` this future. + match AsyncResolver::from_system_conf() { + Ok((resolver, bg)) => { + ::tokio::spawn(bg); + *inner = Some(Box::new(move |name| { + resolver.lookup_ip(name.as_str()) + })); + }, + Err(err) => { + return future::Either::A( + future::err(io::Error::new(io::ErrorKind::Other, err.to_string())) + ); + } + } + } + + future::Either::B((inner + .as_mut() + .expect("resolver is set"))(name) + //.lookup_ip(name.as_str()) + .map(|lookup| { + lookup + .iter() + .collect::>() + .into_iter() + }) + .map_err(|err| { + io::Error::new(io::ErrorKind::Other, err.to_string()) + })) + })) + } +} + + diff --git a/src/lib.rs b/src/lib.rs index 7ddca5d..4eecf2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -163,6 +163,7 @@ extern crate serde_urlencoded; extern crate tokio; #[cfg_attr(feature = "default-tls", macro_use)] extern crate tokio_io; +extern crate trust_dns_resolver; extern crate url; extern crate uuid; @@ -203,6 +204,7 @@ mod connect; mod connect_async; mod body; mod client; +mod dns; mod into_url; mod proxy; mod redirect;