Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom domain name resolvers #40

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/blocking.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
//! 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<R> {
inner_client: AsyncRestClient<R>,
runtime: Runtime,
}

impl TryFrom<AsyncRestClient> for RestClient {
impl<R> TryFrom<AsyncRestClient<R>> for RestClient<R> {
type Error = Error;

fn try_from(other: AsyncRestClient) -> Result<Self, Self::Error> {
fn try_from(other: AsyncRestClient<R>) -> Result<Self, Self::Error> {
match Builder::new_current_thread().enable_all().build() {
Ok(runtime) => Ok(Self { inner_client: other, runtime }),
Err(e) => Err(Error::IoError(e)),
}
}
}

impl RestClient {
impl<R> RestClient<R>
where
R: Service<dns::Name> + Send + Sync + Default + Clone + 'static,
HttpsConnector<HttpConnector<R>>: 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) {
Expand Down
76 changes: 49 additions & 27 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -68,7 +73,7 @@ static VERSION: &str = env!("CARGO_PKG_VERSION");
/// would be parsed to **param1=1234&param2=abcd** in the request URL.
pub type Query<'a> = [(&'a str, &'a str)];

pub type HyperClient = Client<HttpsConnector<hyper::client::HttpConnector>>;
pub type HyperClient<R> = Client<HttpsConnector<HttpConnector<R>>>;

/// Type returned by client query functions
#[derive(Debug)]
Expand All @@ -95,7 +100,7 @@ impl Response<String> {
#[cfg(feature = "lib-serde-json")]
{
let Self { body, headers } = self;
serde_json::from_str(&body)
serde_json::from_str::<T>(&body)
.map(|body| Response { body, headers })
.map_err(|err| Error::DeserializeParseError(err, body))
}
Expand All @@ -119,8 +124,8 @@ impl<T> Deref for Response<T> {
}

/// REST client to make HTTP GET and POST requests.
pub struct RestClient {
client: HyperClient,
pub struct RestClient<R = GaiResolver> {
client: HyperClient<R>,
baseurl: url::Url,
auth: Option<String>,
headers: HeaderMap,
Expand Down Expand Up @@ -168,15 +173,15 @@ pub enum Error {
}

/// Builder for `RestClient`
pub struct Builder {
pub struct Builder<R = GaiResolver> {
/// Request timeout
timeout: Duration,

/// Send null body
send_null_body: bool,

/// Hyper client to use for the connection
client: Option<HyperClient>,
client: Option<HyperClient<R>>,
}

impl fmt::Display for Error {
Expand Down Expand Up @@ -238,7 +243,7 @@ impl std::convert::From<tokio::time::error::Elapsed> for Error {
}
}

impl Default for Builder {
impl<R: Service<dns::Name> + Clone> Default for Builder<R> {
fn default() -> Self {
Self {
timeout: Duration::from_secs(std::u64::MAX),
Expand All @@ -248,7 +253,11 @@ impl Default for Builder {
}
}

impl Builder {
impl<R> Builder<R>
where
R: Service<dns::Name> + Send + Sync + Default + Clone + 'static,
HttpsConnector<HttpConnector<R>>: hyper::client::connect::Connect,
{
/// Set request timeout
///
/// Default is no timeout
Expand All @@ -267,20 +276,20 @@ impl Builder {
self
}

pub fn with_client(mut self, client: HyperClient) -> Self {
pub fn with_client(mut self, client: HyperClient<R>) -> Self {
self.client = Some(client);
self
}

/// Create `RestClient` with the configuration in this builder
pub fn build(self, url: &str) -> Result<RestClient, Error> {
pub fn build(self, url: &str) -> Result<RestClient<R>, 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<blocking::RestClient, Error> {
pub fn blocking(self, url: &str) -> Result<blocking::RestClient<R>, Error> {
RestClient::with_builder(url, self).and_then(|client| client.try_into())
}
}
Expand All @@ -297,40 +306,53 @@ pub trait RestPath<T> {
fn get_path(par: T) -> Result<String, Error>;
}

impl RestClient {
/// Construct new client with default configuration to make HTTP requests.
impl RestClient<GaiResolver> {
/// 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, Error> {
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<RestClient<GaiResolver>, 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<blocking::RestClient, Error> {
pub fn new_blocking(url: &str) -> Result<blocking::RestClient<GaiResolver>, Error> {
RestClient::new(url).and_then(|client| client.try_into())
}
}

impl<R> RestClient<R>
where
R: Service<dns::Name> + Send + Sync + Default + Clone + 'static,
HttpsConnector<HttpConnector<R>>: hyper::client::connect::Connect,
{
#[cfg(feature = "native-tls")]
fn build_client() -> HyperClient
fn build_client() -> HyperClient<R>
{
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<R>
{
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<RestClient, Error> {
fn with_builder(url: &str, builder: Builder<R>) -> Result<RestClient<R>, Error> {
let client = match builder.client {
Some(client) => client,
None => {
Expand All @@ -353,7 +375,7 @@ impl RestClient {
}

/// Configure a client
pub fn builder() -> Builder {
pub fn builder() -> Builder<R> {
Builder::default()
}

Expand Down
47 changes: 47 additions & 0 deletions src/resolvers.rs
Original file line number Diff line number Diff line change
@@ -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<HyperGaiResolver> for GaiResolver {
fn from(gai: HyperGaiResolver) -> Self {
Self(gai)
}
}

impl Into<HyperGaiResolver> for GaiResolver {
fn into(self) -> HyperGaiResolver {
self.0
}
}

impl Default for GaiResolver {
fn default() -> Self {
Self(HyperGaiResolver::new())
}
}

impl Service<dns::Name> for GaiResolver {
type Response = <HyperGaiResolver as Service<dns::Name>>::Response;
type Error = <HyperGaiResolver as Service<dns::Name>>::Error;
type Future = <HyperGaiResolver as Service<dns::Name>>::Future;

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}

fn call(&mut self, name: dns::Name) -> Self::Future {
self.0.call(name)
}
}