use crate::{Path, Query, Result, USER_AGENT};
use hyper::{
body::Buf,
client::{Client, HttpConnector},
header::{self, HeaderMap, HeaderName, HeaderValue},
Body, Request, Response,
};
use hyper_rustls::HttpsConnector;
#[cfg(feature = "json")]
use serde::de::DeserializeOwned;
#[cfg(feature = "proxy")]
use {
crate::{Error, Uri},
hyper_proxy::{Intercept, Proxy, ProxyConnector},
};
pub struct HttpsClient {
inner: InnerClient,
hostname: String,
headers: HeaderMap,
}
impl HttpsClient {
pub fn new(hostname: impl Into<String>) -> Self {
let client = Client::builder().build(Self::https_connector());
Self {
inner: InnerClient::Https(client),
hostname: hostname.into(),
headers: default_headers(),
}
}
#[cfg(feature = "proxy")]
#[cfg_attr(docsrs, doc(cfg(feature = "proxy")))]
pub fn new_with_proxy(hostname: impl Into<String>, proxy_uri: Uri) -> Result<Self> {
let connector = Self::https_connector();
let proxy = Proxy::new(Intercept::All, proxy_uri);
let proxy_connector = ProxyConnector::from_proxy(connector, proxy).map_err(Error::Proxy)?;
let client = Client::builder().build(proxy_connector);
Ok(Self {
inner: InnerClient::HttpsViaProxy(client),
hostname: hostname.into(),
headers: default_headers(),
})
}
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
}
pub fn add_header(&mut self, name: HeaderName, value: &str) -> Result<Option<HeaderValue>> {
Ok(self.headers.insert(name, value.parse()?))
}
pub async fn request(&self, mut request: Request<Body>) -> Result<Response<Body>> {
if request.headers().is_empty() {
*request.headers_mut() = self.headers.clone();
} else {
for (name, value) in &self.headers {
request.headers_mut().append(name, value.clone());
}
}
Ok(match &self.inner {
InnerClient::Https(client) => client.request(request),
#[cfg(feature = "proxy")]
InnerClient::HttpsViaProxy(client) => client.request(request),
}
.await?)
}
pub async fn get(&self, path: &Path, query: &Query) -> Result<Response<Body>> {
let uri = query.to_request_uri(&self.hostname, path);
let request = Request::builder()
.method("GET")
.uri(&uri)
.body(Body::empty())?;
self.request(request).await
}
pub async fn get_body(&self, path: &Path, query: &Query) -> Result<impl Buf> {
let response = self.get(path, query).await?;
Ok(hyper::body::aggregate(response.into_body()).await?)
}
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub async fn get_json<T>(&self, path: &Path, query: &Query) -> Result<T>
where
T: DeserializeOwned,
{
let uri = query.to_request_uri(&self.hostname, path);
let mut request = Request::builder()
.method("GET")
.uri(&uri)
.body(Body::empty())?;
request
.headers_mut()
.append(header::CONTENT_TYPE, "application/json".parse()?);
let response = self.request(request).await?;
let body = hyper::body::aggregate(response.into_body()).await?;
Ok(serde_json::from_reader(body.reader())?)
}
fn https_connector() -> HttpsConnector<HttpConnector> {
HttpsConnector::with_native_roots()
}
}
enum InnerClient {
Https(Client<HttpsConnector<HttpConnector>, Body>),
#[cfg(feature = "proxy")]
HttpsViaProxy(Client<ProxyConnector<HttpsConnector<HttpConnector>>, Body>),
}
fn default_headers() -> HeaderMap {
let mut headers = HeaderMap::new();
headers.insert(
header::USER_AGENT,
USER_AGENT.parse().expect("USER_AGENT invalid"),
);
headers
}