refactor DNS resolver construction
- System Conf is read as `ClientBuilder::build()` time, providing the error earlier. - If there is an error reading the resolve system conf, a better error is reported. - Resolver only needs to lock a mutex once to spawn the background task, instead of every single `resolve` call.
This commit is contained in:
		| @@ -112,7 +112,8 @@ impl ClientBuilder { | ||||
|     /// | ||||
|     /// # Errors | ||||
|     /// | ||||
|     /// This method fails if TLS backend cannot be initialized. | ||||
|     /// This method fails if TLS backend cannot be initialized, or the resolver | ||||
|     /// cannot load the system configuration. | ||||
|     pub fn build(self) -> ::Result<Client> { | ||||
|         let config = self.config; | ||||
|         let proxies = Arc::new(config.proxies); | ||||
| @@ -208,7 +209,7 @@ impl ClientBuilder { | ||||
|             } | ||||
|  | ||||
|             #[cfg(not(feature = "tls"))] | ||||
|             Connector::new(proxies.clone()) | ||||
|             Connector::new(proxies.clone())? | ||||
|         }; | ||||
|  | ||||
|         let hyper_client = ::hyper::Client::builder() | ||||
| @@ -362,13 +363,15 @@ impl Client { | ||||
|     /// | ||||
|     /// # Panics | ||||
|     /// | ||||
|     /// This method panics if TLS backend cannot be created or | ||||
|     /// initialized. Use `Client::builder()` if you wish to handle the failure | ||||
|     /// as an `Error` instead of panicking. | ||||
|     /// This method panics if TLS backend cannot initialized, or the resolver | ||||
|     /// cannot load the system configuration. | ||||
|     /// | ||||
|     /// Use `Client::builder()` if you wish to handle the failure as an `Error` | ||||
|     /// instead of panicking. | ||||
|     pub fn new() -> Client { | ||||
|         ClientBuilder::new() | ||||
|             .build() | ||||
|             .expect("TLS failed to initialize") | ||||
|             .expect("Client::new()") | ||||
|     } | ||||
|  | ||||
|     /// Creates a `ClientBuilder` to configure a `Client`. | ||||
|   | ||||
| @@ -75,7 +75,8 @@ impl ClientBuilder { | ||||
|     /// | ||||
|     /// # Errors | ||||
|     /// | ||||
|     /// This method fails if native TLS backend cannot be initialized. | ||||
|     /// This method fails if TLS backend cannot be initialized, or the resolver | ||||
|     /// cannot load the system configuration. | ||||
|     pub fn build(self) -> ::Result<Client> { | ||||
|         ClientHandle::new(self).map(|handle| Client { | ||||
|             inner: handle, | ||||
| @@ -300,13 +301,15 @@ impl Client { | ||||
|     /// | ||||
|     /// # Panic | ||||
|     /// | ||||
|     /// This method panics if native TLS backend cannot be created or | ||||
|     /// initialized. Use `Client::builder()` if you wish to handle the failure | ||||
|     /// as an `Error` instead of panicking. | ||||
|     /// This method panics if TLS backend cannot initialized, or the resolver | ||||
|     /// cannot load the system configuration. | ||||
|     /// | ||||
|     /// Use `Client::builder()` if you wish to handle the failure as an `Error` | ||||
|     /// instead of panicking. | ||||
|     pub fn new() -> Client { | ||||
|         ClientBuilder::new() | ||||
|             .build() | ||||
|             .expect("Client failed to initialize") | ||||
|             .expect("Client::new()") | ||||
|     } | ||||
|  | ||||
|     /// Creates a `ClientBuilder` to configure a `Client`. | ||||
|   | ||||
| @@ -35,19 +35,19 @@ enum Inner { | ||||
|  | ||||
| impl Connector { | ||||
|     #[cfg(not(feature = "tls"))] | ||||
|     pub(crate) fn new(proxies: Arc<Vec<Proxy>>) -> Connector { | ||||
|         let http = http_connector(); | ||||
|         Connector { | ||||
|     pub(crate) fn new(proxies: Arc<Vec<Proxy>>) -> ::Result<Connector> { | ||||
|         let http = http_connector()?; | ||||
|         Ok(Connector { | ||||
|             proxies, | ||||
|             inner: Inner::Http(http) | ||||
|         } | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "default-tls")] | ||||
|     pub(crate) fn new_default_tls(tls: TlsConnectorBuilder, proxies: Arc<Vec<Proxy>>) -> ::Result<Connector> { | ||||
|         let tls = try_!(tls.build()); | ||||
|  | ||||
|         let mut http = http_connector(); | ||||
|         let mut http = http_connector()?; | ||||
|         http.enforce_http(false); | ||||
|         let http = ::hyper_tls::HttpsConnector::from((http, tls.clone())); | ||||
|  | ||||
| @@ -59,7 +59,7 @@ impl Connector { | ||||
|  | ||||
|     #[cfg(feature = "rustls-tls")] | ||||
|     pub(crate) fn new_rustls_tls(tls: rustls::ClientConfig, proxies: Arc<Vec<Proxy>>) -> ::Result<Connector> { | ||||
|         let mut http = http_connector(); | ||||
|         let mut http = http_connector()?; | ||||
|         http.enforce_http(false); | ||||
|         let http = ::hyper_rustls::HttpsConnector::from((http, tls.clone())); | ||||
|  | ||||
| @@ -70,9 +70,10 @@ impl Connector { | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn http_connector() -> HttpConnector { | ||||
|     let http = HttpConnector::new_with_resolver(TrustDnsResolver::new()); | ||||
|     http | ||||
| fn http_connector() -> ::Result<HttpConnector> { | ||||
|     TrustDnsResolver::new() | ||||
|         .map(HttpConnector::new_with_resolver) | ||||
|         .map_err(::error::dns_system_conf) | ||||
| } | ||||
|  | ||||
| impl Connect for Connector { | ||||
|   | ||||
							
								
								
									
										76
									
								
								src/dns.rs
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								src/dns.rs
									
									
									
									
									
								
							| @@ -1,62 +1,75 @@ | ||||
| use std::io; | ||||
| use std::sync::{Arc, Mutex}; | ||||
| use std::{io, vec}; | ||||
| use std::net::IpAddr; | ||||
| use std::sync::{Arc, Mutex, Once}; | ||||
|  | ||||
| use futures::{future, Future}; | ||||
| use hyper::client::connect::dns as hyper_dns; | ||||
| use trust_dns_resolver::AsyncResolver; | ||||
| use tokio; | ||||
| use trust_dns_resolver::{system_conf, AsyncResolver, BackgroundLookupIp}; | ||||
|  | ||||
| // 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<Fn(hyper_dns::Name) -> ::trust_dns_resolver::BackgroundLookupIp + Send + Sync>; | ||||
| type ErasedResolver = Box<dyn Fn(hyper_dns::Name) -> BackgroundLookupIp + Send + Sync>; | ||||
| type Background = Box<dyn Future<Item=(), Error=()> + Send>; | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub(crate) struct TrustDnsResolver { | ||||
|     inner: Arc<Mutex<Option<ErasedResolver>>>, | ||||
|     inner: Arc<Inner>, | ||||
| } | ||||
|  | ||||
| struct Inner { | ||||
|     background: Mutex<Option<Background>>, | ||||
|     once: Once, | ||||
|     resolver: ErasedResolver, | ||||
| } | ||||
|  | ||||
| impl TrustDnsResolver { | ||||
|     pub(crate) fn new() -> Self { | ||||
|         TrustDnsResolver { | ||||
|             inner: Arc::new(Mutex::new(None)), | ||||
|         } | ||||
|     pub(crate) fn new() -> io::Result<Self> { | ||||
|         let (conf, opts) = system_conf::read_system_conf()?; | ||||
|         let (resolver, bg) = AsyncResolver::new(conf, opts); | ||||
|  | ||||
|         let resolver: ErasedResolver = Box::new(move |name| { | ||||
|             resolver.lookup_ip(name.as_str()) | ||||
|         }); | ||||
|         let background = Mutex::new(Some(Box::new(bg) as Background)); | ||||
|         let once = Once::new(); | ||||
|  | ||||
|         Ok(TrustDnsResolver { | ||||
|             inner: Arc::new(Inner { | ||||
|                 background, | ||||
|                 once, | ||||
|                 resolver, | ||||
|             }), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl hyper_dns::Resolve for TrustDnsResolver { | ||||
|     type Addrs = ::std::vec::IntoIter<::std::net::IpAddr>; | ||||
|     type Addrs = vec::IntoIter<IpAddr>; | ||||
|     type Future = Box<Future<Item=Self::Addrs, Error=io::Error> + 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() { | ||||
|             inner.once.call_once(|| { | ||||
|                 // 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())) | ||||
|                         ); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|                 let bg = inner | ||||
|                     .background | ||||
|                     .lock() | ||||
|                     .expect("resolver background lock") | ||||
|                     .take() | ||||
|                     .expect("background only taken once"); | ||||
|  | ||||
|             future::Either::B((inner | ||||
|                 .as_mut() | ||||
|                 .expect("resolver is set"))(name) | ||||
|                 //.lookup_ip(name.as_str()) | ||||
|                 tokio::spawn(bg); | ||||
|             }); | ||||
|  | ||||
|             (inner.resolver)(name) | ||||
|                 .map(|lookup| { | ||||
|                     lookup | ||||
|                         .iter() | ||||
| @@ -65,9 +78,8 @@ impl hyper_dns::Resolve for TrustDnsResolver { | ||||
|                 }) | ||||
|                 .map_err(|err| { | ||||
|                     io::Error::new(io::ErrorKind::Other, err.to_string()) | ||||
|                 })) | ||||
|                 }) | ||||
|         })) | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										11
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/error.rs
									
									
									
									
									
								
							| @@ -140,6 +140,7 @@ impl Error { | ||||
|             Kind::NativeTls(ref e) => Some(e), | ||||
|             #[cfg(feature = "rustls-tls")] | ||||
|             Kind::Rustls(ref e) => Some(e), | ||||
|             Kind::DnsSystemConf(ref e) => Some(e), | ||||
|             Kind::Io(ref e) => Some(e), | ||||
|             Kind::UrlEncoded(ref e) => Some(e), | ||||
|             Kind::Json(ref e) => Some(e), | ||||
| @@ -237,6 +238,9 @@ impl fmt::Display for Error { | ||||
|             Kind::NativeTls(ref e) => fmt::Display::fmt(e, f), | ||||
|             #[cfg(feature = "rustls-tls")] | ||||
|             Kind::Rustls(ref e) => fmt::Display::fmt(e, f), | ||||
|             Kind::DnsSystemConf(ref e) => { | ||||
|                 write!(f, "failed to load DNS system conf: {}", e) | ||||
|             }, | ||||
|             Kind::Io(ref e) => fmt::Display::fmt(e, f), | ||||
|             Kind::UrlEncoded(ref e) => fmt::Display::fmt(e, f), | ||||
|             Kind::Json(ref e) => fmt::Display::fmt(e, f), | ||||
| @@ -268,6 +272,7 @@ impl StdError for Error { | ||||
|             Kind::NativeTls(ref e) => e.description(), | ||||
|             #[cfg(feature = "rustls-tls")] | ||||
|             Kind::Rustls(ref e) => e.description(), | ||||
|             Kind::DnsSystemConf(_) => "failed to load DNS system conf", | ||||
|             Kind::Io(ref e) => e.description(), | ||||
|             Kind::UrlEncoded(ref e) => e.description(), | ||||
|             Kind::Json(ref e) => e.description(), | ||||
| @@ -291,6 +296,7 @@ impl StdError for Error { | ||||
|             Kind::NativeTls(ref e) => e.cause(), | ||||
|             #[cfg(feature = "rustls-tls")] | ||||
|             Kind::Rustls(ref e) => e.cause(), | ||||
|             Kind::DnsSystemConf(ref e) => e.cause(), | ||||
|             Kind::Io(ref e) => e.cause(), | ||||
|             Kind::UrlEncoded(ref e) => e.cause(), | ||||
|             Kind::Json(ref e) => e.cause(), | ||||
| @@ -316,6 +322,7 @@ pub(crate) enum Kind { | ||||
|     NativeTls(::native_tls::Error), | ||||
|     #[cfg(feature = "rustls-tls")] | ||||
|     Rustls(::rustls::TLSError), | ||||
|     DnsSystemConf(io::Error), | ||||
|     Io(io::Error), | ||||
|     UrlEncoded(::serde_urlencoded::ser::Error), | ||||
|     Json(::serde_json::Error), | ||||
| @@ -489,6 +496,10 @@ pub(crate) fn url_bad_scheme(url: Url) -> Error { | ||||
|     Error::new(Kind::UrlBadScheme, Some(url)) | ||||
| } | ||||
|  | ||||
| pub(crate) fn dns_system_conf(io: io::Error) -> Error { | ||||
|     Error::new(Kind::DnsSystemConf(io), None) | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|   | ||||
| @@ -13,7 +13,7 @@ | ||||
| //! | ||||
| //! - Plain bodies, [JSON](#json), [urlencoded](#forms), [multipart](multipart) | ||||
| //! - Customizable [redirect policy](#redirect-policy) | ||||
| //! - HTTP [Proxies](proxies) | ||||
| //! - HTTP [Proxies](#proxies) | ||||
| //! - Uses system-native [TLS](#tls) | ||||
| //! - Cookies (only rudimentary support, full support is TODO) | ||||
| //! | ||||
|   | ||||
		Reference in New Issue
	
	Block a user