aws_smithy_http_client_reqwest/
lib.rs1#![warn(missing_docs)]
2use std::time::Duration;
36
37use aws_smithy_runtime_api::client::{
38 http::{
39 HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector,
40 },
41 orchestrator::{HttpRequest, HttpResponse},
42 result::ConnectorError,
43 runtime_components::RuntimeComponents,
44};
45use aws_smithy_types::body::SdkBody;
46use http_body_util::BodyExt;
47
48#[derive(Debug)]
49pub struct ReqwestHttpClient {
59 client: reqwest::Client,
60}
61
62impl ReqwestHttpClient {
63 #[must_use]
78 pub fn new(client: reqwest::Client) -> Self {
79 Self { client }
80 }
81}
82
83impl Default for ReqwestHttpClient {
84 fn default() -> Self {
85 Self::new(reqwest::Client::new())
86 }
87}
88
89impl HttpClient for ReqwestHttpClient {
90 fn http_connector(
91 &self,
92 settings: &HttpConnectorSettings,
93 _components: &RuntimeComponents,
94 ) -> SharedHttpConnector {
95 SharedHttpConnector::new(ReqwestHttpConnector {
96 client: self.client.clone(),
97 settings: settings.clone(),
98 })
99 }
100}
101
102enum CustomConnectorError {
103 ReqwestError(reqwest::Error),
104 HttpError(aws_smithy_runtime_api::http::HttpError),
105}
106
107#[derive(Debug)]
108struct ReqwestHttpConnector {
109 client: reqwest::Client,
110 settings: HttpConnectorSettings,
111}
112
113impl ReqwestHttpConnector {
114 async fn convert_request(
115 req: HttpRequest,
116 timeout: Option<Duration>,
117 ) -> Result<reqwest::Request, CustomConnectorError> {
118 let req = req
119 .try_into_http1x()
120 .map_err(|err| CustomConnectorError::HttpError(err))?;
121 let (parts, body) = req.into_parts();
122
123 let mut req = reqwest::Request::new(
124 parts.method.clone(),
125 parts.uri.to_string().parse().expect("known valid"),
126 );
127
128 *req.headers_mut() = parts.headers;
129 req.body_mut()
130 .replace(reqwest::Body::wrap_stream(body.into_data_stream()));
131
132 if let Some(timeout) = timeout {
133 req.timeout_mut().replace(timeout);
134 }
135
136 Ok(req)
137 }
138
139 async fn convert_response(
140 resp: reqwest::Response,
141 ) -> Result<HttpResponse, CustomConnectorError> {
142 let headers = resp.headers().clone();
143
144 let mut resp = HttpResponse::new(
145 aws_smithy_runtime_api::http::StatusCode::from(resp.status()),
146 SdkBody::from(
147 resp.bytes()
148 .await
149 .map_err(|err| CustomConnectorError::ReqwestError(err))?,
150 ),
151 );
152
153 *resp.headers_mut() = aws_smithy_runtime_api::http::Headers::try_from(headers)
154 .map_err(|err| CustomConnectorError::HttpError(err))?;
155
156 Ok(resp)
157 }
158}
159
160impl HttpConnector for ReqwestHttpConnector {
161 fn call(&self, req: HttpRequest) -> HttpConnectorFuture {
162 let client = self.client.clone();
163 let timeout = self.settings.read_timeout();
164 HttpConnectorFuture::new(async move {
165 let req = Self::convert_request(req, timeout)
166 .await
167 .map_err(|err| match err {
168 CustomConnectorError::HttpError(err) => {
169 ConnectorError::user(Box::new(err)).never_connected()
170 }
171 CustomConnectorError::ReqwestError(err) => {
172 ConnectorError::other(Box::new(err), None).never_connected()
173 }
174 })?;
175
176 let resp = client
177 .execute(req)
178 .await
179 .map_err(|err| ConnectorError::other(Box::new(err), None))?;
180
181 let resp = Self::convert_response(resp)
182 .await
183 .map_err(|err| match err {
184 CustomConnectorError::HttpError(err) => ConnectorError::user(Box::new(err)),
185 CustomConnectorError::ReqwestError(err) => {
186 ConnectorError::other(Box::new(err), None)
187 }
188 })?;
189
190 Ok(resp)
191 })
192 }
193}