#![warn(missing_docs)]
use std::time::Duration;
use aws_smithy_runtime_api::client::{
http::{
HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector,
},
orchestrator::{HttpRequest, HttpResponse},
result::ConnectorError,
runtime_components::RuntimeComponents,
};
use aws_smithy_types::body::SdkBody;
use http_body_util::BodyExt;
#[derive(Debug)]
pub struct ReqwestHttpClient {
client: reqwest::Client,
}
impl ReqwestHttpClient {
#[must_use]
pub fn new(client: reqwest::Client) -> Self {
Self { client }
}
}
impl Default for ReqwestHttpClient {
fn default() -> Self {
Self::new(reqwest::Client::new())
}
}
impl HttpClient for ReqwestHttpClient {
fn http_connector(
&self,
settings: &HttpConnectorSettings,
_components: &RuntimeComponents,
) -> SharedHttpConnector {
SharedHttpConnector::new(ReqwestHttpConnector {
client: self.client.clone(),
settings: settings.clone(),
})
}
}
enum CustomConnectorError {
ReqwestError(reqwest::Error),
HttpError(aws_smithy_runtime_api::http::HttpError),
}
#[derive(Debug)]
struct ReqwestHttpConnector {
client: reqwest::Client,
settings: HttpConnectorSettings,
}
impl ReqwestHttpConnector {
async fn convert_request(
req: HttpRequest,
timeout: Option<Duration>,
) -> Result<reqwest::Request, CustomConnectorError> {
let req = req
.try_into_http1x()
.map_err(|err| CustomConnectorError::HttpError(err))?;
let (parts, body) = req.into_parts();
let mut req = reqwest::Request::new(
parts.method.clone(),
parts.uri.to_string().parse().expect("known valid"),
);
*req.headers_mut() = parts.headers;
req.body_mut()
.replace(reqwest::Body::wrap_stream(body.into_data_stream()));
if let Some(timeout) = timeout {
req.timeout_mut().replace(timeout);
}
Ok(req)
}
async fn convert_response(
resp: reqwest::Response,
) -> Result<HttpResponse, CustomConnectorError> {
let headers = resp.headers().clone();
let mut resp = HttpResponse::new(
aws_smithy_runtime_api::http::StatusCode::from(resp.status()),
SdkBody::from(
resp.bytes()
.await
.map_err(|err| CustomConnectorError::ReqwestError(err))?,
),
);
*resp.headers_mut() = aws_smithy_runtime_api::http::Headers::try_from(headers)
.map_err(|err| CustomConnectorError::HttpError(err))?;
Ok(resp)
}
}
impl HttpConnector for ReqwestHttpConnector {
fn call(&self, req: HttpRequest) -> HttpConnectorFuture {
let client = self.client.clone();
let timeout = self.settings.read_timeout();
HttpConnectorFuture::new(async move {
let req = Self::convert_request(req, timeout)
.await
.map_err(|err| match err {
CustomConnectorError::HttpError(err) => {
ConnectorError::user(Box::new(err)).never_connected()
}
CustomConnectorError::ReqwestError(err) => {
ConnectorError::other(Box::new(err), None).never_connected()
}
})?;
let resp = client
.execute(req)
.await
.map_err(|err| ConnectorError::other(Box::new(err), None))?;
let resp = Self::convert_response(resp)
.await
.map_err(|err| match err {
CustomConnectorError::HttpError(err) => ConnectorError::user(Box::new(err)),
CustomConnectorError::ReqwestError(err) => {
ConnectorError::other(Box::new(err), None)
}
})?;
Ok(resp)
})
}
}