diff --git a/src/blocking.rs b/src/blocking.rs index 2f1644b..b0ba0c4 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -1,20 +1,27 @@ //! Blocking variant of the `RestClient` use crate::{Error, Query, Response, RestClient as AsyncRestClient, RestPath}; +use hyper::client::connect::{dns, HttpConnector}; use hyper::header::HeaderValue; +use hyper::service::Service; use std::{convert::TryFrom, time::Duration}; use tokio::runtime::{Builder, Runtime}; +#[cfg(feature = "native-tls")] +use hyper_tls::HttpsConnector; +#[cfg(feature = "rustls")] +use hyper_rustls::HttpsConnector; + /// REST client to make HTTP GET and POST requests. Blocking version. -pub struct RestClient { - inner_client: AsyncRestClient, +pub struct RestClient { + inner_client: AsyncRestClient, runtime: Runtime, } -impl TryFrom for RestClient { +impl TryFrom> for RestClient { type Error = Error; - fn try_from(other: AsyncRestClient) -> Result { + fn try_from(other: AsyncRestClient) -> Result { match Builder::new_current_thread().enable_all().build() { Ok(runtime) => Ok(Self { inner_client: other, runtime }), Err(e) => Err(Error::IoError(e)), @@ -22,7 +29,11 @@ impl TryFrom for RestClient { } } -impl RestClient { +impl RestClient +where + R: Service + Send + Sync + Default + Clone + 'static, + HttpsConnector>: hyper::client::connect::Connect, +{ /// Set whether a message body consisting only 'null' (from serde serialization) /// is sent in POST/PUT pub fn set_send_null_body(&mut self, send_null: bool) { diff --git a/src/lib.rs b/src/lib.rs index a67ee45..6886e71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,6 +39,8 @@ use tokio::time::timeout; use hyper::header::*; use hyper::body::Buf; use hyper::{Client, Method, Request}; +use hyper::service::Service; +use hyper::client::connect::{dns, HttpConnector}; use log::{debug, trace, error}; use std::{error, fmt}; use std::ops::Deref; @@ -50,6 +52,9 @@ use hyper_tls::HttpsConnector; #[cfg(feature = "rustls")] use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; +use resolvers::GaiResolver; + +pub mod resolvers; #[cfg(feature = "blocking")] pub mod blocking; @@ -68,7 +73,7 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); /// would be parsed to **param1=1234¶m2=abcd** in the request URL. pub type Query<'a> = [(&'a str, &'a str)]; -pub type HyperClient = Client>; +pub type HyperClient = Client>>; /// Type returned by client query functions #[derive(Debug)] @@ -95,7 +100,7 @@ impl Response { #[cfg(feature = "lib-serde-json")] { let Self { body, headers } = self; - serde_json::from_str(&body) + serde_json::from_str::(&body) .map(|body| Response { body, headers }) .map_err(|err| Error::DeserializeParseError(err, body)) } @@ -119,8 +124,8 @@ impl Deref for Response { } /// REST client to make HTTP GET and POST requests. -pub struct RestClient { - client: HyperClient, +pub struct RestClient { + client: HyperClient, baseurl: url::Url, auth: Option, headers: HeaderMap, @@ -168,7 +173,7 @@ pub enum Error { } /// Builder for `RestClient` -pub struct Builder { +pub struct Builder { /// Request timeout timeout: Duration, @@ -176,7 +181,7 @@ pub struct Builder { send_null_body: bool, /// Hyper client to use for the connection - client: Option, + client: Option>, } impl fmt::Display for Error { @@ -238,7 +243,7 @@ impl std::convert::From for Error { } } -impl Default for Builder { +impl + Clone> Default for Builder { fn default() -> Self { Self { timeout: Duration::from_secs(std::u64::MAX), @@ -248,7 +253,11 @@ impl Default for Builder { } } -impl Builder { +impl Builder +where + R: Service + Send + Sync + Default + Clone + 'static, + HttpsConnector>: hyper::client::connect::Connect, +{ /// Set request timeout /// /// Default is no timeout @@ -267,20 +276,20 @@ impl Builder { self } - pub fn with_client(mut self, client: HyperClient) -> Self { + pub fn with_client(mut self, client: HyperClient) -> Self { self.client = Some(client); self } /// Create `RestClient` with the configuration in this builder - pub fn build(self, url: &str) -> Result { + pub fn build(self, url: &str) -> Result, Error> { RestClient::with_builder(url, self) } #[cfg(feature = "blocking")] /// Create [`blocking::RestClient`](blocking/struct.RestClient.html) with the configuration in /// this builder - pub fn blocking(self, url: &str) -> Result { + pub fn blocking(self, url: &str) -> Result, Error> { RestClient::with_builder(url, self).and_then(|client| client.try_into()) } } @@ -297,40 +306,53 @@ pub trait RestPath { fn get_path(par: T) -> Result; } -impl RestClient { - /// Construct new client with default configuration to make HTTP requests. +impl RestClient { + /// Construct new client with default configuration and DNS resolver + /// implementation to make HTTP requests. /// - /// Use `Builder` to configure the client. - pub fn new(url: &str) -> Result { - RestClient::with_builder(url, RestClient::builder()) + /// Use `Builder` to configure the client or to use a different + /// resolver type. + pub fn new(url: &str) -> Result, Error> { + RestClient::with_builder(url, Self::builder()) } - /// Construct new blocking client with default configuration to make HTTP requests. + /// Construct new blocking client with default configuration and DNS resolver + /// implementation to make HTTP requests. /// - /// Use `Builder` to configure the client. + /// Use `Builder` to configure the client or to use a different + /// resolver type. #[cfg(feature = "blocking")] - pub fn new_blocking(url: &str) -> Result { + pub fn new_blocking(url: &str) -> Result, Error> { RestClient::new(url).and_then(|client| client.try_into()) } +} +impl RestClient +where + R: Service + Send + Sync + Default + Clone + 'static, + HttpsConnector>: hyper::client::connect::Connect, +{ #[cfg(feature = "native-tls")] - fn build_client() -> HyperClient + fn build_client() -> HyperClient { - Client::builder().build(HttpsConnector::new()) + let http_connector = HttpConnector::new_with_resolver(R::default()); + let https_connector = HttpsConnector::new_with_connector(http_connector); + Client::builder().build(https_connector) } #[cfg(feature = "rustls")] - fn build_client() -> HyperClient + fn build_client() -> HyperClient { - let connector = HttpsConnectorBuilder::new() + let http_connector = HttpConnector::new_with_resolver(R::default()); + let https_connector = HttpsConnectorBuilder::new() .with_native_roots() .https_or_http() .enable_all_versions() - .build(); - Client::builder().build(connector) + .wrap_connector(http_connector); + Client::builder().build(https_connector) } - fn with_builder(url: &str, builder: Builder) -> Result { + fn with_builder(url: &str, builder: Builder) -> Result, Error> { let client = match builder.client { Some(client) => client, None => { @@ -353,7 +375,7 @@ impl RestClient { } /// Configure a client - pub fn builder() -> Builder { + pub fn builder() -> Builder { Builder::default() } diff --git a/src/resolvers.rs b/src/resolvers.rs new file mode 100644 index 0000000..25dda59 --- /dev/null +++ b/src/resolvers.rs @@ -0,0 +1,47 @@ +use core::task::{Context, Poll}; + +use hyper::client::connect::dns::{self, GaiResolver as HyperGaiResolver}; +use hyper::service::Service; + +/// Newtype wrapper around hyper's GaiResolver to provide Default +/// trait implementation +#[derive(Clone, Debug)] +pub struct GaiResolver(HyperGaiResolver); + +impl GaiResolver { + pub fn new() -> Self { + Self::default() + } +} + +impl From for GaiResolver { + fn from(gai: HyperGaiResolver) -> Self { + Self(gai) + } +} + +impl Into for GaiResolver { + fn into(self) -> HyperGaiResolver { + self.0 + } +} + +impl Default for GaiResolver { + fn default() -> Self { + Self(HyperGaiResolver::new()) + } +} + +impl Service for GaiResolver { + type Response = >::Response; + type Error = >::Error; + type Future = >::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.0.poll_ready(cx) + } + + fn call(&mut self, name: dns::Name) -> Self::Future { + self.0.call(name) + } +}