2023-11-06 09:14:46 -05:00
|
|
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
|
|
|
#![doc = include_str!("../README.md")]
|
|
|
|
|
|
2023-11-06 11:45:31 -05:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
use tokio::sync::Mutex;
|
|
|
|
|
|
2023-11-15 17:18:24 -05:00
|
|
|
#[cfg(feature = "tls")]
|
2023-11-06 09:14:46 -05:00
|
|
|
use hyper_rustls::{HttpsConnectorBuilder, HttpsConnector};
|
2023-11-06 11:45:31 -05:00
|
|
|
use hyper::{
|
|
|
|
|
Uri,
|
|
|
|
|
header::HeaderValue,
|
|
|
|
|
body::Body,
|
|
|
|
|
service::Service,
|
|
|
|
|
client::{HttpConnector, conn::http1::SendRequest},
|
|
|
|
|
};
|
2023-11-06 10:31:26 -05:00
|
|
|
pub use hyper;
|
|
|
|
|
|
|
|
|
|
mod request;
|
|
|
|
|
pub use request::*;
|
|
|
|
|
|
|
|
|
|
mod response;
|
|
|
|
|
pub use response::*;
|
2023-11-06 09:14:46 -05:00
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2023-11-06 10:31:26 -05:00
|
|
|
pub enum Error {
|
|
|
|
|
InvalidUri,
|
2023-11-06 11:45:31 -05:00
|
|
|
MissingHost,
|
|
|
|
|
InconsistentHost,
|
2023-11-15 17:18:24 -05:00
|
|
|
ConnectionError(Box<dyn Send + Sync + std::error::Error>),
|
2023-11-06 10:31:26 -05:00
|
|
|
Hyper(hyper::Error),
|
2023-11-06 09:14:46 -05:00
|
|
|
}
|
|
|
|
|
|
2023-11-15 17:18:24 -05:00
|
|
|
#[cfg(not(feature = "tls"))]
|
|
|
|
|
type Connector = HttpConnector;
|
|
|
|
|
#[cfg(feature = "tls")]
|
|
|
|
|
type Connector = HttpsConnector<HttpConnector>;
|
|
|
|
|
|
2023-11-06 09:14:46 -05:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
enum Connection {
|
2023-11-15 17:18:24 -05:00
|
|
|
ConnectionPool(hyper::Client<Connector>),
|
|
|
|
|
Connection { connector: Connector, host: Uri, connection: Arc<Mutex<Option<SendRequest<Body>>>> },
|
2023-11-06 09:14:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct Client {
|
|
|
|
|
connection: Connection,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Client {
|
2023-11-15 17:18:24 -05:00
|
|
|
fn connector() -> Connector {
|
|
|
|
|
#[cfg(feature = "tls")]
|
|
|
|
|
let res =
|
|
|
|
|
HttpsConnectorBuilder::new().with_native_roots().https_or_http().enable_http1().build();
|
|
|
|
|
#[cfg(not(feature = "tls"))]
|
|
|
|
|
let res = HttpConnector::new();
|
|
|
|
|
res
|
2023-11-06 09:14:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn with_connection_pool() -> Client {
|
|
|
|
|
Client {
|
2023-11-15 17:18:24 -05:00
|
|
|
connection: Connection::ConnectionPool(hyper::Client::builder().build(Self::connector())),
|
2023-11-06 09:14:46 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-06 11:45:31 -05:00
|
|
|
pub fn without_connection_pool(host: String) -> Result<Client, Error> {
|
|
|
|
|
Ok(Client {
|
|
|
|
|
connection: Connection::Connection {
|
2023-11-15 17:18:24 -05:00
|
|
|
connector: Self::connector(),
|
2023-11-06 11:45:31 -05:00
|
|
|
host: {
|
|
|
|
|
let uri: Uri = host.parse().map_err(|_| Error::InvalidUri)?;
|
|
|
|
|
if uri.host().is_none() {
|
|
|
|
|
Err(Error::MissingHost)?;
|
|
|
|
|
};
|
|
|
|
|
uri
|
|
|
|
|
},
|
|
|
|
|
connection: Arc::new(Mutex::new(None)),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
2023-11-06 09:14:46 -05:00
|
|
|
|
2023-11-29 01:16:18 -05:00
|
|
|
pub async fn request<R: Into<Request>>(&self, request: R) -> Result<Response<'_>, Error> {
|
2023-11-06 10:31:26 -05:00
|
|
|
let request: Request = request.into();
|
|
|
|
|
let mut request = request.0;
|
2023-11-06 11:45:31 -05:00
|
|
|
if let Some(header_host) = request.headers().get(hyper::header::HOST) {
|
|
|
|
|
match &self.connection {
|
|
|
|
|
Connection::ConnectionPool(_) => {}
|
|
|
|
|
Connection::Connection { host, .. } => {
|
|
|
|
|
if header_host.to_str().map_err(|_| Error::InvalidUri)? != host.host().unwrap() {
|
|
|
|
|
Err(Error::InconsistentHost)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
let host = match &self.connection {
|
|
|
|
|
Connection::ConnectionPool(_) => {
|
|
|
|
|
request.uri().host().ok_or(Error::MissingHost)?.to_string()
|
|
|
|
|
}
|
|
|
|
|
Connection::Connection { host, .. } => {
|
|
|
|
|
let host_str = host.host().unwrap();
|
|
|
|
|
if let Some(uri_host) = request.uri().host() {
|
|
|
|
|
if host_str != uri_host {
|
|
|
|
|
Err(Error::InconsistentHost)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
host_str.to_string()
|
|
|
|
|
}
|
|
|
|
|
};
|
2023-11-06 09:14:46 -05:00
|
|
|
request
|
|
|
|
|
.headers_mut()
|
2023-11-06 10:31:26 -05:00
|
|
|
.insert(hyper::header::HOST, HeaderValue::from_str(&host).map_err(|_| Error::InvalidUri)?);
|
2023-11-06 09:14:46 -05:00
|
|
|
}
|
|
|
|
|
|
2023-11-29 01:16:18 -05:00
|
|
|
let response = match &self.connection {
|
2023-11-06 09:14:46 -05:00
|
|
|
Connection::ConnectionPool(client) => client.request(request).await.map_err(Error::Hyper)?,
|
2023-11-15 17:18:24 -05:00
|
|
|
Connection::Connection { connector, host, connection } => {
|
2023-11-06 11:45:31 -05:00
|
|
|
let mut connection_lock = connection.lock().await;
|
|
|
|
|
|
|
|
|
|
// If there's not a connection...
|
|
|
|
|
if connection_lock.is_none() {
|
2023-11-15 17:18:24 -05:00
|
|
|
let call_res = connector.clone().call(host.clone()).await;
|
|
|
|
|
#[cfg(not(feature = "tls"))]
|
|
|
|
|
let call_res = call_res.map_err(|e| Error::ConnectionError(format!("{e:?}").into()));
|
|
|
|
|
#[cfg(feature = "tls")]
|
|
|
|
|
let call_res = call_res.map_err(Error::ConnectionError);
|
|
|
|
|
let (requester, connection) =
|
|
|
|
|
hyper::client::conn::http1::handshake(call_res?).await.map_err(Error::Hyper)?;
|
2023-11-29 01:16:18 -05:00
|
|
|
// This will die when we drop the requester, so we don't need to track an AbortHandle
|
|
|
|
|
// for it
|
2023-11-06 11:45:31 -05:00
|
|
|
tokio::spawn(connection);
|
|
|
|
|
*connection_lock = Some(requester);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let connection = connection_lock.as_mut().unwrap();
|
|
|
|
|
let mut err = connection.ready().await.err();
|
|
|
|
|
if err.is_none() {
|
|
|
|
|
// Send the request
|
|
|
|
|
let res = connection.send_request(request).await;
|
|
|
|
|
if let Ok(res) = res {
|
2023-11-29 01:16:18 -05:00
|
|
|
return Ok(Response(res, self));
|
2023-11-06 11:45:31 -05:00
|
|
|
}
|
|
|
|
|
err = res.err();
|
|
|
|
|
}
|
|
|
|
|
// Since this connection has been put into an error state, drop it
|
|
|
|
|
*connection_lock = None;
|
|
|
|
|
Err(Error::Hyper(err.unwrap()))?
|
|
|
|
|
}
|
2023-11-29 01:16:18 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(Response(response, self))
|
2023-11-06 09:14:46 -05:00
|
|
|
}
|
|
|
|
|
}
|