aws_smithy_runtime_api/client/
http.rs1use crate::box_error::BoxError;
54use crate::client::connector_metadata::ConnectorMetadata;
55use crate::client::orchestrator::{HttpRequest, HttpResponse};
56use crate::client::result::ConnectorError;
57use crate::client::runtime_components::sealed::ValidateConfig;
58use crate::client::runtime_components::{RuntimeComponents, RuntimeComponentsBuilder};
59use crate::impl_shared_conversions;
60use aws_smithy_types::config_bag::ConfigBag;
61use std::fmt;
62use std::sync::Arc;
63use std::time::Duration;
64
65new_type_future! {
66 #[doc = "Future for [`HttpConnector::call`]."]
67 pub struct HttpConnectorFuture<'static, HttpResponse, ConnectorError>;
68}
69
70pub trait HttpConnector: Send + Sync + fmt::Debug {
78 fn call(&self, request: HttpRequest) -> HttpConnectorFuture;
80}
81
82#[derive(Clone, Debug)]
84pub struct SharedHttpConnector(Arc<dyn HttpConnector>);
85
86impl SharedHttpConnector {
87 pub fn new(connection: impl HttpConnector + 'static) -> Self {
89 Self(Arc::new(connection))
90 }
91}
92
93impl HttpConnector for SharedHttpConnector {
94 fn call(&self, request: HttpRequest) -> HttpConnectorFuture {
95 (*self.0).call(request)
96 }
97}
98
99impl_shared_conversions!(convert SharedHttpConnector from HttpConnector using SharedHttpConnector::new);
100
101pub fn http_client_fn<F>(connector: F) -> SharedHttpClient
103where
104 F: Fn(&HttpConnectorSettings, &RuntimeComponents) -> SharedHttpConnector
105 + Send
106 + Sync
107 + 'static,
108{
109 struct ConnectorFn<T>(T);
110 impl<T> fmt::Debug for ConnectorFn<T> {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 f.write_str("ConnectorFn")
113 }
114 }
115 impl<T> HttpClient for ConnectorFn<T>
116 where
117 T: (Fn(&HttpConnectorSettings, &RuntimeComponents) -> SharedHttpConnector) + Send + Sync,
118 {
119 fn http_connector(
120 &self,
121 settings: &HttpConnectorSettings,
122 components: &RuntimeComponents,
123 ) -> SharedHttpConnector {
124 (self.0)(settings, components)
125 }
126 }
127
128 SharedHttpClient::new(ConnectorFn(connector))
129}
130
131pub trait HttpClient: Send + Sync + fmt::Debug {
136 fn http_connector(
145 &self,
146 settings: &HttpConnectorSettings,
147 components: &RuntimeComponents,
148 ) -> SharedHttpConnector;
149
150 #[doc = include_str!("../../rustdoc/validate_base_client_config.md")]
151 fn validate_base_client_config(
152 &self,
153 runtime_components: &RuntimeComponentsBuilder,
154 cfg: &ConfigBag,
155 ) -> Result<(), BoxError> {
156 let _ = (runtime_components, cfg);
157 Ok(())
158 }
159
160 #[doc = include_str!("../../rustdoc/validate_final_config.md")]
161 fn validate_final_config(
162 &self,
163 runtime_components: &RuntimeComponents,
164 cfg: &ConfigBag,
165 ) -> Result<(), BoxError> {
166 let _ = (runtime_components, cfg);
167 Ok(())
168 }
169
170 fn connector_metadata(&self) -> Option<ConnectorMetadata> {
176 None
177 }
178}
179
180#[derive(Clone, Debug)]
182pub struct SharedHttpClient {
183 selector: Arc<dyn HttpClient>,
184}
185
186impl SharedHttpClient {
187 pub fn new(selector: impl HttpClient + 'static) -> Self {
189 Self {
190 selector: Arc::new(selector),
191 }
192 }
193}
194
195impl HttpClient for SharedHttpClient {
196 fn http_connector(
197 &self,
198 settings: &HttpConnectorSettings,
199 components: &RuntimeComponents,
200 ) -> SharedHttpConnector {
201 self.selector.http_connector(settings, components)
202 }
203
204 fn validate_base_client_config(
205 &self,
206 runtime_components: &RuntimeComponentsBuilder,
207 cfg: &ConfigBag,
208 ) -> Result<(), BoxError> {
209 self.selector
210 .validate_base_client_config(runtime_components, cfg)
211 }
212
213 fn validate_final_config(
214 &self,
215 runtime_components: &RuntimeComponents,
216 cfg: &ConfigBag,
217 ) -> Result<(), BoxError> {
218 self.selector.validate_final_config(runtime_components, cfg)
219 }
220
221 fn connector_metadata(&self) -> Option<ConnectorMetadata> {
222 self.selector.connector_metadata()
223 }
224}
225
226impl ValidateConfig for SharedHttpClient {
227 fn validate_base_client_config(
228 &self,
229 runtime_components: &RuntimeComponentsBuilder,
230 cfg: &ConfigBag,
231 ) -> Result<(), BoxError> {
232 HttpClient::validate_base_client_config(self, runtime_components, cfg)
233 }
234
235 fn validate_final_config(
236 &self,
237 runtime_components: &RuntimeComponents,
238 cfg: &ConfigBag,
239 ) -> Result<(), BoxError> {
240 HttpClient::validate_final_config(self, runtime_components, cfg)
241 }
242}
243
244impl_shared_conversions!(convert SharedHttpClient from HttpClient using SharedHttpClient::new);
245
246#[non_exhaustive]
248#[derive(Default, Debug)]
249pub struct HttpConnectorSettingsBuilder {
250 connect_timeout: Option<Duration>,
251 read_timeout: Option<Duration>,
252}
253
254impl HttpConnectorSettingsBuilder {
255 pub fn new() -> Self {
257 Default::default()
258 }
259
260 pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
264 self.connect_timeout = Some(connect_timeout);
265 self
266 }
267
268 pub fn set_connect_timeout(&mut self, connect_timeout: Option<Duration>) -> &mut Self {
272 self.connect_timeout = connect_timeout;
273 self
274 }
275
276 pub fn read_timeout(mut self, read_timeout: Duration) -> Self {
281 self.read_timeout = Some(read_timeout);
282 self
283 }
284
285 pub fn set_read_timeout(&mut self, read_timeout: Option<Duration>) -> &mut Self {
290 self.read_timeout = read_timeout;
291 self
292 }
293
294 pub fn build(self) -> HttpConnectorSettings {
296 HttpConnectorSettings {
297 connect_timeout: self.connect_timeout,
298 read_timeout: self.read_timeout,
299 }
300 }
301}
302
303#[non_exhaustive]
305#[derive(Clone, Default, Debug)]
306pub struct HttpConnectorSettings {
307 connect_timeout: Option<Duration>,
308 read_timeout: Option<Duration>,
309}
310
311impl HttpConnectorSettings {
312 pub fn builder() -> HttpConnectorSettingsBuilder {
314 Default::default()
315 }
316
317 pub fn connect_timeout(&self) -> Option<Duration> {
321 self.connect_timeout
322 }
323
324 pub fn read_timeout(&self) -> Option<Duration> {
329 self.read_timeout
330 }
331}
332
333#[cfg(all(test, feature = "test-util"))]
334mod tests {
335 use super::*;
336 use crate::client::runtime_components::RuntimeComponentsBuilder;
337 use aws_smithy_types::config_bag::ConfigBag;
338 use std::sync::atomic::{AtomicUsize, Ordering};
339
340 #[derive(Debug, Default)]
342 struct CountingSelector {
343 base: AtomicUsize,
344 final_: AtomicUsize,
345 }
346
347 impl HttpClient for CountingSelector {
348 fn http_connector(
349 &self,
350 _settings: &HttpConnectorSettings,
351 _components: &RuntimeComponents,
352 ) -> SharedHttpConnector {
353 unreachable!("http_connector is not exercised by these tests")
354 }
355
356 fn validate_base_client_config(
357 &self,
358 _runtime_components: &RuntimeComponentsBuilder,
359 _cfg: &ConfigBag,
360 ) -> Result<(), BoxError> {
361 self.base.fetch_add(1, Ordering::SeqCst);
362 Ok(())
363 }
364
365 fn validate_final_config(
366 &self,
367 _runtime_components: &RuntimeComponents,
368 _cfg: &ConfigBag,
369 ) -> Result<(), BoxError> {
370 self.final_.fetch_add(1, Ordering::SeqCst);
371 Ok(())
372 }
373 }
374
375 #[derive(Debug)]
380 struct DecoratingClient {
381 inner: SharedHttpClient,
382 }
383
384 impl HttpClient for DecoratingClient {
385 fn http_connector(
386 &self,
387 settings: &HttpConnectorSettings,
388 components: &RuntimeComponents,
389 ) -> SharedHttpConnector {
390 self.inner.http_connector(settings, components)
391 }
392
393 fn validate_base_client_config(
394 &self,
395 runtime_components: &RuntimeComponentsBuilder,
396 cfg: &ConfigBag,
397 ) -> Result<(), BoxError> {
398 HttpClient::validate_base_client_config(&self.inner, runtime_components, cfg)
399 }
400
401 fn validate_final_config(
402 &self,
403 runtime_components: &RuntimeComponents,
404 cfg: &ConfigBag,
405 ) -> Result<(), BoxError> {
406 HttpClient::validate_final_config(&self.inner, runtime_components, cfg)
407 }
408 }
409
410 #[test]
411 fn shared_http_client_forwards_validation_to_selector() {
412 let selector = Arc::new(CountingSelector::default());
413 let shared = SharedHttpClient {
414 selector: selector.clone(),
415 };
416 let cfg = ConfigBag::base();
417 let builder = RuntimeComponentsBuilder::for_tests();
418 let components = RuntimeComponentsBuilder::for_tests().build().unwrap();
419
420 HttpClient::validate_base_client_config(&shared, &builder, &cfg).unwrap();
422 HttpClient::validate_final_config(&shared, &components, &cfg).unwrap();
423
424 ValidateConfig::validate_base_client_config(&shared, &builder, &cfg).unwrap();
426 ValidateConfig::validate_final_config(&shared, &components, &cfg).unwrap();
427
428 assert_eq!(2, selector.base.load(Ordering::SeqCst));
432 assert_eq!(2, selector.final_.load(Ordering::SeqCst));
433 }
434
435 #[test]
436 fn external_decorator_reaches_selector_through_shared_client() {
437 let selector = Arc::new(CountingSelector::default());
438 let decorator = DecoratingClient {
439 inner: SharedHttpClient {
440 selector: selector.clone(),
441 },
442 };
443 let cfg = ConfigBag::base();
444 let builder = RuntimeComponentsBuilder::for_tests();
445 let components = RuntimeComponentsBuilder::for_tests().build().unwrap();
446
447 decorator
448 .validate_base_client_config(&builder, &cfg)
449 .unwrap();
450 decorator.validate_final_config(&components, &cfg).unwrap();
451
452 assert_eq!(1, selector.base.load(Ordering::SeqCst));
453 assert_eq!(1, selector.final_.load(Ordering::SeqCst));
454 }
455}