use anyhow::{Context, Result};
use bytes::Bytes;
use http::{Method, Request};
use http_body_util::{BodyExt, Full};
use hyper_rustls::HttpsConnector;
use hyper_util::{
client::legacy::{connect::HttpConnector, Client},
rt::TokioExecutor,
};
type RequestBody = Full<Bytes>;
type HyperClient = Client<HttpsConnector<HttpConnector>, RequestBody>;
struct HyperResponse {
status: u16,
#[cfg(feature = "client-pir")]
headers: Vec<(String, String)>,
body: Vec<u8>,
}
pub struct HyperTransport {
client: HyperClient,
#[cfg(feature = "client-tree-sync")]
runtime: tokio::runtime::Runtime,
}
impl HyperTransport {
pub fn new() -> Self {
#[cfg(feature = "client-tree-sync")]
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("create tree-sync HTTP runtime");
let mut connector = HttpConnector::new();
connector.enforce_http(false);
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_webpki_roots()
.https_or_http()
.enable_http1()
.enable_http2()
.wrap_connector(connector);
let client = Client::builder(TokioExecutor::new()).build(https);
Self {
client,
#[cfg(feature = "client-tree-sync")]
runtime,
}
}
async fn request(&self, method: Method, url: &str, body: Vec<u8>) -> Result<HyperResponse> {
let request = Request::builder()
.method(method)
.uri(url)
.body(Full::new(Bytes::from(body)))
.context("build HTTP request")?;
let response = self
.client
.request(request)
.await
.context("send HTTP request")?;
let status = response.status().as_u16();
#[cfg(feature = "client-pir")]
let headers = response
.headers()
.iter()
.filter_map(|(name, value)| {
value
.to_str()
.ok()
.map(|value| (name.as_str().to_string(), value.to_string()))
})
.collect();
let body = response
.into_body()
.collect()
.await
.context("read HTTP response body")?
.to_bytes()
.to_vec();
Ok(HyperResponse {
status,
#[cfg(feature = "client-pir")]
headers,
body,
})
}
}
impl Default for HyperTransport {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "client-pir")]
impl pir_client::Transport for HyperTransport {
fn get<'a>(&'a self, url: &'a str) -> pir_client::TransportFuture<'a> {
Box::pin(async move {
self.request(Method::GET, url, Vec::new())
.await
.map(|response| pir_client::TransportResponse {
status: response.status,
headers: response.headers,
body: response.body,
})
})
}
fn post<'a>(&'a self, url: &'a str, body: Vec<u8>) -> pir_client::TransportFuture<'a> {
Box::pin(async move {
self.request(Method::POST, url, body).await.map(|response| {
pir_client::TransportResponse {
status: response.status,
headers: response.headers,
body: response.body,
}
})
})
}
}
#[cfg(feature = "client-tree-sync")]
impl vote_commitment_tree_client::transport::Transport for HyperTransport {
fn get(
&self,
url: &str,
) -> std::result::Result<
vote_commitment_tree_client::transport::TransportResponse,
vote_commitment_tree_client::transport::TransportError,
> {
self.runtime
.block_on(async { self.request(Method::GET, url, Vec::new()).await })
.map(
|response| vote_commitment_tree_client::transport::TransportResponse {
status: response.status,
body: response.body,
},
)
.map_err(|e| {
vote_commitment_tree_client::transport::TransportError::Request(e.to_string())
})
}
}