1use crate::hyper_legacy::timeout_middleware::HttpTimeoutError;
7use aws_smithy_async::future::timeout::TimedOutError;
8use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep};
9use aws_smithy_runtime_api::box_error::BoxError;
10use aws_smithy_runtime_api::client::connection::CaptureSmithyConnection;
11use aws_smithy_runtime_api::client::connection::ConnectionMetadata;
12use aws_smithy_runtime_api::client::connector_metadata::ConnectorMetadata;
13use aws_smithy_runtime_api::client::http::{
14 HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpClient,
15 SharedHttpConnector,
16};
17use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse};
18use aws_smithy_runtime_api::client::result::ConnectorError;
19use aws_smithy_runtime_api::client::runtime_components::{
20 RuntimeComponents, RuntimeComponentsBuilder,
21};
22use aws_smithy_runtime_api::shared::IntoShared;
23use aws_smithy_types::body::SdkBody;
24use aws_smithy_types::config_bag::ConfigBag;
25use aws_smithy_types::error::display::DisplayErrorContext;
26use aws_smithy_types::retry::ErrorKind;
27use h2_0_3::Reason;
28use hyper_0_14::client::connect::{capture_connection, CaptureConnection, Connection, HttpInfo};
29use std::borrow::Cow;
30use std::collections::HashMap;
31use std::error::Error;
32use std::fmt;
33use std::sync::RwLock;
34use std::time::Duration;
35use tokio::io::{AsyncRead, AsyncWrite};
36
37#[cfg(feature = "legacy-rustls-ring")]
38mod default_connector {
39 use aws_smithy_async::rt::sleep::SharedAsyncSleep;
40 use aws_smithy_runtime_api::client::http::HttpConnectorSettings;
41 use legacy_hyper_rustls as hyper_rustls;
42 use legacy_rustls as rustls;
43 use std::sync::LazyLock;
44
45 pub(crate) static HTTPS_NATIVE_ROOTS: LazyLock<
48 hyper_rustls::HttpsConnector<hyper_0_14::client::HttpConnector>,
49 > = LazyLock::new(default_tls);
50
51 fn default_tls() -> hyper_rustls::HttpsConnector<hyper_0_14::client::HttpConnector> {
52 use legacy_rustls::client::WantsTransparencyPolicyOrClientCert;
53 use legacy_rustls::{ClientConfig, ConfigBuilder, WantsVerifier};
54 use rustls_native_certs;
55 fn with_native_roots(
58 this: ConfigBuilder<ClientConfig, WantsVerifier>,
59 ) -> ConfigBuilder<ClientConfig, WantsTransparencyPolicyOrClientCert> {
60 let mut roots = rustls::RootCertStore::empty();
61 let mut valid_count = 0;
62 let mut invalid_count = 0;
63
64 for cert in
65 rustls_native_certs::load_native_certs().expect("could not load platform certs")
66 {
67 let cert = rustls::Certificate(cert.to_vec());
68 match roots.add(&cert) {
69 Ok(_) => valid_count += 1,
70 Err(err) => {
71 tracing::trace!("invalid cert der {:?}", cert.0);
72 tracing::debug!("certificate parsing failed: {:?}", err);
73 invalid_count += 1
74 }
75 }
76 }
77 tracing::debug!(
78 "with_native_roots processed {} valid and {} invalid certs",
79 valid_count,
80 invalid_count
81 );
82 assert!(!roots.is_empty(), "no CA certificates found");
83
84 this.with_root_certificates(roots)
85 }
86 hyper_rustls::HttpsConnectorBuilder::new()
87 .with_tls_config(
88 with_native_roots(rustls::ClientConfig::builder()
89 .with_cipher_suites(&[
90 rustls::cipher_suite::TLS13_AES_256_GCM_SHA384,
92 rustls::cipher_suite::TLS13_AES_128_GCM_SHA256,
93 rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
95 rustls::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
96 rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
97 rustls::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
98 rustls::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
99 ])
100 .with_safe_default_kx_groups()
101 .with_safe_default_protocol_versions()
102 .expect("Error with the TLS configuration. Please file a bug report under https://github.com/smithy-lang/smithy-rs/issues."))
103 .with_no_client_auth()
104 )
105 .https_or_http()
106 .enable_http1()
107 .enable_http2()
108 .build()
109 }
110
111 pub(super) fn base(
112 settings: &HttpConnectorSettings,
113 sleep: Option<SharedAsyncSleep>,
114 ) -> super::HyperConnectorBuilder {
115 let mut hyper = super::HyperConnector::builder().connector_settings(settings.clone());
116 if let Some(sleep) = sleep {
117 hyper = hyper.sleep_impl(sleep);
118 }
119 hyper
120 }
121
122 pub(super) fn https() -> hyper_rustls::HttpsConnector<hyper_0_14::client::HttpConnector> {
127 HTTPS_NATIVE_ROOTS.clone()
128 }
129}
130
131pub fn default_connector(
133 settings: &HttpConnectorSettings,
134 sleep: Option<SharedAsyncSleep>,
135) -> Option<SharedHttpConnector> {
136 #[cfg(feature = "legacy-rustls-ring")]
137 {
138 tracing::trace!(settings = ?settings, sleep = ?sleep, "creating a new default connector");
139 let hyper = default_connector::base(settings, sleep).build_https();
140 Some(SharedHttpConnector::new(hyper))
141 }
142 #[cfg(not(feature = "legacy-rustls-ring"))]
143 {
144 tracing::trace!(settings = ?settings, sleep = ?sleep, "no default connector available");
145 None
146 }
147}
148
149pub fn default_client() -> Option<SharedHttpClient> {
151 #[cfg(feature = "legacy-rustls-ring")]
152 {
153 tracing::trace!("creating a new default hyper 0.14.x client");
154 Some(HyperClientBuilder::new().build_https())
155 }
156 #[cfg(not(feature = "legacy-rustls-ring"))]
157 {
158 tracing::trace!("no default connector available");
159 None
160 }
161}
162
163#[derive(Debug)]
171pub struct HyperConnector {
172 adapter: Box<dyn HttpConnector>,
173}
174
175impl HyperConnector {
176 pub fn builder() -> HyperConnectorBuilder {
178 Default::default()
179 }
180}
181
182impl HttpConnector for HyperConnector {
183 fn call(&self, request: HttpRequest) -> HttpConnectorFuture {
184 self.adapter.call(request)
185 }
186}
187
188#[derive(Default, Debug)]
190pub struct HyperConnectorBuilder {
191 connector_settings: Option<HttpConnectorSettings>,
192 sleep_impl: Option<SharedAsyncSleep>,
193 client_builder: Option<hyper_0_14::client::Builder>,
194}
195
196impl HyperConnectorBuilder {
197 pub fn build<C>(self, tcp_connector: C) -> HyperConnector
199 where
200 C: Clone + Send + Sync + 'static,
201 C: hyper_0_14::service::Service<http_02x::Uri>,
202 C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static,
203 C::Future: Unpin + Send + 'static,
204 C::Error: Into<BoxError>,
205 {
206 let client_builder = self.client_builder.unwrap_or_default();
207 let sleep_impl = self.sleep_impl.or_else(default_async_sleep);
208 let (connect_timeout, read_timeout) = self
209 .connector_settings
210 .map(|c| (c.connect_timeout(), c.read_timeout()))
211 .unwrap_or((None, None));
212
213 let connector = match connect_timeout {
214 Some(duration) => timeout_middleware::ConnectTimeout::new(
215 tcp_connector,
216 sleep_impl
217 .clone()
218 .expect("a sleep impl must be provided in order to have a connect timeout"),
219 duration,
220 ),
221 None => timeout_middleware::ConnectTimeout::no_timeout(tcp_connector),
222 };
223 let base = client_builder.build(connector);
224 let read_timeout = match read_timeout {
225 Some(duration) => timeout_middleware::HttpReadTimeout::new(
226 base,
227 sleep_impl.expect("a sleep impl must be provided in order to have a read timeout"),
228 duration,
229 ),
230 None => timeout_middleware::HttpReadTimeout::no_timeout(base),
231 };
232 HyperConnector {
233 adapter: Box::new(Adapter {
234 client: read_timeout,
235 }),
236 }
237 }
238
239 #[cfg(feature = "legacy-rustls-ring")]
241 pub fn build_https(self) -> HyperConnector {
242 self.build(default_connector::https())
243 }
244
245 pub fn sleep_impl(mut self, sleep_impl: impl AsyncSleep + 'static) -> Self {
250 self.sleep_impl = Some(sleep_impl.into_shared());
251 self
252 }
253
254 pub fn set_sleep_impl(&mut self, sleep_impl: Option<SharedAsyncSleep>) -> &mut Self {
259 self.sleep_impl = sleep_impl;
260 self
261 }
262
263 pub fn connector_settings(mut self, connector_settings: HttpConnectorSettings) -> Self {
265 self.connector_settings = Some(connector_settings);
266 self
267 }
268
269 pub fn set_connector_settings(
271 &mut self,
272 connector_settings: Option<HttpConnectorSettings>,
273 ) -> &mut Self {
274 self.connector_settings = connector_settings;
275 self
276 }
277
278 pub fn hyper_builder(mut self, hyper_builder: hyper_0_14::client::Builder) -> Self {
282 self.client_builder = Some(hyper_builder);
283 self
284 }
285
286 pub fn set_hyper_builder(
290 &mut self,
291 hyper_builder: Option<hyper_0_14::client::Builder>,
292 ) -> &mut Self {
293 self.client_builder = hyper_builder;
294 self
295 }
296}
297
298struct Adapter<C> {
302 client: timeout_middleware::HttpReadTimeout<
303 hyper_0_14::Client<timeout_middleware::ConnectTimeout<C>, SdkBody>,
304 >,
305}
306
307impl<C> fmt::Debug for Adapter<C> {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 f.debug_struct("Adapter")
310 .field("client", &"** hyper client **")
311 .finish()
312 }
313}
314
315fn extract_smithy_connection(capture_conn: &CaptureConnection) -> Option<ConnectionMetadata> {
317 let capture_conn = capture_conn.clone();
318 if let Some(conn) = capture_conn.clone().connection_metadata().as_ref() {
319 let mut extensions = http_02x::Extensions::new();
320 conn.get_extras(&mut extensions);
321 let http_info = extensions.get::<HttpInfo>();
322 let mut builder = ConnectionMetadata::builder()
323 .proxied(conn.is_proxied())
324 .poison_fn(move || match capture_conn.connection_metadata().as_ref() {
325 Some(conn) => conn.poison(),
326 None => tracing::trace!("no connection existed to poison"),
327 });
328
329 builder
330 .set_local_addr(http_info.map(|info| info.local_addr()))
331 .set_remote_addr(http_info.map(|info| info.remote_addr()));
332
333 let smithy_connection = builder.build();
334
335 Some(smithy_connection)
336 } else {
337 None
338 }
339}
340
341impl<C> HttpConnector for Adapter<C>
342where
343 C: Clone + Send + Sync + 'static,
344 C: hyper_0_14::service::Service<http_02x::Uri>,
345 C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static,
346 C::Future: Unpin + Send + 'static,
347 C::Error: Into<BoxError>,
348{
349 fn call(&self, request: HttpRequest) -> HttpConnectorFuture {
350 use hyper_0_14::service::Service;
351
352 let mut request = match request.try_into_http02x() {
353 Ok(request) => request,
354 Err(err) => {
355 return HttpConnectorFuture::ready(Err(ConnectorError::other(err.into(), None)));
356 }
357 };
358 let capture_connection = capture_connection(&mut request);
359 if let Some(capture_smithy_connection) =
360 request.extensions().get::<CaptureSmithyConnection>()
361 {
362 capture_smithy_connection
363 .set_connection_retriever(move || extract_smithy_connection(&capture_connection));
364 }
365 let mut client = self.client.clone();
366 let fut = client.call(request);
367 HttpConnectorFuture::new(async move {
368 match fut.await {
369 Ok(response) => HttpResponse::try_from(response.map(SdkBody::from_body_0_4))
370 .map_err(|err| ConnectorError::other(err.into(), None)),
371 Err(err) => Err(downcast_error(err)),
372 }
373 })
374 }
375}
376
377fn downcast_error(err: BoxError) -> ConnectorError {
379 if find_source::<TimedOutError>(err.as_ref()).is_some() {
381 return ConnectorError::timeout(err);
382 }
383 let err = match err.downcast::<ConnectorError>() {
385 Ok(connector_error) => return *connector_error,
386 Err(box_error) => box_error,
387 };
388 let err = match err.downcast::<hyper_0_14::Error>() {
391 Ok(hyper_error) => return to_connector_error(*hyper_error),
392 Err(box_error) => box_error,
393 };
394
395 ConnectorError::other(err, None)
397}
398
399fn to_connector_error(err: hyper_0_14::Error) -> ConnectorError {
401 if err.is_timeout() || find_source::<HttpTimeoutError>(&err).is_some() {
402 return ConnectorError::timeout(err.into());
403 }
404 if err.is_user() {
405 return ConnectorError::user(err.into());
406 }
407 if err.is_closed() || err.is_canceled() || find_source::<std::io::Error>(&err).is_some() {
408 return ConnectorError::io(err.into());
409 }
410 if err.is_incomplete_message() {
412 return ConnectorError::other(err.into(), Some(ErrorKind::TransientError));
413 }
414 if let Some(h2_err) = find_source::<h2_0_3::Error>(&err) {
415 if h2_err.is_go_away()
416 || (h2_err.is_reset() && h2_err.reason() == Some(Reason::REFUSED_STREAM))
417 {
418 return ConnectorError::io(err.into());
419 }
420 }
421
422 tracing::warn!(err = %DisplayErrorContext(&err), "unrecognized error from Hyper. If this error should be retried, please file an issue.");
423 ConnectorError::other(err.into(), None)
424}
425
426fn find_source<'a, E: Error + 'static>(err: &'a (dyn Error + 'static)) -> Option<&'a E> {
427 let mut next = Some(err);
428 while let Some(err) = next {
429 if let Some(matching_err) = err.downcast_ref::<E>() {
430 return Some(matching_err);
431 }
432 next = err.source();
433 }
434 None
435}
436
437#[derive(Clone, Debug, Eq, PartialEq, Hash)]
438struct CacheKey {
439 connect_timeout: Option<Duration>,
440 read_timeout: Option<Duration>,
441}
442
443impl From<&HttpConnectorSettings> for CacheKey {
444 fn from(value: &HttpConnectorSettings) -> Self {
445 Self {
446 connect_timeout: value.connect_timeout(),
447 read_timeout: value.read_timeout(),
448 }
449 }
450}
451
452struct HyperClient<F> {
453 connector_cache: RwLock<HashMap<CacheKey, SharedHttpConnector>>,
454 client_builder: hyper_0_14::client::Builder,
455 tcp_connector_fn: F,
456}
457
458impl<F> fmt::Debug for HyperClient<F> {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460 f.debug_struct("HyperClient")
461 .field("connector_cache", &self.connector_cache)
462 .field("client_builder", &self.client_builder)
463 .finish()
464 }
465}
466
467impl<C, F> HttpClient for HyperClient<F>
468where
469 F: Fn() -> C + Send + Sync,
470 C: Clone + Send + Sync + 'static,
471 C: hyper_0_14::service::Service<http_02x::Uri>,
472 C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static,
473 C::Future: Unpin + Send + 'static,
474 C::Error: Into<BoxError>,
475{
476 fn http_connector(
477 &self,
478 settings: &HttpConnectorSettings,
479 components: &RuntimeComponents,
480 ) -> SharedHttpConnector {
481 let key = CacheKey::from(settings);
482 let mut connector = self.connector_cache.read().unwrap().get(&key).cloned();
483 if connector.is_none() {
484 let mut cache = self.connector_cache.write().unwrap();
485 if !cache.contains_key(&key) {
487 let mut builder = HyperConnector::builder()
488 .hyper_builder(self.client_builder.clone())
489 .connector_settings(settings.clone());
490 builder.set_sleep_impl(components.sleep_impl());
491 let start = components.time_source().map(|ts| ts.now());
492 let tcp_connector = (self.tcp_connector_fn)();
493 let end = components.time_source().map(|ts| ts.now());
494 if let (Some(start), Some(end)) = (start, end) {
495 if let Ok(elapsed) = end.duration_since(start) {
496 tracing::debug!("new TCP connector created in {:?}", elapsed);
497 }
498 }
499 let connector = SharedHttpConnector::new(builder.build(tcp_connector));
500 cache.insert(key.clone(), connector);
501 }
502 connector = cache.get(&key).cloned();
503 }
504
505 connector.expect("cache populated above")
506 }
507
508 fn validate_base_client_config(
509 &self,
510 _: &RuntimeComponentsBuilder,
511 _: &ConfigBag,
512 ) -> Result<(), BoxError> {
513 let _ = (self.tcp_connector_fn)();
519 Ok(())
520 }
521
522 fn connector_metadata(&self) -> Option<ConnectorMetadata> {
523 Some(ConnectorMetadata::new("hyper", Some(Cow::Borrowed("0.x"))))
524 }
525}
526
527#[derive(Clone, Default, Debug)]
581pub struct HyperClientBuilder {
582 client_builder: Option<hyper_0_14::client::Builder>,
583}
584
585impl HyperClientBuilder {
586 pub fn new() -> Self {
588 Self::default()
589 }
590
591 pub fn hyper_builder(mut self, hyper_builder: hyper_0_14::client::Builder) -> Self {
595 self.client_builder = Some(hyper_builder);
596 self
597 }
598
599 pub fn set_hyper_builder(
603 &mut self,
604 hyper_builder: Option<hyper_0_14::client::Builder>,
605 ) -> &mut Self {
606 self.client_builder = hyper_builder;
607 self
608 }
609
610 #[cfg(feature = "legacy-rustls-ring")]
615 pub fn build_https(self) -> SharedHttpClient {
616 self.build_with_fn(default_connector::https)
617 }
618
619 #[cfg_attr(
622 feature = "legacy-rustls-ring",
623 doc = "Use [`build_https`](HyperClientBuilder::build_https) if you don't want to provide a custom TCP connector."
624 )]
625 pub fn build<C>(self, tcp_connector: C) -> SharedHttpClient
626 where
627 C: Clone + Send + Sync + 'static,
628 C: hyper_0_14::service::Service<http_02x::Uri>,
629 C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static,
630 C::Future: Unpin + Send + 'static,
631 C::Error: Into<BoxError>,
632 {
633 self.build_with_fn(move || tcp_connector.clone())
634 }
635
636 fn build_with_fn<C, F>(self, tcp_connector_fn: F) -> SharedHttpClient
637 where
638 F: Fn() -> C + Send + Sync + 'static,
639 C: Clone + Send + Sync + 'static,
640 C: hyper_0_14::service::Service<http_02x::Uri>,
641 C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static,
642 C::Future: Unpin + Send + 'static,
643 C::Error: Into<BoxError>,
644 {
645 SharedHttpClient::new(HyperClient {
646 connector_cache: RwLock::new(HashMap::new()),
647 client_builder: self.client_builder.unwrap_or_default(),
648 tcp_connector_fn,
649 })
650 }
651}
652
653mod timeout_middleware {
654 use aws_smithy_async::future::timeout::{TimedOutError, Timeout};
655 use aws_smithy_async::rt::sleep::Sleep;
656 use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep};
657 use aws_smithy_runtime_api::box_error::BoxError;
658 use pin_project_lite::pin_project;
659 use std::error::Error;
660 use std::fmt::Formatter;
661 use std::future::Future;
662 use std::pin::Pin;
663 use std::task::{Context, Poll};
664 use std::time::Duration;
665
666 #[derive(Debug)]
667 pub(crate) struct HttpTimeoutError {
668 kind: &'static str,
669 duration: Duration,
670 }
671
672 impl std::fmt::Display for HttpTimeoutError {
673 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
674 write!(
675 f,
676 "{} timeout occurred after {:?}",
677 self.kind, self.duration
678 )
679 }
680 }
681
682 impl Error for HttpTimeoutError {
683 fn source(&self) -> Option<&(dyn Error + 'static)> {
687 Some(&TimedOutError)
688 }
689 }
690
691 #[derive(Clone, Debug)]
696 pub(super) struct ConnectTimeout<I> {
697 inner: I,
698 timeout: Option<(SharedAsyncSleep, Duration)>,
699 }
700
701 impl<I> ConnectTimeout<I> {
702 pub(crate) fn new(inner: I, sleep: SharedAsyncSleep, timeout: Duration) -> Self {
706 Self {
707 inner,
708 timeout: Some((sleep, timeout)),
709 }
710 }
711
712 pub(crate) fn no_timeout(inner: I) -> Self {
713 Self {
714 inner,
715 timeout: None,
716 }
717 }
718 }
719
720 #[derive(Clone, Debug)]
721 pub(crate) struct HttpReadTimeout<I> {
722 inner: I,
723 timeout: Option<(SharedAsyncSleep, Duration)>,
724 }
725
726 impl<I> HttpReadTimeout<I> {
727 pub(crate) fn new(inner: I, sleep: SharedAsyncSleep, timeout: Duration) -> Self {
731 Self {
732 inner,
733 timeout: Some((sleep, timeout)),
734 }
735 }
736
737 pub(crate) fn no_timeout(inner: I) -> Self {
738 Self {
739 inner,
740 timeout: None,
741 }
742 }
743 }
744
745 pin_project! {
746 #[project = MaybeTimeoutFutureProj]
751 pub enum MaybeTimeoutFuture<F> {
752 Timeout {
753 #[pin]
754 timeout: Timeout<F, Sleep>,
755 error_type: &'static str,
756 duration: Duration,
757 },
758 NoTimeout {
759 #[pin]
760 future: F
761 }
762 }
763 }
764
765 impl<F, T, E> Future for MaybeTimeoutFuture<F>
766 where
767 F: Future<Output = Result<T, E>>,
768 E: Into<BoxError>,
769 {
770 type Output = Result<T, BoxError>;
771
772 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
773 let (timeout_future, kind, &mut duration) = match self.project() {
774 MaybeTimeoutFutureProj::NoTimeout { future } => {
775 return future.poll(cx).map_err(|err| err.into());
776 }
777 MaybeTimeoutFutureProj::Timeout {
778 timeout,
779 error_type,
780 duration,
781 } => (timeout, error_type, duration),
782 };
783 match timeout_future.poll(cx) {
784 Poll::Ready(Ok(response)) => Poll::Ready(response.map_err(|err| err.into())),
785 Poll::Ready(Err(_timeout)) => {
786 Poll::Ready(Err(HttpTimeoutError { kind, duration }.into()))
787 }
788 Poll::Pending => Poll::Pending,
789 }
790 }
791 }
792
793 impl<I> hyper_0_14::service::Service<http_02x::Uri> for ConnectTimeout<I>
794 where
795 I: hyper_0_14::service::Service<http_02x::Uri>,
796 I::Error: Into<BoxError>,
797 {
798 type Response = I::Response;
799 type Error = BoxError;
800 type Future = MaybeTimeoutFuture<I::Future>;
801
802 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
803 self.inner.poll_ready(cx).map_err(|err| err.into())
804 }
805
806 fn call(&mut self, req: http_02x::Uri) -> Self::Future {
807 match &self.timeout {
808 Some((sleep, duration)) => {
809 let sleep = sleep.sleep(*duration);
810 MaybeTimeoutFuture::Timeout {
811 timeout: Timeout::new(self.inner.call(req), sleep),
812 error_type: "HTTP connect",
813 duration: *duration,
814 }
815 }
816 None => MaybeTimeoutFuture::NoTimeout {
817 future: self.inner.call(req),
818 },
819 }
820 }
821 }
822
823 impl<I, B> hyper_0_14::service::Service<http_02x::Request<B>> for HttpReadTimeout<I>
824 where
825 I: hyper_0_14::service::Service<http_02x::Request<B>, Error = hyper_0_14::Error>,
826 {
827 type Response = I::Response;
828 type Error = BoxError;
829 type Future = MaybeTimeoutFuture<I::Future>;
830
831 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
832 self.inner.poll_ready(cx).map_err(|err| err.into())
833 }
834
835 fn call(&mut self, req: http_02x::Request<B>) -> Self::Future {
836 match &self.timeout {
837 Some((sleep, duration)) => {
838 let sleep = sleep.sleep(*duration);
839 MaybeTimeoutFuture::Timeout {
840 timeout: Timeout::new(self.inner.call(req), sleep),
841 error_type: "HTTP read",
842 duration: *duration,
843 }
844 }
845 None => MaybeTimeoutFuture::NoTimeout {
846 future: self.inner.call(req),
847 },
848 }
849 }
850 }
851
852 #[cfg(test)]
853 pub(crate) mod test {
854 use crate::hyper_014::HyperConnector;
855 use aws_smithy_async::assert_elapsed;
856 use aws_smithy_async::future::never::Never;
857 use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep};
858 use aws_smithy_runtime_api::box_error::BoxError;
859 use aws_smithy_runtime_api::client::http::HttpConnectorSettings;
860 use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
861 use aws_smithy_runtime_api::client::result::ConnectorError;
862 use aws_smithy_types::error::display::DisplayErrorContext;
863 use hyper_0_14::client::connect::{Connected, Connection};
864 use std::future::Future;
865 use std::pin::Pin;
866 use std::task::{Context, Poll};
867 use std::time::Duration;
868 use tokio::io::ReadBuf;
869 use tokio::io::{AsyncRead, AsyncWrite};
870 use tokio::net::TcpStream;
871
872 #[allow(unused)]
873 fn connect_timeout_is_correct<T: Send + Sync + Clone + 'static>() {
874 is_send_sync::<super::ConnectTimeout<T>>();
875 }
876
877 #[allow(unused)]
878 fn is_send_sync<T: Send + Sync>() {}
879
880 #[non_exhaustive]
884 #[derive(Clone, Default, Debug)]
885 pub(crate) struct NeverConnects;
886 impl hyper_0_14::service::Service<http_02x::Uri> for NeverConnects {
887 type Response = TcpStream;
888 type Error = ConnectorError;
889 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
890
891 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
892 Poll::Ready(Ok(()))
893 }
894
895 fn call(&mut self, _uri: http_02x::Uri) -> Self::Future {
896 Box::pin(async move {
897 Never::new().await;
898 unreachable!()
899 })
900 }
901 }
902
903 #[derive(Clone, Debug, Default)]
905 struct NeverReplies;
906 impl hyper_0_14::service::Service<http_02x::Uri> for NeverReplies {
907 type Response = EmptyStream;
908 type Error = BoxError;
909 type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
910
911 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
912 Poll::Ready(Ok(()))
913 }
914
915 fn call(&mut self, _req: http_02x::Uri) -> Self::Future {
916 std::future::ready(Ok(EmptyStream))
917 }
918 }
919
920 #[non_exhaustive]
922 #[derive(Debug, Default)]
923 struct EmptyStream;
924 impl AsyncRead for EmptyStream {
925 fn poll_read(
926 self: Pin<&mut Self>,
927 _cx: &mut Context<'_>,
928 _buf: &mut ReadBuf<'_>,
929 ) -> Poll<std::io::Result<()>> {
930 Poll::Pending
931 }
932 }
933 impl AsyncWrite for EmptyStream {
934 fn poll_write(
935 self: Pin<&mut Self>,
936 _cx: &mut Context<'_>,
937 _buf: &[u8],
938 ) -> Poll<Result<usize, std::io::Error>> {
939 Poll::Pending
940 }
941
942 fn poll_flush(
943 self: Pin<&mut Self>,
944 _cx: &mut Context<'_>,
945 ) -> Poll<Result<(), std::io::Error>> {
946 Poll::Pending
947 }
948
949 fn poll_shutdown(
950 self: Pin<&mut Self>,
951 _cx: &mut Context<'_>,
952 ) -> Poll<Result<(), std::io::Error>> {
953 Poll::Pending
954 }
955 }
956 impl Connection for EmptyStream {
957 fn connected(&self) -> Connected {
958 Connected::new()
959 }
960 }
961
962 #[tokio::test]
963 async fn http_connect_timeout_works() {
964 let tcp_connector = NeverConnects::default();
965 let connector_settings = HttpConnectorSettings::builder()
966 .connect_timeout(Duration::from_secs(1))
967 .build();
968 let hyper = HyperConnector::builder()
969 .connector_settings(connector_settings)
970 .sleep_impl(SharedAsyncSleep::new(TokioSleep::new()))
971 .build(tcp_connector)
972 .adapter;
973 let now = tokio::time::Instant::now();
974 tokio::time::pause();
975 let resp = hyper
976 .call(HttpRequest::get("https://static-uri.com").unwrap())
977 .await
978 .unwrap_err();
979 assert!(
980 resp.is_timeout(),
981 "expected resp.is_timeout() to be true but it was false, resp == {:?}",
982 resp
983 );
984 let message = DisplayErrorContext(&resp).to_string();
985 let expected =
986 "timeout: error trying to connect: HTTP connect timeout occurred after 1s";
987 assert!(
988 message.contains(expected),
989 "expected '{message}' to contain '{expected}'"
990 );
991 assert_elapsed!(now, Duration::from_secs(1));
992 }
993
994 #[tokio::test]
995 async fn http_read_timeout_works() {
996 let tcp_connector = NeverReplies;
997 let connector_settings = HttpConnectorSettings::builder()
998 .connect_timeout(Duration::from_secs(1))
999 .read_timeout(Duration::from_secs(2))
1000 .build();
1001 let hyper = HyperConnector::builder()
1002 .connector_settings(connector_settings)
1003 .sleep_impl(SharedAsyncSleep::new(TokioSleep::new()))
1004 .build(tcp_connector)
1005 .adapter;
1006 let now = tokio::time::Instant::now();
1007 tokio::time::pause();
1008 let err = hyper
1009 .call(HttpRequest::get("https://fake-uri.com").unwrap())
1010 .await
1011 .unwrap_err();
1012 assert!(
1013 err.is_timeout(),
1014 "expected err.is_timeout() to be true but it was false, err == {err:?}",
1015 );
1016 let message = format!("{}", DisplayErrorContext(&err));
1017 let expected = "timeout: HTTP read timeout occurred after 2s";
1018 assert!(
1019 message.contains(expected),
1020 "expected '{message}' to contain '{expected}'"
1021 );
1022 assert_elapsed!(now, Duration::from_secs(2));
1023 }
1024 }
1025}
1026
1027#[cfg(test)]
1028mod test {
1029 use crate::hyper_legacy::timeout_middleware::test::NeverConnects;
1030 use crate::hyper_legacy::{HyperClientBuilder, HyperConnector};
1031 use aws_smithy_async::time::SystemTimeSource;
1032 use aws_smithy_runtime_api::box_error::BoxError;
1033 use aws_smithy_runtime_api::client::http::{HttpClient, HttpConnectorSettings};
1034 use aws_smithy_runtime_api::client::orchestrator::HttpRequest;
1035 use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder;
1036 use hyper_0_14::client::connect::{Connected, Connection};
1037 use std::io::{Error, ErrorKind};
1038 use std::pin::Pin;
1039 use std::sync::atomic::{AtomicU32, Ordering};
1040 use std::sync::Arc;
1041 use std::task::{Context, Poll};
1042 use std::time::Duration;
1043 use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
1044
1045 #[tokio::test]
1046 async fn connector_selection() {
1047 let creation_count = Arc::new(AtomicU32::new(0));
1049 let http_client = HyperClientBuilder::new().build_with_fn({
1050 let count = creation_count.clone();
1051 move || {
1052 count.fetch_add(1, Ordering::Relaxed);
1053 NeverConnects::default()
1054 }
1055 });
1056
1057 let settings = [
1059 HttpConnectorSettings::builder()
1060 .connect_timeout(Duration::from_secs(3))
1061 .build(),
1062 HttpConnectorSettings::builder()
1063 .read_timeout(Duration::from_secs(3))
1064 .build(),
1065 HttpConnectorSettings::builder()
1066 .connect_timeout(Duration::from_secs(3))
1067 .read_timeout(Duration::from_secs(3))
1068 .build(),
1069 HttpConnectorSettings::builder()
1070 .connect_timeout(Duration::from_secs(5))
1071 .read_timeout(Duration::from_secs(3))
1072 .build(),
1073 ];
1074
1075 let components = RuntimeComponentsBuilder::for_tests()
1077 .with_time_source(Some(SystemTimeSource::new()))
1078 .build()
1079 .unwrap();
1080 let mut handles = Vec::new();
1081 for setting in &settings {
1082 for _ in 0..1000 {
1083 let client = http_client.clone();
1084 handles.push(tokio::spawn({
1085 let setting = setting.clone();
1086 let components = components.clone();
1087 async move {
1088 let _ = client.http_connector(&setting, &components);
1089 }
1090 }));
1091 }
1092 }
1093 for handle in handles {
1094 handle.await.unwrap();
1095 }
1096
1097 assert_eq!(4, creation_count.load(Ordering::Relaxed));
1099 }
1100
1101 #[tokio::test]
1102 async fn hyper_io_error() {
1103 let connector = TestConnection {
1104 inner: HangupStream,
1105 };
1106 let adapter = HyperConnector::builder().build(connector).adapter;
1107 let err = adapter
1108 .call(HttpRequest::get("https://socket-hangup.com").unwrap())
1109 .await
1110 .expect_err("socket hangup");
1111 assert!(err.is_io(), "{:?}", err);
1112 }
1113
1114 #[derive(Clone)]
1116 struct HangupStream;
1117
1118 impl Connection for HangupStream {
1119 fn connected(&self) -> Connected {
1120 Connected::new()
1121 }
1122 }
1123
1124 impl AsyncRead for HangupStream {
1125 fn poll_read(
1126 self: Pin<&mut Self>,
1127 _cx: &mut Context<'_>,
1128 _buf: &mut ReadBuf<'_>,
1129 ) -> Poll<std::io::Result<()>> {
1130 Poll::Ready(Err(Error::new(
1131 ErrorKind::ConnectionReset,
1132 "connection reset",
1133 )))
1134 }
1135 }
1136
1137 impl AsyncWrite for HangupStream {
1138 fn poll_write(
1139 self: Pin<&mut Self>,
1140 _cx: &mut Context<'_>,
1141 _buf: &[u8],
1142 ) -> Poll<Result<usize, Error>> {
1143 Poll::Pending
1144 }
1145
1146 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
1147 Poll::Pending
1148 }
1149
1150 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
1151 Poll::Pending
1152 }
1153 }
1154
1155 #[derive(Clone)]
1156 struct TestConnection<T> {
1157 inner: T,
1158 }
1159
1160 impl<T> hyper_0_14::service::Service<http_02x::Uri> for TestConnection<T>
1161 where
1162 T: Clone + Connection,
1163 {
1164 type Response = T;
1165 type Error = BoxError;
1166 type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
1167
1168 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
1169 Poll::Ready(Ok(()))
1170 }
1171
1172 fn call(&mut self, _req: http_02x::Uri) -> Self::Future {
1173 std::future::ready(Ok(self.inner.clone()))
1174 }
1175 }
1176}