diff --git a/aws/rust-runtime/aws-config/Cargo.toml b/aws/rust-runtime/aws-config/Cargo.toml index 2e9d00f08a1..00da2bc351c 100644 --- a/aws/rust-runtime/aws-config/Cargo.toml +++ b/aws/rust-runtime/aws-config/Cargo.toml @@ -9,11 +9,10 @@ license = "Apache-2.0" repository = "https://github.com/awslabs/smithy-rs" [features] -client-hyper = ["aws-smithy-client/client-hyper", "aws-smithy-runtime/connector-hyper"] -rustls = ["aws-smithy-client/rustls", "client-hyper"] -native-tls = [] +client-hyper = ["aws-smithy-runtime/connector-hyper-0-14-x"] +rustls = ["aws-smithy-runtime/tls-rustls", "client-hyper"] allow-compilation = [] # our tests use `cargo test --all-features` and native-tls breaks CI -rt-tokio = ["aws-smithy-async/rt-tokio", "tokio/rt"] +rt-tokio = ["aws-smithy-async/rt-tokio", "aws-smithy-runtime/rt-tokio", "tokio/rt"] credentials-sso = ["dep:aws-sdk-sso", "dep:ring", "dep:hex", "dep:zeroize"] default = ["client-hyper", "rustls", "rt-tokio", "credentials-sso"] @@ -23,9 +22,7 @@ aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-type aws-http = { path = "../../sdk/build/aws-sdk/sdk/aws-http" } aws-sdk-sts = { path = "../../sdk/build/aws-sdk/sdk/sts", default-features = false } aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async" } -aws-smithy-client = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-client", default-features = false } aws-smithy-http = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-http" } -aws-smithy-http-tower = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-http-tower" } aws-smithy-json = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-json" } aws-smithy-runtime = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client"] } aws-smithy-runtime-api = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["client"] } @@ -42,7 +39,6 @@ fastrand = "2.0.0" bytes = "1.1.0" http = "0.2.4" -tower = { version = "0.4.8" } # implementation detail of SSO credential caching aws-sdk-sso = { path = "../../sdk/build/aws-sdk/sdk/sso", default-features = false, optional = true } @@ -66,7 +62,6 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" aws-credential-types = { path = "../../sdk/build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } -aws-smithy-client = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rt-tokio", "client-hyper"] } # used for a usage example hyper-rustls = { version = "0.24", features = ["webpki-tokio", "http2", "http1"] } diff --git a/aws/rust-runtime/aws-config/external-types.toml b/aws/rust-runtime/aws-config/external-types.toml index b90a84885cb..38d857870c1 100644 --- a/aws/rust-runtime/aws-config/external-types.toml +++ b/aws/rust-runtime/aws-config/external-types.toml @@ -9,17 +9,16 @@ allowed_external_types = [ "aws_credential_types::provider::SharedCredentialsProvider", "aws_sdk_sts::types::_policy_descriptor_type::PolicyDescriptorType", "aws_smithy_async::rt::sleep::AsyncSleep", + "aws_smithy_async::rt::sleep::SharedAsyncSleep", + "aws_smithy_async::time::SharedTimeSource", "aws_smithy_async::time::TimeSource", - "aws_smithy_client::bounds::SmithyConnector", - "aws_smithy_client::conns::default_connector::default_connector", - "aws_smithy_client::erase::DynConnector", - "aws_smithy_client::erase::boxclone::BoxCloneService", - "aws_smithy_client::http_connector::ConnectorSettings", - "aws_smithy_client::http_connector::HttpConnector", "aws_smithy_http::body::SdkBody", "aws_smithy_http::endpoint", "aws_smithy_http::endpoint::error::InvalidEndpointError", "aws_smithy_http::result::SdkError", + "aws_smithy_runtime_api::client::dns::SharedDnsResolver", + "aws_smithy_runtime_api::client::http::SharedHttpClient", + "aws_smithy_runtime_api::shared::IntoShared", "aws_smithy_types::retry", "aws_smithy_types::retry::*", "aws_smithy_types::timeout", diff --git a/aws/rust-runtime/aws-config/src/connector.rs b/aws/rust-runtime/aws-config/src/connector.rs deleted file mode 100644 index 33c820261fa..00000000000 --- a/aws/rust-runtime/aws-config/src/connector.rs +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Functionality related to creating new HTTP Connectors - -use aws_smithy_client::erase::DynConnector; - -/// Unwrap an [`Option`](aws_smithy_client::erase::DynConnector), and panic with a helpful error message if it's `None` -pub(crate) fn expect_connector(for_what: &str, connector: Option) -> DynConnector { - if let Some(conn) = connector { - conn - } else { - panic!("{for_what} require(s) a HTTP connector, but none was available. Enable the `rustls` crate feature or set a connector to fix this.") - } -} - -#[cfg(feature = "client-hyper")] -pub use aws_smithy_client::conns::default_connector; - -#[cfg(all(feature = "native-tls", not(feature = "allow-compilation")))] -compile_error!("Feature native-tls has been removed. For upgrade instructions, see: https://awslabs.github.io/smithy-rs/design/transport/connector.html"); - -/// Given `ConnectorSettings` and a [`SharedAsyncSleep`](aws_smithy_async::rt::sleep::SharedAsyncSleep), create a `DynConnector` from defaults depending on what cargo features are activated. -#[cfg(not(feature = "client-hyper"))] -pub fn default_connector( - _settings: &aws_smithy_client::http_connector::ConnectorSettings, - _sleep: Option, -) -> Option { - None -} diff --git a/aws/rust-runtime/aws-config/src/default_provider/app_name.rs b/aws/rust-runtime/aws-config/src/default_provider/app_name.rs index 041d0f82612..55e87f82bc1 100644 --- a/aws/rust-runtime/aws-config/src/default_provider/app_name.rs +++ b/aws/rust-runtime/aws-config/src/default_provider/app_name.rs @@ -90,7 +90,7 @@ mod tests { use super::*; use crate::profile::profile_file::{ProfileFileKind, ProfileFiles}; use crate::provider_config::ProviderConfig; - use crate::test_case::{no_traffic_connector, InstantSleep}; + use crate::test_case::{no_traffic_client, InstantSleep}; use aws_types::os_shim_internal::{Env, Fs}; #[tokio::test] @@ -105,7 +105,7 @@ mod tests { &ProviderConfig::no_configuration() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()), + .with_http_client(no_traffic_client()), ) .app_name() .await; @@ -120,7 +120,7 @@ mod tests { let conf = crate::from_env() .sleep_impl(InstantSleep) .fs(fs) - .http_connector(no_traffic_connector()) + .http_client(no_traffic_client()) .profile_name("custom") .profile_files( ProfileFiles::builder() @@ -141,7 +141,7 @@ mod tests { &ProviderConfig::empty() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()), + .with_http_client(no_traffic_client()), ) .app_name() .await; @@ -158,7 +158,7 @@ mod tests { &ProviderConfig::empty() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()), + .with_http_client(no_traffic_client()), ) .app_name() .await; diff --git a/aws/rust-runtime/aws-config/src/default_provider/credentials.rs b/aws/rust-runtime/aws-config/src/default_provider/credentials.rs index a5e1fb0dc5d..82e3280dc1f 100644 --- a/aws/rust-runtime/aws-config/src/default_provider/credentials.rs +++ b/aws/rust-runtime/aws-config/src/default_provider/credentials.rs @@ -226,7 +226,7 @@ mod test { /// make_test!(live: test_name) /// ``` macro_rules! make_test { - ($name: ident $(#[$m:meta])*) => { + ($name:ident $(#[$m:meta])*) => { make_test!($name, execute, $(#[$m])*); }; (update: $name:ident) => { @@ -235,13 +235,13 @@ mod test { (live: $name:ident) => { make_test!($name, execute_from_live_traffic); }; - ($name: ident, $func: ident, $(#[$m:meta])*) => { + ($name:ident, $func:ident, $(#[$m:meta])*) => { make_test!($name, $func, std::convert::identity $(, #[$m])*); }; - ($name: ident, builder: $provider_config_builder: expr) => { + ($name:ident, builder: $provider_config_builder:expr) => { make_test!($name, execute, $provider_config_builder); }; - ($name: ident, $func: ident, $provider_config_builder: expr $(, #[$m:meta])*) => { + ($name:ident, $func:ident, $provider_config_builder:expr $(, #[$m:meta])*) => { $(#[$m])* #[tokio::test] async fn $name() { @@ -332,14 +332,14 @@ mod test { use crate::provider_config::ProviderConfig; use aws_credential_types::provider::error::CredentialsError; use aws_smithy_async::rt::sleep::TokioSleep; - use aws_smithy_client::erase::boxclone::BoxCloneService; - use aws_smithy_client::never::NeverConnected; + use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; + use aws_smithy_runtime::client::http::test_util::NeverTcpConnector; tokio::time::pause(); let conf = ProviderConfig::no_configuration() - .with_tcp_connector(BoxCloneService::new(NeverConnected::new())) + .with_http_client(HyperClientBuilder::new().build(NeverTcpConnector::new())) .with_time_source(StaticTimeSource::new(UNIX_EPOCH)) - .with_sleep(TokioSleep::new()); + .with_sleep_impl(TokioSleep::new()); let provider = DefaultCredentialsChain::builder() .configure(conf) .build() diff --git a/aws/rust-runtime/aws-config/src/ecs.rs b/aws/rust-runtime/aws-config/src/ecs.rs index f5ca10e54c1..6b4d6acf4ba 100644 --- a/aws/rust-runtime/aws-config/src/ecs.rs +++ b/aws/rust-runtime/aws-config/src/ecs.rs @@ -46,24 +46,22 @@ //! } //! ``` -use std::error::Error; -use std::fmt::{Display, Formatter}; -use std::io; -use std::net::IpAddr; - +use crate::http_credential_provider::HttpCredentialProvider; +use crate::provider_config::ProviderConfig; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; -use aws_smithy_client::erase::boxclone::BoxCloneService; use aws_smithy_http::endpoint::apply_endpoint; +use aws_smithy_runtime_api::box_error::BoxError; +use aws_smithy_runtime_api::client::dns::{DnsResolver, SharedDnsResolver}; +use aws_smithy_runtime_api::client::http::HttpConnectorSettings; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; -use http::uri::{InvalidUri, PathAndQuery, Scheme}; -use http::{HeaderValue, Uri}; -use tower::{Service, ServiceExt}; - -use crate::http_credential_provider::HttpCredentialProvider; -use crate::provider_config::ProviderConfig; -use aws_smithy_client::http_connector::ConnectorSettings; use aws_types::os_shim_internal::Env; use http::header::InvalidHeaderValue; +use http::uri::{InvalidUri, PathAndQuery, Scheme}; +use http::{HeaderValue, Uri}; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::net::IpAddr; use std::time::Duration; use tokio::sync::OnceCell; @@ -143,14 +141,14 @@ enum Provider { } impl Provider { - async fn uri(env: Env, dns: Option) -> Result { + async fn uri(env: Env, dns: Option) -> Result { let relative_uri = env.get(ENV_RELATIVE_URI).ok(); let full_uri = env.get(ENV_FULL_URI).ok(); if let Some(relative_uri) = relative_uri { Self::build_full_uri(relative_uri) } else if let Some(full_uri) = full_uri { - let mut dns = dns.or_else(tokio_dns); - validate_full_uri(&full_uri, dns.as_mut()) + let dns = dns.or_else(default_dns); + validate_full_uri(&full_uri, dns) .await .map_err(|err| EcsConfigurationError::InvalidFullUri { err, uri: full_uri }) } else { @@ -177,8 +175,8 @@ impl Provider { let http_provider = HttpCredentialProvider::builder() .configure(&provider_config) - .connector_settings( - ConnectorSettings::builder() + .http_connector_settings( + HttpConnectorSettings::builder() .connect_timeout(DEFAULT_CONNECT_TIMEOUT) .read_timeout(DEFAULT_READ_TIMEOUT) .build(), @@ -261,7 +259,7 @@ impl Error for EcsConfigurationError { #[derive(Default, Debug, Clone)] pub struct Builder { provider_config: Option, - dns: Option, + dns: Option, connect_timeout: Option, read_timeout: Option, } @@ -275,10 +273,10 @@ impl Builder { /// Override the DNS resolver used to validate URIs /// - /// URIs must refer to loopback addresses. The `DnsService` is used to retrieve IP addresses for - /// a given domain. - pub fn dns(mut self, dns: DnsService) -> Self { - self.dns = Some(dns); + /// URIs must refer to loopback addresses. The [`DnsResolver`](aws_smithy_runtime_api::client::dns::DnsResolver) + /// is used to retrieve IP addresses for a given domain. + pub fn dns(mut self, dns: impl IntoShared) -> Self { + self.dns = Some(dns.into_shared()); self } @@ -319,9 +317,9 @@ enum InvalidFullUriErrorKind { #[non_exhaustive] InvalidUri(InvalidUri), - /// No Dns service was provided + /// No Dns resolver was provided #[non_exhaustive] - NoDnsService, + NoDnsResolver, /// The URI did not specify a host #[non_exhaustive] @@ -332,7 +330,7 @@ enum InvalidFullUriErrorKind { NotLoopback, /// DNS lookup failed when attempting to resolve the host to an IP Address for validation. - DnsLookupFailed(io::Error), + DnsLookupFailed(BoxError), } /// Invalid Full URI @@ -358,7 +356,7 @@ impl Display for InvalidFullUriError { "failed to perform DNS lookup while validating URI" ) } - NoDnsService => write!(f, "no DNS service was provided. Enable `rt-tokio` or provide a `dns` service to the builder.") + NoDnsResolver => write!(f, "no DNS resolver was provided. Enable `rt-tokio` or provide a `dns` resolver to the builder.") } } } @@ -368,7 +366,7 @@ impl Error for InvalidFullUriError { use InvalidFullUriErrorKind::*; match &self.kind { InvalidUri(err) => Some(err), - DnsLookupFailed(err) => Some(err), + DnsLookupFailed(err) => Some(&**err as _), _ => None, } } @@ -380,9 +378,6 @@ impl From for InvalidFullUriError { } } -/// Dns resolver interface -pub type DnsService = BoxCloneService, io::Error>; - /// Validate that `uri` is valid to be used as a full provider URI /// Either: /// 1. The URL is uses `https` @@ -391,7 +386,7 @@ pub type DnsService = BoxCloneService, io::Error>; /// the credentials provider will return `CredentialsError::InvalidConfiguration` async fn validate_full_uri( uri: &str, - dns: Option<&mut DnsService>, + dns: Option, ) -> Result { let uri = uri .parse::() @@ -404,12 +399,11 @@ async fn validate_full_uri( let is_loopback = match host.parse::() { Ok(addr) => addr.is_loopback(), Err(_domain_name) => { - let dns = dns.ok_or(InvalidFullUriErrorKind::NoDnsService)?; - dns.ready().await.map_err(InvalidFullUriErrorKind::DnsLookupFailed)? - .call(host.to_owned()) - .await - .map_err(InvalidFullUriErrorKind::DnsLookupFailed)? - .iter() + let dns = dns.ok_or(InvalidFullUriErrorKind::NoDnsResolver)?; + dns.resolve_dns(host.to_owned()) + .await + .map_err(InvalidFullUriErrorKind::DnsLookupFailed)? + .iter() .all(|addr| { if !addr.is_loopback() { tracing::warn!( @@ -427,87 +421,52 @@ async fn validate_full_uri( } } +/// Default DNS resolver impl +/// +/// DNS resolution is required to validate that provided URIs point to the loopback interface #[cfg(any(not(feature = "rt-tokio"), target_family = "wasm"))] -fn tokio_dns() -> Option { +fn default_dns() -> Option { None } - -/// DNS resolver that uses tokio::spawn_blocking -/// -/// DNS resolution is required to validate that provided URIs point to the loopback interface #[cfg(all(feature = "rt-tokio", not(target_family = "wasm")))] -fn tokio_dns() -> Option { - use aws_smithy_client::erase::boxclone::BoxFuture; - use std::io::ErrorKind; - use std::net::ToSocketAddrs; - use std::task::{Context, Poll}; - - #[derive(Clone)] - struct TokioDns; - impl Service for TokioDns { - type Response = Vec; - type Error = io::Error; - type Future = BoxFuture; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: String) -> Self::Future { - Box::pin(async move { - let result = tokio::task::spawn_blocking(move || (req, 0).to_socket_addrs()).await; - match result { - Err(join_failure) => Err(io::Error::new(ErrorKind::Other, join_failure)), - Ok(Ok(dns_result)) => { - Ok(dns_result.into_iter().map(|addr| addr.ip()).collect()) - } - Ok(Err(dns_failure)) => Err(dns_failure), - } - }) - } - } - Some(BoxCloneService::new(TokioDns)) +fn default_dns() -> Option { + use aws_smithy_runtime::client::dns::TokioDnsResolver; + Some(TokioDnsResolver::new().into_shared()) } #[cfg(test)] mod test { - use aws_smithy_client::erase::boxclone::BoxCloneService; - use aws_smithy_client::never::NeverService; - use futures_util::FutureExt; - use http::Uri; - use serde::Deserialize; - use tracing_test::traced_test; - - use crate::ecs::{ - tokio_dns, validate_full_uri, Builder, EcsCredentialsProvider, InvalidFullUriError, - InvalidFullUriErrorKind, Provider, - }; + use super::*; use crate::provider_config::ProviderConfig; use crate::test_case::GenericTestResult; - use aws_credential_types::provider::ProvideCredentials; use aws_credential_types::Credentials; - use aws_types::os_shim_internal::Env; - + use aws_smithy_async::future::never::Never; use aws_smithy_async::rt::sleep::TokioSleep; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; + use aws_smithy_runtime_api::client::dns::DnsFuture; + use aws_smithy_runtime_api::shared::IntoShared; + use aws_types::os_shim_internal::Env; + use aws_types::sdk_config::SharedHttpClient; + use futures_util::FutureExt; use http::header::AUTHORIZATION; + use http::Uri; + use serde::Deserialize; use std::collections::HashMap; use std::error::Error; - use std::future::Ready; - use std::io; use std::net::IpAddr; - use std::task::{Context, Poll}; use std::time::{Duration, UNIX_EPOCH}; - use tower::Service; + use tracing_test::traced_test; - fn provider(env: Env, connector: DynConnector) -> EcsCredentialsProvider { + fn provider( + env: Env, + http_client: impl IntoShared, + ) -> EcsCredentialsProvider { let provider_config = ProviderConfig::empty() .with_env(env) - .with_http_connector(connector) - .with_sleep(TokioSleep::new()); + .with_http_client(http_client) + .with_sleep_impl(TokioSleep::new()); Builder::default().configure(&provider_config).build() } @@ -520,7 +479,7 @@ mod test { impl EcsUriTest { async fn check(&self) { let env = Env::from(self.env.clone()); - let uri = Provider::uri(env, Some(BoxCloneService::new(TestDns::default()))) + let uri = Provider::uri(env, Some(TestDns::default().into_shared())) .await .map(|uri| uri.to_string()); self.result.assert_matches(uri); @@ -546,8 +505,7 @@ mod test { #[test] fn validate_uri_https() { // over HTTPs, any URI is fine - let never = NeverService::new(); - let mut dns = Some(BoxCloneService::new(never)); + let dns = Some(NeverDns.into_shared()); assert_eq!( validate_full_uri("https://amazon.com", None) .now_or_never() @@ -557,7 +515,7 @@ mod test { ); // over HTTP, it will try to lookup assert!( - validate_full_uri("http://amazon.com", dns.as_mut()) + validate_full_uri("http://amazon.com", dns) .now_or_never() .is_none(), "DNS lookup should occur, but it will never return" @@ -571,7 +529,7 @@ mod test { matches!( no_dns_error, InvalidFullUriError { - kind: InvalidFullUriErrorKind::NoDnsService + kind: InvalidFullUriErrorKind::NoDnsResolver } ), "expected no dns service, got: {}", @@ -603,12 +561,14 @@ mod test { #[test] fn all_addrs_local() { - let svc = TestDns::with_fallback(vec![ - "127.0.0.1".parse().unwrap(), - "127.0.0.2".parse().unwrap(), - ]); - let mut svc = Some(BoxCloneService::new(svc)); - let resp = validate_full_uri("http://localhost:8888", svc.as_mut()) + let dns = Some( + TestDns::with_fallback(vec![ + "127.0.0.1".parse().unwrap(), + "127.0.0.2".parse().unwrap(), + ]) + .into_shared(), + ); + let resp = validate_full_uri("http://localhost:8888", dns) .now_or_never() .unwrap(); assert!(resp.is_ok(), "Should be valid: {:?}", resp); @@ -616,12 +576,14 @@ mod test { #[test] fn all_addrs_not_local() { - let svc = TestDns::with_fallback(vec![ - "127.0.0.1".parse().unwrap(), - "192.168.0.1".parse().unwrap(), - ]); - let mut svc = Some(BoxCloneService::new(svc)); - let resp = validate_full_uri("http://localhost:8888", svc.as_mut()) + let dns = Some( + TestDns::with_fallback(vec![ + "127.0.0.1".parse().unwrap(), + "192.168.0.1".parse().unwrap(), + ]) + .into_shared(), + ); + let resp = validate_full_uri("http://localhost:8888", dns) .now_or_never() .unwrap(); assert!( @@ -675,37 +637,43 @@ mod test { ("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials"), ("AWS_CONTAINER_AUTHORIZATION_TOKEN", "Basic password"), ]); - let connector = TestConnection::new(vec![( - creds_request("http://169.254.170.2/credentials", Some("Basic password")), - ok_creds_response(), - )]); - let provider = provider(env, DynConnector::new(connector.clone())); + let http_client = EventClient::new( + vec![ConnectionEvent::new( + creds_request("http://169.254.170.2/credentials", Some("Basic password")), + ok_creds_response(), + )], + TokioSleep::new(), + ); + let provider = provider(env, http_client.clone()); let creds = provider .provide_credentials() .await .expect("valid credentials"); assert_correct(creds); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn retry_5xx() { let env = Env::from_slice(&[("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials")]); - let connector = TestConnection::new(vec![ - ( - creds_request("http://169.254.170.2/credentials", None), - http::Response::builder() - .status(500) - .body(SdkBody::empty()) - .unwrap(), - ), - ( - creds_request("http://169.254.170.2/credentials", None), - ok_creds_response(), - ), - ]); + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + creds_request("http://169.254.170.2/credentials", None), + http::Response::builder() + .status(500) + .body(SdkBody::empty()) + .unwrap(), + ), + ConnectionEvent::new( + creds_request("http://169.254.170.2/credentials", None), + ok_creds_response(), + ), + ], + TokioSleep::new(), + ); tokio::time::pause(); - let provider = provider(env, DynConnector::new(connector.clone())); + let provider = provider(env, http_client.clone()); let creds = provider .provide_credentials() .await @@ -716,17 +684,20 @@ mod test { #[tokio::test] async fn load_valid_creds_no_auth() { let env = Env::from_slice(&[("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials")]); - let connector = TestConnection::new(vec![( - creds_request("http://169.254.170.2/credentials", None), - ok_creds_response(), - )]); - let provider = provider(env, DynConnector::new(connector.clone())); + let http_client = EventClient::new( + vec![ConnectionEvent::new( + creds_request("http://169.254.170.2/credentials", None), + ok_creds_response(), + )], + TokioSleep::new(), + ); + let provider = provider(env, http_client.clone()); let creds = provider .provide_credentials() .await .expect("valid credentials"); assert_correct(creds); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } // ignored by default because it relies on actual DNS resolution @@ -735,8 +706,12 @@ mod test { #[traced_test] #[ignore] async fn real_dns_lookup() { - let mut dns = Some(tokio_dns().expect("feature must be enabled")); - let err = validate_full_uri("http://www.amazon.com/creds", dns.as_mut()) + let dns = Some( + default_dns() + .expect("feature must be enabled") + .into_shared(), + ); + let err = validate_full_uri("http://www.amazon.com/creds", dns.clone()) .await .expect_err("not a loopback"); assert!( @@ -752,13 +727,13 @@ mod test { assert!(logs_contain( "Address does not resolve to the loopback interface" )); - validate_full_uri("http://localhost:8888/creds", dns.as_mut()) + validate_full_uri("http://localhost:8888/creds", dns) .await .expect("localhost is the loopback interface"); } - /// TestService which always returns the same IP addresses - #[derive(Clone)] + /// Always returns the same IP addresses + #[derive(Clone, Debug)] struct TestDns { addrs: HashMap>, fallback: Vec, @@ -789,17 +764,20 @@ mod test { } } - impl Service for TestDns { - type Response = Vec; - type Error = io::Error; - type Future = Ready>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) + impl DnsResolver for TestDns { + fn resolve_dns(&self, name: String) -> DnsFuture { + DnsFuture::ready(Ok(self.addrs.get(&name).unwrap_or(&self.fallback).clone())) } + } - fn call(&mut self, _req: String) -> Self::Future { - std::future::ready(Ok(self.addrs.get(&_req).unwrap_or(&self.fallback).clone())) + #[derive(Debug)] + struct NeverDns; + impl DnsResolver for NeverDns { + fn resolve_dns(&self, _name: String) -> DnsFuture { + DnsFuture::new(async { + Never::new().await; + unreachable!() + }) } } } diff --git a/aws/rust-runtime/aws-config/src/http_credential_provider.rs b/aws/rust-runtime/aws-config/src/http_credential_provider.rs index 87950ea17cf..ea5fce0603f 100644 --- a/aws/rust-runtime/aws-config/src/http_credential_provider.rs +++ b/aws/rust-runtime/aws-config/src/http_credential_provider.rs @@ -8,20 +8,17 @@ //! //! Future work will stabilize this interface and enable it to be used directly. -use crate::connector::expect_connector; use crate::json_credentials::{parse_json_credentials, JsonCredentials, RefreshableCredentials}; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::{self, error::CredentialsError}; use aws_credential_types::Credentials; -use aws_smithy_client::http_connector::ConnectorSettings; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::SdkError; -use aws_smithy_runtime::client::connectors::adapter::DynConnectorAdapter; use aws_smithy_runtime::client::orchestrator::operation::Operation; use aws_smithy_runtime::client::retries::classifier::{ HttpStatusCodeClassifier, SmithyErrorClassifier, }; -use aws_smithy_runtime_api::client::connectors::SharedHttpConnector; +use aws_smithy_runtime_api::client::http::HttpConnectorSettings; use aws_smithy_runtime_api::client::interceptors::context::{Error, InterceptorContext}; use aws_smithy_runtime_api::client::orchestrator::{ HttpResponse, OrchestratorError, SensitiveOutput, @@ -30,6 +27,7 @@ use aws_smithy_runtime_api::client::retries::{ClassifyRetry, RetryClassifiers, R use aws_smithy_runtime_api::client::runtime_plugin::StaticRuntimePlugin; use aws_smithy_types::config_bag::Layer; use aws_smithy_types::retry::{ErrorKind, RetryConfig}; +use aws_smithy_types::timeout::TimeoutConfig; use http::header::{ACCEPT, AUTHORIZATION}; use http::{HeaderValue, Response}; use std::time::Duration; @@ -65,7 +63,7 @@ impl HttpCredentialProvider { #[derive(Default)] pub(crate) struct Builder { provider_config: Option, - connector_settings: Option, + http_connector_settings: Option, } impl Builder { @@ -74,8 +72,11 @@ impl Builder { self } - pub(crate) fn connector_settings(mut self, connector_settings: ConnectorSettings) -> Self { - self.connector_settings = Some(connector_settings); + pub(crate) fn http_connector_settings( + mut self, + http_connector_settings: HttpConnectorSettings, + ) -> Self { + self.http_connector_settings = Some(http_connector_settings); self } @@ -86,16 +87,6 @@ impl Builder { path: impl Into, ) -> HttpCredentialProvider { let provider_config = self.provider_config.unwrap_or_default(); - let connector_settings = self.connector_settings.unwrap_or_else(|| { - ConnectorSettings::builder() - .connect_timeout(DEFAULT_CONNECT_TIMEOUT) - .read_timeout(DEFAULT_READ_TIMEOUT) - .build() - }); - let connector = expect_connector( - "The HTTP credentials provider", - provider_config.connector(&connector_settings), - ); // The following errors are retryable: // - Socket errors @@ -112,17 +103,23 @@ impl Builder { let mut builder = Operation::builder() .service_name("HttpCredentialProvider") .operation_name("LoadCredentials") - .http_connector(SharedHttpConnector::new(DynConnectorAdapter::new( - connector, - ))) .endpoint_url(endpoint) .no_auth() + .timeout_config( + TimeoutConfig::builder() + .connect_timeout(DEFAULT_CONNECT_TIMEOUT) + .read_timeout(DEFAULT_READ_TIMEOUT) + .build(), + ) .runtime_plugin(StaticRuntimePlugin::new().with_config({ let mut layer = Layer::new("SensitiveOutput"); layer.store_put(SensitiveOutput); layer.freeze() })); - if let Some(sleep_impl) = provider_config.sleep() { + if let Some(http_client) = provider_config.http_client() { + builder = builder.http_client(http_client); + } + if let Some(sleep_impl) = provider_config.sleep_impl() { builder = builder .standard_retry(&RetryConfig::standard()) .retry_classifiers(retry_classifiers) @@ -220,24 +217,22 @@ impl ClassifyRetry for HttpCredentialRetryClassifier { mod test { use super::*; use aws_credential_types::provider::error::CredentialsError; - use aws_smithy_client::test_connection::TestConnection; + use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_http::body::SdkBody; - use aws_smithy_runtime_api::client::orchestrator::HttpRequest; + use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use http::{Request, Response, Uri}; use std::time::SystemTime; - async fn provide_creds( - connector: TestConnection, - ) -> Result { - let provider_config = ProviderConfig::default().with_http_connector(connector.clone()); + async fn provide_creds(http_client: EventClient) -> Result { + let provider_config = ProviderConfig::default().with_http_client(http_client.clone()); let provider = HttpCredentialProvider::builder() .configure(&provider_config) .build("test", "http://localhost:1234/", "/some-creds"); provider.credentials(None).await } - fn successful_req_resp() -> (HttpRequest, HttpResponse) { - ( + fn successful_req_resp() -> ConnectionEvent { + ConnectionEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -258,8 +253,8 @@ mod test { #[tokio::test] async fn successful_response() { - let connector = TestConnection::new(vec![successful_req_resp()]); - let creds = provide_creds(connector.clone()).await.expect("success"); + let http_client = EventClient::new(vec![successful_req_resp()], TokioSleep::new()); + let creds = provide_creds(http_client.clone()).await.expect("success"); assert_eq!("MUA...", creds.access_key_id()); assert_eq!("/7PC5om....", creds.secret_access_key()); assert_eq!(Some("AQoDY....="), creds.session_token()); @@ -267,52 +262,59 @@ mod test { Some(SystemTime::UNIX_EPOCH + Duration::from_secs(1456380211)), creds.expiry() ); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn retry_nonparseable_response() { - let connector = TestConnection::new(vec![ - ( - Request::builder() - .uri(Uri::from_static("http://localhost:1234/some-creds")) - .body(SdkBody::empty()) - .unwrap(), - Response::builder() - .status(200) - .body(SdkBody::from(r#"not json"#)) - .unwrap(), - ), - successful_req_resp(), - ]); - let creds = provide_creds(connector.clone()).await.expect("success"); + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + Request::builder() + .uri(Uri::from_static("http://localhost:1234/some-creds")) + .body(SdkBody::empty()) + .unwrap(), + Response::builder() + .status(200) + .body(SdkBody::from(r#"not json"#)) + .unwrap(), + ), + successful_req_resp(), + ], + TokioSleep::new(), + ); + let creds = provide_creds(http_client.clone()).await.expect("success"); assert_eq!("MUA...", creds.access_key_id()); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn retry_error_code() { - let connector = TestConnection::new(vec![ - ( - Request::builder() - .uri(Uri::from_static("http://localhost:1234/some-creds")) - .body(SdkBody::empty()) - .unwrap(), - Response::builder() - .status(500) - .body(SdkBody::from(r#"it broke"#)) - .unwrap(), - ), - successful_req_resp(), - ]); - let creds = provide_creds(connector.clone()).await.expect("success"); + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + Request::builder() + .uri(Uri::from_static("http://localhost:1234/some-creds")) + .body(SdkBody::empty()) + .unwrap(), + Response::builder() + .status(500) + .body(SdkBody::from(r#"it broke"#)) + .unwrap(), + ), + successful_req_resp(), + ], + TokioSleep::new(), + ); + let creds = provide_creds(http_client.clone()).await.expect("success"); assert_eq!("MUA...", creds.access_key_id()); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn explicit_error_not_retriable() { - let connector = TestConnection::new(vec![( + let http_client = EventClient::new( + vec![ConnectionEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -323,14 +325,16 @@ mod test { r#"{ "Code": "Error", "Message": "There was a problem, it was your fault" }"#, )) .unwrap(), - )]); - let err = provide_creds(connector.clone()) + )], + TokioSleep::new(), + ); + let err = provide_creds(http_client.clone()) .await .expect_err("it should fail"); assert!( matches!(err, CredentialsError::ProviderError { .. }), "should be CredentialsError::ProviderError: {err}", ); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } } diff --git a/aws/rust-runtime/aws-config/src/imds/client.rs b/aws/rust-runtime/aws-config/src/imds/client.rs index ed76fc8660f..245c0201189 100644 --- a/aws/rust-runtime/aws-config/src/imds/client.rs +++ b/aws/rust-runtime/aws-config/src/imds/client.rs @@ -7,7 +7,6 @@ //! //! Client for direct access to IMDSv2. -use crate::connector::expect_connector; use crate::imds::client::error::{BuildError, ImdsError, InnerImdsError, InvalidEndpointMode}; use crate::imds::client::token::TokenRuntimePlugin; use crate::provider_config::ProviderConfig; @@ -16,27 +15,18 @@ use aws_http::user_agent::{ApiMetadata, AwsUserAgent}; use aws_runtime::user_agent::UserAgentInterceptor; use aws_smithy_async::rt::sleep::SharedAsyncSleep; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::erase::DynConnector; -use aws_smithy_client::http_connector::ConnectorSettings; -use aws_smithy_client::SdkError; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; -use aws_smithy_runtime::client::connectors::adapter::DynConnectorAdapter; +use aws_smithy_http::result::SdkError; use aws_smithy_runtime::client::orchestrator::operation::Operation; use aws_smithy_runtime::client::retries::strategy::StandardRetryStrategy; use aws_smithy_runtime_api::client::auth::AuthSchemeOptionResolverParams; -use aws_smithy_runtime_api::client::connectors::SharedHttpConnector; -use aws_smithy_runtime_api::client::endpoint::{ - EndpointResolver, EndpointResolverParams, SharedEndpointResolver, -}; +use aws_smithy_runtime_api::client::endpoint::{EndpointResolver, EndpointResolverParams}; use aws_smithy_runtime_api::client::interceptors::context::InterceptorContext; -use aws_smithy_runtime_api::client::interceptors::SharedInterceptor; use aws_smithy_runtime_api::client::orchestrator::{ Future, HttpResponse, OrchestratorError, SensitiveOutput, }; -use aws_smithy_runtime_api::client::retries::{ - ClassifyRetry, RetryClassifiers, RetryReason, SharedRetryStrategy, -}; +use aws_smithy_runtime_api::client::retries::{ClassifyRetry, RetryClassifiers, RetryReason}; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, SharedRuntimePlugin}; use aws_smithy_types::config_bag::{FrozenLayer, Layer}; @@ -44,6 +34,7 @@ use aws_smithy_types::endpoint::Endpoint; use aws_smithy_types::retry::{ErrorKind, RetryConfig}; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::os_shim_internal::Env; +use aws_types::sdk_config::SharedHttpClient; use http::Uri; use std::borrow::Cow; use std::error::Error as _; @@ -244,7 +235,7 @@ struct ImdsCommonRuntimePlugin { impl ImdsCommonRuntimePlugin { fn new( - connector: DynConnector, + http_client: Option, endpoint_resolver: ImdsEndpointResolver, retry_config: &RetryConfig, timeout_config: TimeoutConfig, @@ -261,17 +252,13 @@ impl ImdsCommonRuntimePlugin { Self { config: layer.freeze(), components: RuntimeComponentsBuilder::new("ImdsCommonRuntimePlugin") - .with_http_connector(Some(SharedHttpConnector::new(DynConnectorAdapter::new( - connector, - )))) - .with_endpoint_resolver(Some(SharedEndpointResolver::new(endpoint_resolver))) - .with_interceptor(SharedInterceptor::new(UserAgentInterceptor::new())) + .with_http_client(http_client) + .with_endpoint_resolver(Some(endpoint_resolver)) + .with_interceptor(UserAgentInterceptor::new()) .with_retry_classifiers(Some( RetryClassifiers::new().with_classifier(ImdsResponseRetryClassifier), )) - .with_retry_strategy(Some(SharedRetryStrategy::new(StandardRetryStrategy::new( - retry_config, - )))) + .with_retry_strategy(Some(StandardRetryStrategy::new(retry_config))) .with_time_source(Some(time_source)) .with_sleep_impl(sleep_impl), } @@ -427,11 +414,6 @@ impl Builder { .connect_timeout(self.connect_timeout.unwrap_or(DEFAULT_CONNECT_TIMEOUT)) .read_timeout(self.read_timeout.unwrap_or(DEFAULT_READ_TIMEOUT)) .build(); - let connector_settings = ConnectorSettings::from_timeout_config(&timeout_config); - let connector = expect_connector( - "The IMDS credentials provider", - config.connector(&connector_settings), - ); let endpoint_source = self .endpoint .unwrap_or_else(|| EndpointSource::Env(config.clone())); @@ -442,12 +424,12 @@ impl Builder { let retry_config = RetryConfig::standard() .with_max_attempts(self.max_attempts.unwrap_or(DEFAULT_ATTEMPTS)); let common_plugin = SharedRuntimePlugin::new(ImdsCommonRuntimePlugin::new( - connector, + config.http_client(), endpoint_resolver, &retry_config, timeout_config, config.time_source(), - config.sleep(), + config.sleep_impl(), )); let operation = Operation::builder() .service_name("imds") @@ -608,15 +590,18 @@ pub(crate) mod test { use crate::provider_config::ProviderConfig; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::test_util::instant_time_and_sleep; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::{capture_request, TestConnection}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; + use aws_smithy_runtime::client::http::test_util::{ + capture_request, ConnectionEvent, EventClient, + }; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; use aws_smithy_runtime_api::client::interceptors::context::{ Input, InterceptorContext, Output, }; - use aws_smithy_runtime_api::client::orchestrator::OrchestratorError; + use aws_smithy_runtime_api::client::orchestrator::{ + HttpRequest, HttpResponse, OrchestratorError, + }; use aws_smithy_runtime_api::client::retries::ClassifyRetry; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; @@ -647,7 +632,7 @@ pub(crate) mod test { const TOKEN_A: &str = "AQAEAFTNrA4eEGx0AQgJ1arIq_Cc-t4tWt3fB0Hd8RKhXlKc5ccvhg=="; const TOKEN_B: &str = "alternatetoken=="; - pub(crate) fn token_request(base: &str, ttl: u32) -> http::Request { + pub(crate) fn token_request(base: &str, ttl: u32) -> HttpRequest { http::Request::builder() .uri(format!("{}/latest/api/token", base)) .header("x-aws-ec2-metadata-token-ttl-seconds", ttl) @@ -656,15 +641,15 @@ pub(crate) mod test { .unwrap() } - pub(crate) fn token_response(ttl: u32, token: &'static str) -> http::Response<&'static str> { + pub(crate) fn token_response(ttl: u32, token: &'static str) -> HttpResponse { http::Response::builder() .status(200) .header("X-aws-ec2-metadata-token-ttl-seconds", ttl) - .body(token) + .body(SdkBody::from(token)) .unwrap() } - pub(crate) fn imds_request(path: &'static str, token: &str) -> http::Request { + pub(crate) fn imds_request(path: &'static str, token: &str) -> HttpRequest { http::Request::builder() .uri(Uri::from_static(path)) .method("GET") @@ -673,67 +658,71 @@ pub(crate) mod test { .unwrap() } - pub(crate) fn imds_response(body: &'static str) -> http::Response<&'static str> { - http::Response::builder().status(200).body(body).unwrap() + pub(crate) fn imds_response(body: &'static str) -> HttpResponse { + http::Response::builder() + .status(200) + .body(SdkBody::from(body)) + .unwrap() } - pub(crate) fn make_client(conn: &TestConnection) -> super::Client - where - SdkBody: From, - T: Send + 'static, - { + pub(crate) fn make_client(http_client: &EventClient) -> super::Client { tokio::time::pause(); super::Client::builder() .configure( &ProviderConfig::no_configuration() - .with_sleep(TokioSleep::new()) - .with_http_connector(DynConnector::new(conn.clone())), + .with_sleep_impl(TokioSleep::new()) + .with_http_client(http_client.clone()), ) .build() } + fn event_client(events: Vec) -> (Client, EventClient) { + let http_client = EventClient::new(events, TokioSleep::new()); + let client = make_client(&http_client); + (client, http_client) + } + #[tokio::test] async fn client_caches_token() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata2", TOKEN_A), imds_response("output2"), ), ]); - let client = make_client(&connection); // load once let metadata = client.get("/latest/metadata").await.expect("failed"); assert_eq!("test-imds-output", metadata.as_ref()); // load again: the cached token should be used let metadata = client.get("/latest/metadata2").await.expect("failed"); assert_eq!("output2", metadata.as_ref()); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn token_can_expire() { - let connection = TestConnection::new(vec![ - ( + let (_, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output1"#), ), - ( + ConnectionEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_B), ), - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_B), imds_response(r#"test-imds-output2"#), ), @@ -742,9 +731,9 @@ pub(crate) mod test { let client = super::Client::builder() .configure( &ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(connection.clone())) + .with_http_client(http_client.clone()) .with_time_source(time_source.clone()) - .with_sleep(sleep), + .with_sleep_impl(sleep), ) .endpoint_mode(EndpointMode::IpV6) .token_ttl(Duration::from_secs(600)) @@ -754,7 +743,7 @@ pub(crate) mod test { // now the cached credential has expired time_source.advance(Duration::from_secs(600)); let resp2 = client.get("/latest/metadata").await.expect("success"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); assert_eq!("test-imds-output1", resp1.as_ref()); assert_eq!("test-imds-output2", resp2.as_ref()); } @@ -762,27 +751,27 @@ pub(crate) mod test { /// Tokens are refreshed up to 120 seconds early to avoid using an expired token. #[tokio::test] async fn token_refresh_buffer() { - let connection = TestConnection::new(vec![ - ( + let (_, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), ), // t = 0 - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output1"#), ), // t = 400 (no refresh) - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output2"#), ), // t = 550 (within buffer) - ( + ConnectionEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_B), ), - ( + ConnectionEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_B), imds_response(r#"test-imds-output3"#), ), @@ -791,8 +780,8 @@ pub(crate) mod test { let client = super::Client::builder() .configure( &ProviderConfig::no_configuration() - .with_sleep(sleep) - .with_http_connector(DynConnector::new(connection.clone())) + .with_sleep_impl(sleep) + .with_http_client(http_client.clone()) .with_time_source(time_source.clone()), ) .endpoint_mode(EndpointMode::IpV6) @@ -805,7 +794,7 @@ pub(crate) mod test { let resp2 = client.get("/latest/metadata").await.expect("success"); time_source.advance(Duration::from_secs(150)); let resp3 = client.get("/latest/metadata").await.expect("success"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); assert_eq!("test-imds-output1", resp1.as_ref()); assert_eq!("test-imds-output2", resp2.as_ref()); assert_eq!("test-imds-output3", resp3.as_ref()); @@ -815,21 +804,23 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn retry_500() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), - http::Response::builder().status(500).body("").unwrap(), + http::Response::builder() + .status(500) + .body(SdkBody::empty()) + .unwrap(), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response("ok"), ), ]); - let client = make_client(&connection); assert_eq!( "ok", client @@ -838,11 +829,11 @@ pub(crate) mod test { .expect("success") .as_ref() ); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); // all requests should have a user agent header - for request in connection.requests().iter() { - assert!(request.actual.headers().get(USER_AGENT).is_some()); + for request in http_client.actual_requests() { + assert!(request.headers().get(USER_AGENT).is_some()); } } @@ -850,21 +841,23 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn retry_token_failure() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), - http::Response::builder().status(500).body("").unwrap(), + http::Response::builder() + .status(500) + .body(SdkBody::empty()) + .unwrap(), ), - ( + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response("ok"), ), ]); - let client = make_client(&connection); assert_eq!( "ok", client @@ -873,32 +866,34 @@ pub(crate) mod test { .expect("success") .as_ref() ); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// 401 error during metadata retrieval must be retried #[tokio::test] #[traced_test] async fn retry_metadata_401() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(0, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), - http::Response::builder().status(401).body("").unwrap(), + http::Response::builder() + .status(401) + .body(SdkBody::empty()) + .unwrap(), ), - ( + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_B), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_B), imds_response("ok"), ), ]); - let client = make_client(&connection); assert_eq!( "ok", client @@ -907,21 +902,23 @@ pub(crate) mod test { .expect("success") .as_ref() ); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// 403 responses from IMDS during token acquisition MUST NOT be retried #[tokio::test] #[traced_test] async fn no_403_retry() { - let connection = TestConnection::new(vec![( + let (client, http_client) = event_client(vec![ConnectionEvent::new( token_request("http://169.254.169.254", 21600), - http::Response::builder().status(403).body("").unwrap(), + http::Response::builder() + .status(403) + .body(SdkBody::empty()) + .unwrap(), )]); - let client = make_client(&connection); let err = client.get("/latest/metadata").await.expect_err("no token"); assert_full_error_contains!(err, "forbidden"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// Successful responses should classify as `RetryKind::Unnecessary` @@ -944,24 +941,23 @@ pub(crate) mod test { // since tokens are sent as headers, the tokens need to be valid header values #[tokio::test] async fn invalid_token() { - let connection = TestConnection::new(vec![( + let (client, http_client) = event_client(vec![ConnectionEvent::new( token_request("http://169.254.169.254", 21600), - token_response(21600, "replaced").map(|_| vec![1, 0]), + token_response(21600, "invalid\nheader\nvalue\0"), )]); - let client = make_client(&connection); let err = client.get("/latest/metadata").await.expect_err("no token"); assert_full_error_contains!(err, "invalid token"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn non_utf8_response() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = event_client(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A).map(SdkBody::from), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), http::Response::builder() .status(200) @@ -969,10 +965,9 @@ pub(crate) mod test { .unwrap(), ), ]); - let client = make_client(&connection); let err = client.get("/latest/metadata").await.expect_err("no token"); assert_full_error_contains!(err, "invalid UTF-8"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// Verify that the end-to-end real client has a 1-second connect timeout @@ -1042,12 +1037,12 @@ pub(crate) mod test { } async fn check(test_case: ImdsConfigTest) { - let (server, watcher) = capture_request(None); + let (http_client, watcher) = capture_request(None); let provider_config = ProviderConfig::no_configuration() - .with_sleep(TokioSleep::new()) + .with_sleep_impl(TokioSleep::new()) .with_env(Env::from(test_case.env)) .with_fs(Fs::from_map(test_case.fs)) - .with_http_connector(DynConnector::new(server)); + .with_http_client(http_client); let mut imds_client = Client::builder().configure(&provider_config); if let Some(endpoint_override) = test_case.endpoint_override { imds_client = imds_client.endpoint(endpoint_override.parse::().unwrap()); diff --git a/aws/rust-runtime/aws-config/src/imds/client/error.rs b/aws/rust-runtime/aws-config/src/imds/client/error.rs index 4d32aee0127..a97c3961c72 100644 --- a/aws/rust-runtime/aws-config/src/imds/client/error.rs +++ b/aws/rust-runtime/aws-config/src/imds/client/error.rs @@ -5,9 +5,9 @@ //! Error types for [`ImdsClient`](crate::imds::client::Client) -use aws_smithy_client::SdkError; use aws_smithy_http::body::SdkBody; use aws_smithy_http::endpoint::error::InvalidEndpointError; +use aws_smithy_http::result::SdkError; use aws_smithy_runtime_api::client::orchestrator::HttpResponse; use std::error::Error; use std::fmt; diff --git a/aws/rust-runtime/aws-config/src/imds/credentials.rs b/aws/rust-runtime/aws-config/src/imds/credentials.rs index 3bde8a4510f..2dd5693b1b2 100644 --- a/aws/rust-runtime/aws-config/src/imds/credentials.rs +++ b/aws/rust-runtime/aws-config/src/imds/credentials.rs @@ -288,9 +288,10 @@ mod test { }; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::ProvideCredentials; + use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::test_util::instant_time_and_sleep; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::TestConnection; + use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use std::time::{Duration, UNIX_EPOCH}; use tracing_test::traced_test; @@ -298,55 +299,55 @@ mod test { #[tokio::test] async fn profile_is_not_cached() { - let connection = TestConnection::new(vec![ - ( + let http_client = EventClient::new(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"different-profile"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/different-profile", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST2\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ]); + ], TokioSleep::new()); let client = ImdsCredentialsProvider::builder() - .imds_client(make_client(&connection)) + .imds_client(make_client(&http_client)) .build(); let creds1 = client.provide_credentials().await.expect("valid creds"); let creds2 = client.provide_credentials().await.expect("valid creds"); assert_eq!(creds1.access_key_id(), "ASIARTEST"); assert_eq!(creds2.access_key_id(), "ASIARTEST2"); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] #[traced_test] async fn credentials_not_stale_should_be_used_as_they_are() { - let connection = TestConnection::new(vec![ - ( + let http_client = EventClient::new(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ]); + ], TokioSleep::new()); // set to 2021-09-21T04:16:50Z that makes returned credentials' expiry (2021-09-21T04:16:53Z) // not stale @@ -354,8 +355,8 @@ mod test { let (time_source, sleep) = instant_time_and_sleep(time_of_request_to_fetch_credentials); let provider_config = ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(connection.clone())) - .with_sleep(sleep) + .with_http_client(http_client.clone()) + .with_sleep_impl(sleep) .with_time_source(time_source); let client = crate::imds::Client::builder() .configure(&provider_config) @@ -370,7 +371,7 @@ mod test { creds.expiry(), UNIX_EPOCH.checked_add(Duration::from_secs(1632197813)) ); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); // There should not be logs indicating credentials are extended for stability. assert!(!logs_contain(WARNING_FOR_EXTENDING_CREDENTIALS_EXPIRY)); @@ -378,28 +379,28 @@ mod test { #[tokio::test] #[traced_test] async fn expired_credentials_should_be_extended() { - let connection = TestConnection::new(vec![ - ( + let http_client = EventClient::new(vec![ + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), - ]); + ], TokioSleep::new()); // set to 2021-09-21T17:41:25Z that renders fetched credentials already expired (2021-09-21T04:16:53Z) let time_of_request_to_fetch_credentials = UNIX_EPOCH + Duration::from_secs(1632246085); let (time_source, sleep) = instant_time_and_sleep(time_of_request_to_fetch_credentials); let provider_config = ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(connection.clone())) - .with_sleep(sleep) + .with_http_client(http_client.clone()) + .with_sleep_impl(sleep) .with_time_source(time_source); let client = crate::imds::Client::builder() .configure(&provider_config) @@ -410,7 +411,7 @@ mod test { .build(); let creds = provider.provide_credentials().await.expect("valid creds"); assert!(creds.expiry().unwrap() > time_of_request_to_fetch_credentials); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); // We should inform customers that expired credentials are being used for stability. assert!(logs_contain(WARNING_FOR_EXTENDING_CREDENTIALS_EXPIRY)); @@ -486,36 +487,36 @@ mod test { #[tokio::test] async fn fallback_credentials_should_be_used_when_imds_returns_500_during_credentials_refresh() { - let connection = TestConnection::new(vec![ + let http_client = EventClient::new(vec![ // The next three request/response pairs will correspond to the first call to `provide_credentials`. // During the call, it populates last_retrieved_credentials. - ( + ConnectionEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/profile-name", TOKEN_A), imds_response("{\n \"Code\" : \"Success\",\n \"LastUpdated\" : \"2021-09-20T21:42:26Z\",\n \"Type\" : \"AWS-HMAC\",\n \"AccessKeyId\" : \"ASIARTEST\",\n \"SecretAccessKey\" : \"testsecret\",\n \"Token\" : \"testtoken\",\n \"Expiration\" : \"2021-09-21T04:16:53Z\"\n}"), ), // The following request/response pair corresponds to the second call to `provide_credentials`. // During the call, IMDS returns response code 500. - ( + ConnectionEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), - http::Response::builder().status(500).body("").unwrap(), + http::Response::builder().status(500).body(SdkBody::empty()).unwrap(), ), - ]); + ], TokioSleep::new()); let provider = ImdsCredentialsProvider::builder() - .imds_client(make_client(&connection)) + .imds_client(make_client(&http_client)) .build(); let creds1 = provider.provide_credentials().await.expect("valid creds"); assert_eq!(creds1.access_key_id(), "ASIARTEST"); // `creds1` should be returned as fallback credentials and assigned to `creds2` let creds2 = provider.provide_credentials().await.expect("valid creds"); assert_eq!(creds1, creds2); - connection.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } } diff --git a/aws/rust-runtime/aws-config/src/imds/region.rs b/aws/rust-runtime/aws-config/src/imds/region.rs index 072dc97a873..4f48448e99a 100644 --- a/aws/rust-runtime/aws-config/src/imds/region.rs +++ b/aws/rust-runtime/aws-config/src/imds/region.rs @@ -112,31 +112,33 @@ mod test { use crate::provider_config::ProviderConfig; use aws_sdk_sts::config::Region; use aws_smithy_async::rt::sleep::TokioSleep; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime::client::http::test_util::{ConnectionEvent, EventClient}; use tracing_test::traced_test; #[tokio::test] async fn load_region() { - let conn = TestConnection::new(vec![ - ( - token_request("http://169.254.169.254", 21600), - token_response(21600, "token"), - ), - ( - imds_request( - "http://169.254.169.254/latest/meta-data/placement/region", - "token", + let http_client = EventClient::new( + vec![ + ConnectionEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, "token"), + ), + ConnectionEvent::new( + imds_request( + "http://169.254.169.254/latest/meta-data/placement/region", + "token", + ), + imds_response("eu-west-1"), ), - imds_response("eu-west-1"), - ), - ]); + ], + TokioSleep::new(), + ); let provider = ImdsRegionProvider::builder() .configure( &ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(conn)) - .with_sleep(TokioSleep::new()), + .with_http_client(http_client) + .with_sleep_impl(TokioSleep::new()), ) .build(); assert_eq!( @@ -148,18 +150,21 @@ mod test { #[traced_test] #[tokio::test] async fn no_region_imds_disabled() { - let conn = TestConnection::new(vec![( - token_request("http://169.254.169.254", 21600), - http::Response::builder() - .status(403) - .body(SdkBody::empty()) - .unwrap(), - )]); + let http_client = EventClient::new( + vec![ConnectionEvent::new( + token_request("http://169.254.169.254", 21600), + http::Response::builder() + .status(403) + .body(SdkBody::empty()) + .unwrap(), + )], + TokioSleep::new(), + ); let provider = ImdsRegionProvider::builder() .configure( &ProviderConfig::no_configuration() - .with_http_connector(DynConnector::new(conn)) - .with_sleep(TokioSleep::new()), + .with_http_client(http_client) + .with_sleep_impl(TokioSleep::new()), ) .build(); assert_eq!(provider.region().await, None); diff --git a/aws/rust-runtime/aws-config/src/lib.rs b/aws/rust-runtime/aws-config/src/lib.rs index 18fc00e46aa..ebf80afdfbc 100644 --- a/aws/rust-runtime/aws-config/src/lib.rs +++ b/aws/rust-runtime/aws-config/src/lib.rs @@ -112,7 +112,6 @@ mod fs_util; mod http_credential_provider; mod json_credentials; -pub mod connector; pub mod credential_process; pub mod default_provider; pub mod ecs; @@ -150,28 +149,25 @@ pub async fn load_from_env() -> aws_types::SdkConfig { } mod loader { - use std::sync::Arc; - + use crate::default_provider::use_dual_stack::use_dual_stack_provider; + use crate::default_provider::use_fips::use_fips_provider; + use crate::default_provider::{app_name, credentials, region, retry_config, timeout_config}; + use crate::meta::region::ProvideRegion; + use crate::profile::profile_file::ProfileFiles; + use crate::provider_config::ProviderConfig; use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider}; - use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; - use aws_smithy_async::time::{SharedTimeSource, TimeSource}; - use aws_smithy_client::http_connector::HttpConnector; + use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; + use aws_smithy_async::time::SharedTimeSource; + use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::app_name::AppName; use aws_types::docs_for; use aws_types::os_shim_internal::{Env, Fs}; + use aws_types::sdk_config::SharedHttpClient; use aws_types::SdkConfig; - use crate::connector::default_connector; - use crate::default_provider::use_dual_stack::use_dual_stack_provider; - use crate::default_provider::use_fips::use_fips_provider; - use crate::default_provider::{app_name, credentials, region, retry_config, timeout_config}; - use crate::meta::region::ProvideRegion; - use crate::profile::profile_file::ProfileFiles; - use crate::provider_config::ProviderConfig; - #[derive(Default, Debug)] enum CredentialsProviderOption { /// No provider was set by the user. We can set up the default credentials provider chain. @@ -200,7 +196,7 @@ mod loader { sleep: Option, timeout_config: Option, provider_config: Option, - http_connector: Option, + http_client: Option, profile_name_override: Option, profile_files_override: Option, use_fips: Option, @@ -272,61 +268,50 @@ mod loader { /// Override the sleep implementation for this [`ConfigLoader`]. The sleep implementation /// is used to create timeout futures. - pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> Self { + pub fn sleep_impl(mut self, sleep: impl IntoShared) -> Self { // it's possible that we could wrapping an `Arc in an `Arc` and that's OK - self.sleep = Some(SharedAsyncSleep::new(sleep)); + self.sleep = Some(sleep.into_shared()); self } /// Set the time source used for tasks like signing requests - pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self { - self.time_source = Some(SharedTimeSource::new(time_source)); + pub fn time_source(mut self, time_source: impl IntoShared) -> Self { + self.time_source = Some(time_source.into_shared()); self } - /// Override the [`HttpConnector`] for this [`ConfigLoader`]. The connector will be used for - /// both AWS services and credential providers. When [`HttpConnector::ConnectorFn`] is used, - /// the connector will be lazily instantiated as needed based on the provided settings. + /// Override the [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) for this + /// [`ConfigLoader`]. The HTTP client will be used for both AWS services and credentials providers. /// - /// **Note**: In order to take advantage of late-configured timeout settings, you MUST use - /// [`HttpConnector::ConnectorFn`] - /// when configuring this connector. + /// If you wish to use a separate HTTP client for credentials providers when creating clients, + /// then override the HTTP client set with this function on the client-specific `Config`s. /// - /// If you wish to use a separate connector when creating clients, use the client-specific config. /// ## Examples + /// /// ```no_run /// # use aws_smithy_async::rt::sleep::SharedAsyncSleep; - /// use aws_smithy_client::http_connector::HttpConnector; /// #[cfg(feature = "client-hyper")] /// # async fn create_config() { /// use std::time::Duration; - /// use aws_smithy_client::{Client, hyper_ext}; - /// use aws_smithy_client::erase::DynConnector; - /// use aws_smithy_client::http_connector::ConnectorSettings; - /// - /// let connector_fn = |settings: &ConnectorSettings, sleep: Option| { - /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() - /// .with_webpki_roots() - /// // NOTE: setting `https_only()` will not allow this connector to work with IMDS. - /// .https_only() - /// .enable_http1() - /// .enable_http2() - /// .build(); - /// let mut smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings(settings.clone()); - /// smithy_connector.set_sleep_impl(sleep); - /// Some(DynConnector::new(smithy_connector.build(https_connector))) - /// }; - /// let connector = HttpConnector::ConnectorFn(std::sync::Arc::new(connector_fn)); + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; + /// + /// let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() + /// .with_webpki_roots() + /// // NOTE: setting `https_only()` will not allow this connector to work with IMDS. + /// .https_only() + /// .enable_http1() + /// .enable_http2() + /// .build(); + /// + /// let hyper_client = HyperClientBuilder::new().build(tls_connector); /// let sdk_config = aws_config::from_env() - /// .http_connector(connector) + /// .http_client(hyper_client) /// .load() /// .await; /// # } /// ``` - pub fn http_connector(mut self, http_connector: impl Into) -> Self { - self.http_connector = Some(http_connector.into()); + pub fn http_client(mut self, http_client: impl IntoShared) -> Self { + self.http_client = Some(http_client.into_shared()); self } @@ -559,10 +544,6 @@ mod loader { /// This means that if you provide a region provider that does not return a region, no region will /// be set in the resulting [`SdkConfig`](aws_types::SdkConfig) pub async fn load(self) -> SdkConfig { - let http_connector = self - .http_connector - .unwrap_or_else(|| HttpConnector::ConnectorFn(Arc::new(default_connector))); - let time_source = self.time_source.unwrap_or_default(); let sleep_impl = if self.sleep.is_some() { @@ -583,10 +564,13 @@ mod loader { let conf = self .provider_config .unwrap_or_else(|| { - ProviderConfig::init(time_source.clone(), sleep_impl.clone()) + let mut config = ProviderConfig::init(time_source.clone(), sleep_impl.clone()) .with_fs(self.fs.unwrap_or_default()) - .with_env(self.env.unwrap_or_default()) - .with_http_connector(http_connector.clone()) + .with_env(self.env.unwrap_or_default()); + if let Some(http_client) = self.http_client.clone() { + config = config.with_http_client(http_client); + } + config }) .with_profile_config(self.profile_files_override, self.profile_name_override); let region = if let Some(provider) = self.region { @@ -641,7 +625,7 @@ mod loader { Some(self.credentials_cache.unwrap_or_else(|| { let mut builder = CredentialsCache::lazy_builder().time_source(conf.time_source()); - builder.set_sleep(conf.sleep()); + builder.set_sleep_impl(conf.sleep_impl()); builder.into_credentials_cache() })) } else { @@ -664,9 +648,9 @@ mod loader { .region(region) .retry_config(retry_config) .timeout_config(timeout_config) - .time_source(time_source) - .http_connector(http_connector); + .time_source(time_source); + builder.set_http_client(self.http_client); builder.set_app_name(app_name); builder.set_credentials_cache(credentials_cache); builder.set_credentials_provider(credentials_provider); @@ -693,12 +677,13 @@ mod loader { #[cfg(test)] mod test { + use crate::profile::profile_file::{ProfileFileKind, ProfileFiles}; + use crate::test_case::{no_traffic_client, InstantSleep}; + use crate::{from_env, ConfigLoader}; use aws_credential_types::provider::ProvideCredentials; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::time::{StaticTimeSource, TimeSource}; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::never::NeverConnector; - use aws_smithy_client::test_connection::infallible_connection_fn; + use aws_smithy_runtime::client::http::test_util::{infallible_client_fn, NeverClient}; use aws_types::app_name::AppName; use aws_types::os_shim_internal::{Env, Fs}; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -706,10 +691,6 @@ mod loader { use std::time::{SystemTime, UNIX_EPOCH}; use tracing_test::traced_test; - use crate::profile::profile_file::{ProfileFileKind, ProfileFiles}; - use crate::test_case::{no_traffic_connector, InstantSleep}; - use crate::{from_env, ConfigLoader}; - #[tokio::test] #[traced_test] async fn provider_config_used() { @@ -725,7 +706,7 @@ mod loader { .sleep_impl(TokioSleep::new()) .env(env) .fs(fs) - .http_connector(DynConnector::new(NeverConnector::new())) + .http_client(NeverClient::new()) .profile_name("custom") .profile_files( ProfileFiles::builder() @@ -767,7 +748,7 @@ mod loader { fn base_conf() -> ConfigLoader { from_env() .sleep_impl(InstantSleep) - .http_connector(no_traffic_connector()) + .http_client(no_traffic_client()) } #[tokio::test] @@ -804,11 +785,11 @@ mod loader { async fn connector_is_shared() { let num_requests = Arc::new(AtomicUsize::new(0)); let movable = num_requests.clone(); - let conn = infallible_connection_fn(move |_req| { + let http_client = infallible_client_fn(move |_req| { movable.fetch_add(1, Ordering::Relaxed); http::Response::new("ok!") }); - let config = from_env().http_connector(conn.clone()).load().await; + let config = from_env().http_client(http_client.clone()).load().await; config .credentials_provider() .unwrap() @@ -831,7 +812,7 @@ mod loader { let config = from_env() .sleep_impl(InstantSleep) .time_source(StaticTimeSource::new(UNIX_EPOCH)) - .http_connector(no_traffic_connector()) + .http_client(no_traffic_client()) .load() .await; // assert that the innards contain the customized fields diff --git a/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs b/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs index 838007ad1e3..cf7978d2751 100644 --- a/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs +++ b/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs @@ -195,7 +195,7 @@ mod test { use crate::profile::credentials::exec::ProviderChain; use crate::profile::credentials::repr::{BaseProvider, ProfileChain}; use crate::provider_config::ProviderConfig; - use crate::test_case::no_traffic_connector; + use crate::test_case::no_traffic_client; use aws_credential_types::Credentials; use std::collections::HashMap; @@ -219,7 +219,7 @@ mod test { fn error_on_unknown_provider() { let factory = NamedProviderFactory::new(HashMap::new()); let chain = ProviderChain::from_repr( - &ProviderConfig::empty().with_http_connector(no_traffic_connector()), + &ProviderConfig::empty().with_http_client(no_traffic_client()), ProfileChain { base: BaseProvider::NamedSource("floozle"), chain: vec![], diff --git a/aws/rust-runtime/aws-config/src/profile/region.rs b/aws/rust-runtime/aws-config/src/profile/region.rs index 3cdcf8f7e4f..7b04ad95927 100644 --- a/aws/rust-runtime/aws-config/src/profile/region.rs +++ b/aws/rust-runtime/aws-config/src/profile/region.rs @@ -157,7 +157,7 @@ impl ProvideRegion for ProfileFileRegionProvider { mod test { use crate::profile::ProfileFileRegionProvider; use crate::provider_config::ProviderConfig; - use crate::test_case::no_traffic_connector; + use crate::test_case::no_traffic_client; use aws_sdk_sts::config::Region; use aws_types::os_shim_internal::{Env, Fs}; use futures_util::FutureExt; @@ -169,7 +169,7 @@ mod test { ProviderConfig::empty() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()) + .with_http_client(no_traffic_client()) } #[traced_test] @@ -244,7 +244,7 @@ role_arn = arn:aws:iam::123456789012:role/test let provider_config = ProviderConfig::empty() .with_fs(fs) .with_env(env) - .with_http_connector(no_traffic_connector()); + .with_http_client(no_traffic_client()); assert_eq!( Some(Region::new("us-east-1")), diff --git a/aws/rust-runtime/aws-config/src/provider_config.rs b/aws/rust-runtime/aws-config/src/provider_config.rs index f1caa75e511..da36ddb6282 100644 --- a/aws/rust-runtime/aws-config/src/provider_config.rs +++ b/aws/rust-runtime/aws-config/src/provider_config.rs @@ -5,27 +5,21 @@ //! Configuration Options for Credential Providers -use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; +use crate::profile; +use crate::profile::profile_file::ProfileFiles; +use crate::profile::{ProfileFileLoadError, ProfileSet}; +use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::erase::DynConnector; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; -use aws_types::{ - http_connector::{ConnectorSettings, HttpConnector}, - region::Region, -}; +use aws_types::region::Region; +use aws_types::sdk_config::SharedHttpClient; use std::borrow::Cow; - use std::fmt::{Debug, Formatter}; use std::sync::Arc; use tokio::sync::OnceCell; -use crate::connector::default_connector; -use crate::profile; - -use crate::profile::profile_file::ProfileFiles; -use crate::profile::{ProfileFileLoadError, ProfileSet}; - /// Configuration options for Credential Providers /// /// Most credential providers builders offer a `configure` method which applies general provider configuration @@ -39,8 +33,8 @@ pub struct ProviderConfig { env: Env, fs: Fs, time_source: SharedTimeSource, - connector: HttpConnector, - sleep: Option, + http_client: Option, + sleep_impl: Option, region: Option, /// An AWS profile created from `ProfileFiles` and a `profile_name` parsed_profile: Arc>>, @@ -55,26 +49,23 @@ impl Debug for ProviderConfig { f.debug_struct("ProviderConfig") .field("env", &self.env) .field("fs", &self.fs) - .field("sleep", &self.sleep) + .field("time_source", &self.time_source) + .field("http_client", &self.http_client) + .field("sleep_impl", &self.sleep_impl) .field("region", &self.region) + .field("profile_name_override", &self.profile_name_override) .finish() } } impl Default for ProviderConfig { fn default() -> Self { - let connector = HttpConnector::ConnectorFn(Arc::new( - |settings: &ConnectorSettings, sleep: Option| { - default_connector(settings, sleep) - }, - )); - Self { env: Env::default(), fs: Fs::default(), time_source: SharedTimeSource::default(), - connector, - sleep: default_async_sleep(), + http_client: None, + sleep_impl: default_async_sleep(), region: None, parsed_profile: Default::default(), profile_files: ProfileFiles::default(), @@ -101,8 +92,8 @@ impl ProviderConfig { env, fs, time_source: SharedTimeSource::new(StaticTimeSource::new(UNIX_EPOCH)), - connector: HttpConnector::Prebuilt(None), - sleep: None, + http_client: None, + sleep_impl: None, region: None, profile_name_override: None, } @@ -141,8 +132,8 @@ impl ProviderConfig { env: Env::default(), fs: Fs::default(), time_source: SharedTimeSource::default(), - connector: HttpConnector::Prebuilt(None), - sleep: None, + http_client: None, + sleep_impl: None, region: None, parsed_profile: Default::default(), profile_files: ProfileFiles::default(), @@ -151,15 +142,18 @@ impl ProviderConfig { } /// Initializer for ConfigBag to avoid possibly setting incorrect defaults. - pub(crate) fn init(time_source: SharedTimeSource, sleep: Option) -> Self { + pub(crate) fn init( + time_source: SharedTimeSource, + sleep_impl: Option, + ) -> Self { Self { parsed_profile: Default::default(), profile_files: ProfileFiles::default(), env: Env::default(), fs: Fs::default(), time_source, - connector: HttpConnector::Prebuilt(None), - sleep, + http_client: None, + sleep_impl, region: None, profile_name_override: None, } @@ -199,19 +193,13 @@ impl ProviderConfig { } #[allow(dead_code)] - pub(crate) fn default_connector(&self) -> Option { - self.connector - .connector(&Default::default(), self.sleep.clone()) - } - - #[allow(dead_code)] - pub(crate) fn connector(&self, settings: &ConnectorSettings) -> Option { - self.connector.connector(settings, self.sleep.clone()) + pub(crate) fn http_client(&self) -> Option { + self.http_client.clone() } #[allow(dead_code)] - pub(crate) fn sleep(&self) -> Option { - self.sleep.clone() + pub(crate) fn sleep_impl(&self) -> Option { + self.sleep_impl.clone() } #[allow(dead_code)] @@ -302,65 +290,25 @@ impl ProviderConfig { } /// Override the time source for this configuration - pub fn with_time_source( - self, - time_source: impl aws_smithy_async::time::TimeSource + 'static, - ) -> Self { - ProviderConfig { - time_source: SharedTimeSource::new(time_source), - ..self - } - } - - /// Override the HTTPS connector for this configuration - /// - /// **Note**: In order to take advantage of late-configured timeout settings, use [`HttpConnector::ConnectorFn`] - /// when configuring this connector. - pub fn with_http_connector(self, connector: impl Into) -> Self { + pub fn with_time_source(self, time_source: impl IntoShared) -> Self { ProviderConfig { - connector: connector.into(), + time_source: time_source.into_shared(), ..self } } - /// Override the TCP connector for this configuration - /// - /// This connector MUST provide an HTTPS encrypted connection. - /// - /// # Stability - /// This method may change to support HTTP configuration. - #[cfg(feature = "client-hyper")] - pub fn with_tcp_connector(self, connector: C) -> Self - where - C: Clone + Send + Sync + 'static, - C: tower::Service, - C::Response: hyper::client::connect::Connection - + tokio::io::AsyncRead - + tokio::io::AsyncWrite - + Send - + Unpin - + 'static, - C::Future: Unpin + Send + 'static, - C::Error: Into>, - { - let connector_fn = move |settings: &ConnectorSettings, sleep: Option| { - let mut builder = aws_smithy_client::hyper_ext::Adapter::builder() - .connector_settings(settings.clone()); - if let Some(sleep) = sleep { - builder = builder.sleep_impl(sleep); - }; - Some(DynConnector::new(builder.build(connector.clone()))) - }; + /// Override the HTTP client for this configuration + pub fn with_http_client(self, http_client: impl IntoShared) -> Self { ProviderConfig { - connector: HttpConnector::ConnectorFn(Arc::new(connector_fn)), + http_client: Some(http_client.into_shared()), ..self } } /// Override the sleep implementation for this configuration - pub fn with_sleep(self, sleep: impl AsyncSleep + 'static) -> Self { + pub fn with_sleep_impl(self, sleep_impl: impl IntoShared) -> Self { ProviderConfig { - sleep: Some(SharedAsyncSleep::new(sleep)), + sleep_impl: Some(sleep_impl.into_shared()), ..self } } diff --git a/aws/rust-runtime/aws-config/src/sso.rs b/aws/rust-runtime/aws-config/src/sso.rs index 592ed5b6bc6..8a14d710084 100644 --- a/aws/rust-runtime/aws-config/src/sso.rs +++ b/aws/rust-runtime/aws-config/src/sso.rs @@ -13,7 +13,6 @@ use crate::fs_util::{home_dir, Os}; use crate::json_credentials::{json_parse_loop, InvalidJsonCredentials}; use crate::provider_config::ProviderConfig; - use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; use aws_credential_types::Credentials; @@ -21,19 +20,16 @@ use aws_sdk_sso::types::RoleCredentials; use aws_sdk_sso::{config::Builder as SsoConfigBuilder, Client as SsoClient, Config as SsoConfig}; use aws_smithy_json::deserialize::Token; use aws_smithy_types::date_time::Format; +use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::DateTime; use aws_types::os_shim_internal::{Env, Fs}; use aws_types::region::Region; - +use ring::digest; use std::convert::TryInto; use std::error::Error; use std::fmt::{Display, Formatter}; use std::io; use std::path::PathBuf; - -use crate::connector::expect_connector; -use aws_smithy_types::retry::RetryConfig; -use ring::digest; use zeroize::Zeroizing; /// SSO Credentials Provider @@ -63,13 +59,9 @@ impl SsoCredentialsProvider { let fs = provider_config.fs(); let env = provider_config.env(); - let mut sso_config = SsoConfig::builder() - .http_connector(expect_connector( - "The SSO credentials provider", - provider_config.connector(&Default::default()), - )) - .retry_config(RetryConfig::standard()); - sso_config.set_sleep_impl(provider_config.sleep()); + let mut sso_config = SsoConfig::builder().retry_config(RetryConfig::standard()); + sso_config.set_http_client(provider_config.http_client()); + sso_config.set_sleep_impl(provider_config.sleep_impl()); SsoCredentialsProvider { fs, diff --git a/aws/rust-runtime/aws-config/src/sts.rs b/aws/rust-runtime/aws-config/src/sts.rs index 028409bfbef..323ae45e0d5 100644 --- a/aws/rust-runtime/aws-config/src/sts.rs +++ b/aws/rust-runtime/aws-config/src/sts.rs @@ -5,27 +5,22 @@ //! Credential provider augmentation through the AWS Security Token Service (STS). -pub(crate) mod util; +use aws_sdk_sts::config::Builder as StsConfigBuilder; +use aws_smithy_types::retry::RetryConfig; pub use assume_role::{AssumeRoleProvider, AssumeRoleProviderBuilder}; mod assume_role; - -use crate::connector::expect_connector; -use aws_sdk_sts::config::Builder as StsConfigBuilder; -use aws_smithy_types::retry::RetryConfig; +pub(crate) mod util; impl crate::provider_config::ProviderConfig { pub(crate) fn sts_client_config(&self) -> StsConfigBuilder { let mut builder = aws_sdk_sts::Config::builder() - .http_connector(expect_connector( - "The STS features of aws-config", - self.connector(&Default::default()), - )) .retry_config(RetryConfig::standard()) .region(self.region()) .time_source(self.time_source()); - builder.set_sleep_impl(self.sleep()); + builder.set_http_client(self.http_client()); + builder.set_sleep_impl(self.sleep_impl()); builder } } diff --git a/aws/rust-runtime/aws-config/src/sts/assume_role.rs b/aws/rust-runtime/aws-config/src/sts/assume_role.rs index 90cd91195f8..6cf1f0ae275 100644 --- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs +++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs @@ -5,7 +5,6 @@ //! Assume credentials for a role through the AWS Security Token Service (STS). -use crate::connector::expect_connector; use crate::provider_config::ProviderConfig; use aws_credential_types::cache::CredentialsCache; use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials}; @@ -13,10 +12,11 @@ use aws_sdk_sts::operation::assume_role::builders::AssumeRoleFluentBuilder; use aws_sdk_sts::operation::assume_role::AssumeRoleError; use aws_sdk_sts::types::PolicyDescriptorType; use aws_sdk_sts::Client as StsClient; -use aws_smithy_client::erase::DynConnector; use aws_smithy_http::result::SdkError; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::region::Region; +use aws_types::sdk_config::SharedHttpClient; use std::time::Duration; use tracing::Instrument; @@ -168,12 +168,14 @@ impl AssumeRoleProviderBuilder { self } - /// If the `rustls` or `nativetls` features are enabled, this field is optional and a default - /// backing connection will be provided. - pub fn connection(mut self, conn: impl aws_smithy_client::bounds::SmithyConnector) -> Self { + /// Set the HTTP client to use. + /// + /// If the `rustls` feature is enabled, this field is optional and a default + /// backing HTTP client will be provided. + pub fn http_client(mut self, http_client: impl IntoShared) -> Self { let conf = match self.conf { - Some(conf) => conf.with_http_connector(DynConnector::new(conn)), - None => ProviderConfig::default().with_http_connector(DynConnector::new(conn)), + Some(conf) => conf.with_http_client(http_client), + None => ProviderConfig::default().with_http_client(http_client), }; self.conf = Some(conf); self @@ -212,12 +214,9 @@ impl AssumeRoleProviderBuilder { .credentials_cache(credentials_cache) .credentials_provider(provider) .time_source(conf.time_source()) - .region(self.region.clone()) - .http_connector(expect_connector( - "The AssumeRole credentials provider", - conf.connector(&Default::default()), - )); - config.set_sleep_impl(conf.sleep()); + .region(self.region.clone()); + config.set_sleep_impl(conf.sleep_impl()); + config.set_http_client(conf.http_client()); let session_name = self.session_name.unwrap_or_else(|| { super::util::default_session_name("assume-role-provider", conf.time_source().now()) @@ -295,21 +294,22 @@ mod test { use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_async::test_util::instant_time_and_sleep; use aws_smithy_async::time::StaticTimeSource; - use aws_smithy_client::erase::DynConnector; - use aws_smithy_client::test_connection::{capture_request, TestConnection}; use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime::client::http::test_util::{ + capture_request, ConnectionEvent, EventClient, + }; use aws_types::region::Region; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn configures_session_length() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let provider_conf = ProviderConfig::empty() - .with_sleep(TokioSleep::new()) + .with_sleep_impl(TokioSleep::new()) .with_time_source(StaticTimeSource::new( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), )) - .with_http_connector(DynConnector::new(server)); + .with_http_client(http_client); let provider = AssumeRoleProvider::builder("myrole") .configure(&provider_conf) .region(Region::new("us-east-1")) @@ -325,25 +325,25 @@ mod test { #[tokio::test] async fn provider_does_not_cache_credentials_by_default() { - let conn = TestConnection::new(vec![ - (http::Request::new(SdkBody::from("request body")), + let http_client = EventClient::new(vec![ + ConnectionEvent::new(http::Request::new(SdkBody::from("request body")), http::Response::builder().status(200).body(SdkBody::from( "\n \n \n AROAR42TAWARILN3MNKUT:assume-role-from-profile-1632246085998\n arn:aws:sts::130633740322:assumed-role/assume-provider-test/assume-role-from-profile-1632246085998\n \n \n ASIARCORRECT\n secretkeycorrect\n tokencorrect\n 2009-02-13T23:31:30Z\n \n \n \n d9d47248-fd55-4686-ad7c-0fb7cd1cddd7\n \n\n" )).unwrap()), - (http::Request::new(SdkBody::from("request body")), + ConnectionEvent::new(http::Request::new(SdkBody::from("request body")), http::Response::builder().status(200).body(SdkBody::from( "\n \n \n AROAR42TAWARILN3MNKUT:assume-role-from-profile-1632246085998\n arn:aws:sts::130633740322:assumed-role/assume-provider-test/assume-role-from-profile-1632246085998\n \n \n ASIARCORRECT\n TESTSECRET\n tokencorrect\n 2009-02-13T23:33:30Z\n \n \n \n c2e971c2-702d-4124-9b1f-1670febbea18\n \n\n" )).unwrap()), - ]); + ], TokioSleep::new()); let (testing_time_source, sleep) = instant_time_and_sleep( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), // 1234567890 since UNIX_EPOCH is 2009-02-13T23:31:30Z ); let provider_conf = ProviderConfig::empty() - .with_sleep(sleep) + .with_sleep_impl(sleep) .with_time_source(testing_time_source.clone()) - .with_http_connector(DynConnector::new(conn)); + .with_http_client(http_client); let credentials_list = std::sync::Arc::new(std::sync::Mutex::new(vec![ Credentials::new( "test", diff --git a/aws/rust-runtime/aws-config/src/test_case.rs b/aws/rust-runtime/aws-config/src/test_case.rs index 14859d57ef2..630511ffa7a 100644 --- a/aws/rust-runtime/aws-config/src/test_case.rs +++ b/aws/rust-runtime/aws-config/src/test_case.rs @@ -4,17 +4,16 @@ */ use crate::provider_config::ProviderConfig; - use aws_credential_types::provider::{self, ProvideCredentials}; use aws_smithy_async::rt::sleep::{AsyncSleep, Sleep, TokioSleep}; -use aws_smithy_client::dvr::{NetworkTraffic, RecordingConnection, ReplayingConnection}; -use aws_smithy_client::erase::DynConnector; +use aws_smithy_runtime::client::http::test_util::dvr::{ + NetworkTraffic, RecordingClient, ReplayingClient, +}; +use aws_smithy_runtime_api::shared::IntoShared; +use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; - +use aws_types::sdk_config::SharedHttpClient; use serde::Deserialize; - -use crate::connector::default_connector; -use aws_smithy_types::error::display::DisplayErrorContext; use std::collections::HashMap; use std::env; use std::error::Error; @@ -68,18 +67,18 @@ impl From for Credentials { /// A credentials test environment is a directory containing: /// - an `fs` directory. This is loaded into the test as if it was mounted at `/` /// - an `env.json` file containing environment variables -/// - an `http-traffic.json` file containing an http traffic log from [`dvr`](aws_smithy_client::dvr) +/// - an `http-traffic.json` file containing an http traffic log from [`dvr`](aws_smithy_runtime::client::http::test_utils::dvr) /// - a `test-case.json` file defining the expected output of the test pub(crate) struct TestEnvironment { metadata: Metadata, base_dir: PathBuf, - connector: ReplayingConnection, + http_client: ReplayingClient, provider_config: ProviderConfig, } /// Connector which expects no traffic -pub(crate) fn no_traffic_connector() -> DynConnector { - DynConnector::new(ReplayingConnection::new(vec![])) +pub(crate) fn no_traffic_client() -> SharedHttpClient { + ReplayingClient::new(Vec::new()).into_shared() } #[derive(Debug)] @@ -228,18 +227,18 @@ impl TestEnvironment { &std::fs::read_to_string(dir.join("test-case.json")) .map_err(|e| format!("failed to load test case: {}", e))?, )?; - let connector = ReplayingConnection::new(network_traffic.events().clone()); + let http_client = ReplayingClient::new(network_traffic.events().clone()); let provider_config = ProviderConfig::empty() .with_fs(fs.clone()) .with_env(env.clone()) - .with_http_connector(DynConnector::new(connector.clone())) - .with_sleep(TokioSleep::new()) + .with_http_client(http_client.clone()) + .with_sleep_impl(TokioSleep::new()) .load_default_region() .await; Ok(TestEnvironment { base_dir: dir.into(), metadata, - connector, + http_client, provider_config, }) } @@ -257,6 +256,7 @@ impl TestEnvironment { } #[allow(unused)] + #[cfg(all(feature = "client-hyper", feature = "rustls"))] /// Record a test case from live (remote) HTTPS traffic /// /// The `default_connector()` from the crate will be used @@ -268,18 +268,21 @@ impl TestEnvironment { P: ProvideCredentials, { // swap out the connector generated from `http-traffic.json` for a real connector: - let live_connector = - default_connector(&Default::default(), self.provider_config.sleep()).unwrap(); - let live_connector = RecordingConnection::new(live_connector); + let live_connector = aws_smithy_runtime::client::http::hyper_014::default_connector( + &Default::default(), + self.provider_config.sleep_impl(), + ) + .expect("feature gate on this function makes this always return Some"); + let live_client = RecordingClient::new(live_connector); let config = self .provider_config .clone() - .with_http_connector(DynConnector::new(live_connector.clone())); + .with_http_client(live_client.clone()); let provider = make_provider(config).await; let result = provider.provide_credentials().await; std::fs::write( self.base_dir.join("http-traffic-recorded.json"), - serde_json::to_string(&live_connector.network_traffic()).unwrap(), + serde_json::to_string(&live_client.network_traffic()).unwrap(), ) .unwrap(); self.check_results(result); @@ -295,16 +298,16 @@ impl TestEnvironment { F: Future, P: ProvideCredentials, { - let recording_connector = RecordingConnection::new(self.connector.clone()); + let recording_client = RecordingClient::new(self.http_client.clone()); let config = self .provider_config .clone() - .with_http_connector(DynConnector::new(recording_connector.clone())); + .with_http_client(recording_client.clone()); let provider = make_provider(config).await; let result = provider.provide_credentials().await; std::fs::write( self.base_dir.join("http-traffic-recorded.json"), - serde_json::to_string(&recording_connector.network_traffic()).unwrap(), + serde_json::to_string(&recording_client.network_traffic()).unwrap(), ) .unwrap(); self.check_results(result); @@ -341,7 +344,7 @@ impl TestEnvironment { self.check_results(result); // todo: validate bodies match self - .connector + .http_client .clone() .validate( &["CONTENT-TYPE", "x-aws-ec2-metadata-token"], diff --git a/aws/rust-runtime/aws-config/src/web_identity_token.rs b/aws/rust-runtime/aws-config/src/web_identity_token.rs index 7ea55fdf26c..b380c64ccf3 100644 --- a/aws/rust-runtime/aws-config/src/web_identity_token.rs +++ b/aws/rust-runtime/aws-config/src/web_identity_token.rs @@ -241,7 +241,7 @@ async fn load_credentials( #[cfg(test)] mod test { use crate::provider_config::ProviderConfig; - use crate::test_case::no_traffic_connector; + use crate::test_case::no_traffic_client; use crate::web_identity_token::{ Builder, ENV_VAR_ROLE_ARN, ENV_VAR_SESSION_NAME, ENV_VAR_TOKEN_FILE, }; @@ -256,9 +256,9 @@ mod test { async fn unloaded_provider() { // empty environment let conf = ProviderConfig::empty() - .with_sleep(TokioSleep::new()) + .with_sleep_impl(TokioSleep::new()) .with_env(Env::from_slice(&[])) - .with_http_connector(no_traffic_connector()) + .with_http_client(no_traffic_client()) .with_region(Some(Region::from_static("us-east-1"))); let provider = Builder::default().configure(&conf).build(); @@ -279,10 +279,10 @@ mod test { let provider = Builder::default() .configure( &ProviderConfig::empty() - .with_sleep(TokioSleep::new()) + .with_sleep_impl(TokioSleep::new()) .with_region(region) .with_env(env) - .with_http_connector(no_traffic_connector()), + .with_http_client(no_traffic_client()), ) .build(); let err = provider @@ -311,8 +311,8 @@ mod test { let provider = Builder::default() .configure( &ProviderConfig::empty() - .with_sleep(TokioSleep::new()) - .with_http_connector(no_traffic_connector()) + .with_sleep_impl(TokioSleep::new()) + .with_http_client(no_traffic_client()) .with_region(Some(Region::new("us-east-1"))) .with_env(env) .with_fs(fs), diff --git a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs index 6169c2b930b..8d09a13a8ef 100644 --- a/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs +++ b/aws/rust-runtime/aws-credential-types/src/cache/lazy_caching.rs @@ -143,6 +143,7 @@ mod builder { LazyCredentialsCache, DEFAULT_BUFFER_TIME, DEFAULT_BUFFER_TIME_JITTER_FRACTION, DEFAULT_CREDENTIAL_EXPIRATION, DEFAULT_LOAD_TIMEOUT, }; + use aws_smithy_runtime_api::shared::IntoShared; /// Builder for constructing a `LazyCredentialsCache`. /// @@ -158,7 +159,7 @@ mod builder { /// `build` to create a `LazyCredentialsCache`. #[derive(Clone, Debug, Default)] pub struct Builder { - sleep: Option, + sleep_impl: Option, time_source: Option, load_timeout: Option, buffer_time: Option, @@ -177,8 +178,8 @@ mod builder { /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). - pub fn sleep(mut self, sleep: SharedAsyncSleep) -> Self { - self.set_sleep(Some(sleep)); + pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { + self.set_sleep_impl(Some(sleep_impl.into_shared())); self } @@ -187,14 +188,14 @@ mod builder { /// This enables use of the `LazyCredentialsCache` with other async runtimes. /// If using Tokio as the async runtime, this should be set to an instance of /// [`TokioSleep`](aws_smithy_async::rt::sleep::TokioSleep). - pub fn set_sleep(&mut self, sleep: Option) -> &mut Self { - self.sleep = sleep; + pub fn set_sleep_impl(&mut self, sleep_impl: Option) -> &mut Self { + self.sleep_impl = sleep_impl; self } #[doc(hidden)] // because they only exist for tests - pub fn time_source(mut self, time_source: SharedTimeSource) -> Self { - self.set_time_source(Some(time_source)); + pub fn time_source(mut self, time_source: impl IntoShared) -> Self { + self.set_time_source(Some(time_source.into_shared())); self } @@ -326,7 +327,7 @@ mod builder { ); LazyCredentialsCache::new( self.time_source.unwrap_or_default(), - self.sleep.unwrap_or_else(|| { + self.sleep_impl.unwrap_or_else(|| { default_async_sleep().expect("no default sleep implementation available") }), provider, diff --git a/aws/rust-runtime/aws-types/Cargo.toml b/aws/rust-runtime/aws-types/Cargo.toml index eb0b527f427..2570ccff73e 100644 --- a/aws/rust-runtime/aws-types/Cargo.toml +++ b/aws/rust-runtime/aws-types/Cargo.toml @@ -9,13 +9,14 @@ repository = "https://github.com/awslabs/smithy-rs" [features] # This feature is to be used only for doc comments -examples = ["dep:hyper-rustls", "aws-smithy-client/client-hyper", "aws-smithy-client/rustls"] +examples = ["dep:hyper-rustls", "aws-smithy-runtime/client", "aws-smithy-runtime/connector-hyper-0-14-x", "aws-smithy-runtime/tls-rustls"] [dependencies] aws-credential-types = { path = "../aws-credential-types" } aws-smithy-async = { path = "../../../rust-runtime/aws-smithy-async" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } -aws-smithy-client = { path = "../../../rust-runtime/aws-smithy-client" } +aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", optional = true } +aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } tracing = "0.1" http = "0.2.6" diff --git a/aws/rust-runtime/aws-types/src/lib.rs b/aws/rust-runtime/aws-types/src/lib.rs index bf54671e150..e6a0a69dcdb 100644 --- a/aws/rust-runtime/aws-types/src/lib.rs +++ b/aws/rust-runtime/aws-types/src/lib.rs @@ -21,8 +21,6 @@ pub mod endpoint_config; pub mod os_shim_internal; pub mod region; pub mod sdk_config; - -pub use aws_smithy_client::http_connector; pub use sdk_config::SdkConfig; use aws_smithy_types::config_bag::{Storable, StoreReplace}; diff --git a/aws/rust-runtime/aws-types/src/sdk_config.rs b/aws/rust-runtime/aws-types/src/sdk_config.rs index 08e49469ba2..bc9d3c13a4d 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -9,18 +9,19 @@ //! //! This module contains an shared configuration representation that is agnostic from a specific service. -use aws_credential_types::cache::CredentialsCache; -use aws_credential_types::provider::SharedCredentialsProvider; -use aws_smithy_async::rt::sleep::SharedAsyncSleep; -use aws_smithy_async::time::{SharedTimeSource, TimeSource}; -use aws_smithy_client::http_connector::HttpConnector; -use aws_smithy_types::retry::RetryConfig; -use aws_smithy_types::timeout::TimeoutConfig; - use crate::app_name::AppName; use crate::docs_for; use crate::region::Region; +pub use aws_credential_types::cache::CredentialsCache; +pub use aws_credential_types::provider::SharedCredentialsProvider; +pub use aws_smithy_async::rt::sleep::SharedAsyncSleep; +pub use aws_smithy_async::time::{SharedTimeSource, TimeSource}; +pub use aws_smithy_runtime_api::client::http::SharedHttpClient; +use aws_smithy_runtime_api::shared::IntoShared; +pub use aws_smithy_types::retry::RetryConfig; +pub use aws_smithy_types::timeout::TimeoutConfig; + #[doc(hidden)] /// Unified docstrings to keep crates in sync. Not intended for public use pub mod unified_docs { @@ -56,7 +57,7 @@ pub struct SdkConfig { sleep_impl: Option, time_source: Option, timeout_config: Option, - http_connector: Option, + http_client: Option, use_fips: Option, use_dual_stack: Option, } @@ -77,7 +78,7 @@ pub struct Builder { sleep_impl: Option, time_source: Option, timeout_config: Option, - http_connector: Option, + http_client: Option, use_fips: Option, use_dual_stack: Option, } @@ -254,8 +255,8 @@ impl Builder { /// let sleep_impl = SharedAsyncSleep::new(ForeverSleep); /// let config = SdkConfig::builder().sleep_impl(sleep_impl).build(); /// ``` - pub fn sleep_impl(mut self, sleep_impl: SharedAsyncSleep) -> Self { - self.set_sleep_impl(Some(sleep_impl)); + pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { + self.set_sleep_impl(Some(sleep_impl.into_shared())); self } @@ -399,81 +400,76 @@ impl Builder { self } - /// Sets the HTTP connector to use when making requests. + /// Sets the HTTP client to use when making requests. /// /// ## Examples /// ```no_run /// # #[cfg(feature = "examples")] /// # fn example() { + /// use aws_types::sdk_config::{SdkConfig, TimeoutConfig}; + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; /// use std::time::Duration; - /// use aws_smithy_client::{Client, hyper_ext}; - /// use aws_smithy_client::erase::DynConnector; - /// use aws_smithy_client::http_connector::ConnectorSettings; - /// use aws_types::SdkConfig; /// - /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + /// // Create a connector that will be used to establish TLS connections + /// let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() /// .with_webpki_roots() /// .https_only() /// .enable_http1() /// .enable_http2() /// .build(); - /// let smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings( - /// ConnectorSettings::builder() + /// // Create a HTTP client that uses the TLS connector. This client is + /// // responsible for creating and caching a HttpConnector when given HttpConnectorSettings. + /// // This hyper client will create HttpConnectors backed by hyper and the tls_connector. + /// let http_client = HyperClientBuilder::new().build(tls_connector); + /// let sdk_config = SdkConfig::builder() + /// .http_client(http_client) + /// // Connect/read timeouts are passed to the HTTP client when servicing a request + /// .timeout_config( + /// TimeoutConfig::builder() /// .connect_timeout(Duration::from_secs(5)) /// .build() /// ) - /// .build(https_connector); - /// let sdk_config = SdkConfig::builder() - /// .http_connector(smithy_connector) /// .build(); /// # } /// ``` - pub fn http_connector(mut self, http_connector: impl Into) -> Self { - self.set_http_connector(Some(http_connector)); + pub fn http_client(mut self, http_client: impl IntoShared) -> Self { + self.set_http_client(Some(http_client.into_shared())); self } - /// Sets the HTTP connector to use when making requests. + /// Sets the HTTP client to use when making requests. /// /// ## Examples /// ```no_run /// # #[cfg(feature = "examples")] /// # fn example() { + /// use aws_types::sdk_config::{Builder, SdkConfig, TimeoutConfig}; + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; /// use std::time::Duration; - /// use aws_smithy_client::hyper_ext; - /// use aws_smithy_client::http_connector::ConnectorSettings; - /// use aws_types::sdk_config::{Builder, SdkConfig}; /// - /// fn override_http_connector(builder: &mut Builder) { - /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + /// fn override_http_client(builder: &mut Builder) { + /// // Create a connector that will be used to establish TLS connections + /// let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() /// .with_webpki_roots() /// .https_only() /// .enable_http1() /// .enable_http2() /// .build(); - /// let smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings( - /// ConnectorSettings::builder() - /// .connect_timeout(Duration::from_secs(5)) - /// .build() - /// ) - /// .build(https_connector); - /// builder.set_http_connector(Some(smithy_connector)); + /// // Create a HTTP client that uses the TLS connector. This client is + /// // responsible for creating and caching a HttpConnector when given HttpConnectorSettings. + /// // This hyper client will create HttpConnectors backed by hyper and the tls_connector. + /// let http_client = HyperClientBuilder::new().build(tls_connector); + /// + /// builder.set_http_client(Some(http_client)); /// } /// /// let mut builder = SdkConfig::builder(); - /// override_http_connector(&mut builder); + /// override_http_client(&mut builder); /// let config = builder.build(); /// # } /// ``` - pub fn set_http_connector( - &mut self, - http_connector: Option>, - ) -> &mut Self { - self.http_connector = http_connector.map(|inner| inner.into()); + pub fn set_http_client(&mut self, http_client: Option) -> &mut Self { + self.http_client = http_client; self } @@ -524,7 +520,7 @@ impl Builder { retry_config: self.retry_config, sleep_impl: self.sleep_impl, timeout_config: self.timeout_config, - http_connector: self.http_connector, + http_client: self.http_client, use_fips: self.use_fips, use_dual_stack: self.use_dual_stack, time_source: self.time_source, @@ -579,9 +575,9 @@ impl SdkConfig { self.app_name.as_ref() } - /// Configured HTTP Connector - pub fn http_connector(&self) -> Option<&HttpConnector> { - self.http_connector.as_ref() + /// Configured HTTP client + pub fn http_client(&self) -> Option { + self.http_client.clone() } /// Use FIPS endpoints diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt index cd8fe1bef1d..878c4a851f1 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCustomizableOperationDecorator.kt @@ -26,7 +26,6 @@ class CustomizableOperationTestHelpers(runtimeConfig: RuntimeConfig) : "InterceptorContext" to RuntimeType.interceptorContext(runtimeConfig), "RuntimeComponentsBuilder" to RuntimeType.runtimeComponentsBuilder(runtimeConfig), "SharedInterceptor" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::interceptors::SharedInterceptor"), - "SharedTimeSource" to CargoDependency.smithyAsync(runtimeConfig).toType().resolve("time::SharedTimeSource"), "StaticRuntimePlugin" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::runtime_plugin::StaticRuntimePlugin"), "StaticTimeSource" to CargoDependency.smithyAsync(runtimeConfig).toType().resolve("time::StaticTimeSource"), "TestParamsSetterInterceptor" to testParamsSetterInterceptor(), @@ -94,7 +93,7 @@ class CustomizableOperationTestHelpers(runtimeConfig: RuntimeConfig) : #{StaticRuntimePlugin}::new() .with_runtime_components( #{RuntimeComponentsBuilder}::new("request_time_for_tests") - .with_time_source(Some(#{SharedTimeSource}::new(#{StaticTimeSource}::new(request_time)))) + .with_time_source(Some(#{StaticTimeSource}::new(request_time))) ) ) } diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt index 7b69c5c72af..e3fdf5dd3b0 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt @@ -66,8 +66,7 @@ class AwsFluentClientDecorator : ClientCodegenDecorator { rustCrate.withModule(ClientRustModule.client) { AwsFluentClientExtensions(codegenContext, types).render(this) } - val awsSmithyClient = "aws-smithy-client" - rustCrate.mergeFeature(Feature("rustls", default = true, listOf("$awsSmithyClient/rustls"))) + rustCrate.mergeFeature(Feature("rustls", default = true, listOf("aws-smithy-runtime/tls-rustls"))) } override fun libRsCustomizations( @@ -99,7 +98,7 @@ class AwsFluentClientDecorator : ClientCodegenDecorator { let mut ${params.configBuilderName} = ${params.configBuilderName}; ${params.configBuilderName}.set_region(Some(crate::config::Region::new("us-east-1"))); - let config = ${params.configBuilderName}.http_connector(${params.connectorName}).build(); + let config = ${params.configBuilderName}.http_connector(${params.httpClientName}).build(); let ${params.clientName} = #{Client}::from_conf(config); """, "Client" to ClientRustModule.root.toType().resolve("Client"), diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt index 198d1b8bc4c..7c4ffd05433 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/CredentialCaches.kt @@ -99,7 +99,7 @@ class CredentialCacheConfig(codegenContext: ClientCodegenContext) : ConfigCustom || match sleep { Some(sleep) => { #{CredentialsCache}::lazy_builder() - .sleep(sleep) + .sleep_impl(sleep) .into_credentials_cache() } None => #{CredentialsCache}::lazy(), diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt index 7ad1e412467..7167910c08e 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/SdkConfigDecorator.kt @@ -76,7 +76,7 @@ class GenericSmithySdkConfigSettings : ClientCodegenDecorator { ${section.serviceConfigBuilder}.set_timeout_config(${section.sdkConfig}.timeout_config().cloned()); ${section.serviceConfigBuilder}.set_sleep_impl(${section.sdkConfig}.sleep_impl()); - ${section.serviceConfigBuilder}.set_http_connector(${section.sdkConfig}.http_connector().cloned()); + ${section.serviceConfigBuilder}.set_http_client(${section.sdkConfig}.http_client()); ${section.serviceConfigBuilder}.set_time_source(${section.sdkConfig}.time_source()); """, ) diff --git a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt index b0ec8aac317..dcb7e69bc15 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/endpoints/OperationInputTestGenerator.kt @@ -88,7 +88,7 @@ fun usesDeprecatedBuiltIns(testOperationInput: EndpointTestOperationInput): Bool * #[allow(unused_mut)] * let mut builder = aws_sdk_s3::Config::builder() * .with_test_defaults() - * .http_connector(conn); + * .http_client(conn); * let builder = builder.region(aws_types::region::Region::new("us-west-2")); * let builder = builder.use_arn_region(false); * builder.build() diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt index 9c1a29ecaea..3fe721b2390 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointBuiltInsDecoratorTest.kt @@ -87,28 +87,34 @@ class EndpointBuiltInsDecoratorTest { ##[#{tokio}::test] async fn endpoint_url_built_in_works() { - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .uri("https://RIGHT/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .uri("https://RIGHT/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap() + )], + #{TokioSleep}::new(), + ); let config = Config::builder() - .http_connector(connector.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .endpoint_url("https://RIGHT") .build(); let client = Client::from_conf(config); dbg!(client.some_operation().send().await).expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, "tokio" to CargoDependency.Tokio.toDevDependency().withFeature("rt").withFeature("macros").toType(), - "TestConnection" to CargoDependency.smithyClient(codegenContext.runtimeConfig) - .toDevDependency().withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), + "EventClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::EventClient"), + "ConnectionEvent" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::ConnectionEvent"), "SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig), + "TokioSleep" to RuntimeType.smithyAsync(codegenContext.runtimeConfig) + .resolve("rt::sleep::TokioSleep"), ) } } diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt index 07080355410..19ad56d1169 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/EndpointsCredentialsTest.kt @@ -84,9 +84,9 @@ class EndpointsCredentialsTest { tokioTest("default_auth") { rustTemplate( """ - let (conn, rcvr) = #{capture_request}(None); + let (http_client, rcvr) = #{capture_request}(None); let conf = $moduleName::Config::builder() - .http_connector(conn) + .http_client(http_client) .region(#{Region}::new("us-west-2")) .credentials_provider(#{Credentials}::for_tests()) .build(); @@ -107,9 +107,9 @@ class EndpointsCredentialsTest { tokioTest("custom_auth") { rustTemplate( """ - let (conn, rcvr) = #{capture_request}(None); + let (http_client, rcvr) = #{capture_request}(None); let conf = $moduleName::Config::builder() - .http_connector(conn) + .http_client(http_client) .region(#{Region}::new("us-west-2")) .credentials_provider(#{Credentials}::for_tests()) .build(); diff --git a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/InvocationIdDecoratorTest.kt b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/InvocationIdDecoratorTest.kt index 3a792e2a0a1..be0861a045b 100644 --- a/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/InvocationIdDecoratorTest.kt +++ b/aws/sdk-codegen/src/test/kotlin/software/amazon/smithy/rustsdk/InvocationIdDecoratorTest.kt @@ -32,9 +32,9 @@ class InvocationIdDecoratorTest { } } - let (conn, rx) = #{capture_request}(None); + let (http_client, rx) = #{capture_request}(None); let config = $moduleName::Config::builder() - .http_connector(conn) + .http_client(http_client) .invocation_id_generator(TestIdGen) .build(); assert!(config.invocation_id_generator().is_some()); diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ConnectionPoisoningConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ConnectionPoisoningConfigCustomization.kt index cd797f938fa..d8a9b6818bd 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ConnectionPoisoningConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ConnectionPoisoningConfigCustomization.kt @@ -26,7 +26,7 @@ class ConnectionPoisoningRuntimePluginCustomization( section.registerInterceptor(runtimeConfig, this) { rust( "#T::new()", - smithyRuntime(runtimeConfig).resolve("client::connectors::connection_poisoning::ConnectionPoisoningInterceptor"), + smithyRuntime(runtimeConfig).resolve("client::http::connection_poisoning::ConnectionPoisoningInterceptor"), ) } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpConnectorConfigDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpConnectorConfigDecorator.kt index 643d0af31a3..1ebc8049ef4 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpConnectorConfigDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpConnectorConfigDecorator.kt @@ -6,7 +6,6 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext -import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ConfigCustomization import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig @@ -35,85 +34,21 @@ private class HttpConnectorConfigCustomization( *preludeScope, "Connection" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::orchestrator::Connection"), "ConnectorSettings" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::ConnectorSettings"), - "DynConnectorAdapter" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::connectors::adapter::DynConnectorAdapter"), - "HttpConnector" to RuntimeType.smithyClient(runtimeConfig).resolve("http_connector::HttpConnector"), + "IntoShared" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("shared::IntoShared"), "Resolver" to RuntimeType.smithyRuntime(runtimeConfig).resolve("client::config_override::Resolver"), "SharedAsyncSleep" to RuntimeType.smithyAsync(runtimeConfig).resolve("rt::sleep::SharedAsyncSleep"), - "SharedHttpConnector" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::connectors::SharedHttpConnector"), + "SharedHttpClient" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::http::SharedHttpClient"), "TimeoutConfig" to RuntimeType.smithyTypes(runtimeConfig).resolve("timeout::TimeoutConfig"), ) - private fun defaultConnectorFn(): RuntimeType = RuntimeType.forInlineFun("default_connector", ClientRustModule.config) { - rustTemplate( - """ - ##[cfg(feature = "rustls")] - fn default_connector( - connector_settings: &#{ConnectorSettings}, - sleep_impl: #{Option}<#{SharedAsyncSleep}>, - ) -> #{Option}<#{DynConnector}> { - #{default_connector}(connector_settings, sleep_impl) - } - - ##[cfg(not(feature = "rustls"))] - fn default_connector( - _connector_settings: &#{ConnectorSettings}, - _sleep_impl: #{Option}<#{SharedAsyncSleep}>, - ) -> #{Option}<#{DynConnector}> { - #{None} - } - """, - *codegenScope, - "default_connector" to RuntimeType.smithyClient(runtimeConfig).resolve("conns::default_connector"), - "DynConnector" to RuntimeType.smithyClient(runtimeConfig).resolve("erase::DynConnector"), - ) - } - - private fun setConnectorFn(): RuntimeType = RuntimeType.forInlineFun("set_connector", ClientRustModule.config) { - rustTemplate( - """ - fn set_connector(resolver: &mut #{Resolver}<'_>) { - // Initial configuration needs to set a default if no connector is given, so it - // should always get into the condition below. - // - // Override configuration should set the connector if the override config - // contains a connector, sleep impl, or a timeout config since these are all - // incorporated into the final connector. - let must_set_connector = resolver.is_initial() - || resolver.is_latest_set::<#{HttpConnector}>() - || resolver.latest_sleep_impl().is_some() - || resolver.is_latest_set::<#{TimeoutConfig}>(); - if must_set_connector { - let sleep_impl = resolver.sleep_impl(); - let timeout_config = resolver.resolve_config::<#{TimeoutConfig}>() - .cloned() - .unwrap_or_else(#{TimeoutConfig}::disabled); - let connector_settings = #{ConnectorSettings}::from_timeout_config(&timeout_config); - let http_connector = resolver.resolve_config::<#{HttpConnector}>(); - - // TODO(enableNewSmithyRuntimeCleanup): Replace the tower-based DynConnector and remove DynConnectorAdapter when deleting the middleware implementation - let connector = - http_connector - .and_then(|c| c.connector(&connector_settings, sleep_impl.clone())) - .or_else(|| #{default_connector}(&connector_settings, sleep_impl)) - .map(|c| #{SharedHttpConnector}::new(#{DynConnectorAdapter}::new(c))); - - resolver.runtime_components_mut().set_http_connector(connector); - } - } - """, - *codegenScope, - "default_connector" to defaultConnectorFn(), - ) - } - override fun section(section: ServiceConfig): Writable { return when (section) { is ServiceConfig.ConfigImpl -> writable { rustTemplate( """ - /// Return the [`SharedHttpConnector`](#{SharedHttpConnector}) to use when making requests, if any. - pub fn http_connector(&self) -> Option<#{SharedHttpConnector}> { - self.runtime_components.http_connector() + /// Return the [`SharedHttpClient`](#{SharedHttpClient}) to use when making requests, if any. + pub fn http_client(&self) -> Option<#{SharedHttpClient}> { + self.runtime_components.http_client() } """, *codegenScope, @@ -123,7 +58,7 @@ private class HttpConnectorConfigCustomization( ServiceConfig.BuilderImpl -> writable { rustTemplate( """ - /// Sets the HTTP connector to use when making requests. + /// Sets the HTTP client to use when making requests. /// /// ## Examples /// ```no_run @@ -132,10 +67,8 @@ private class HttpConnectorConfigCustomization( /// ## ##[test] /// ## fn example() { /// use std::time::Duration; - /// use aws_smithy_client::{Client, hyper_ext}; - /// use aws_smithy_client::erase::DynConnector; - /// use aws_smithy_client::http_connector::ConnectorSettings; /// use $moduleUseName::config::Config; + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; /// /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() /// .with_webpki_roots() @@ -143,23 +76,23 @@ private class HttpConnectorConfigCustomization( /// .enable_http1() /// .enable_http2() /// .build(); - /// let smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings( - /// ConnectorSettings::builder() - /// .connect_timeout(Duration::from_secs(5)) - /// .build() - /// ) - /// .build(https_connector); + /// let hyper_client = HyperClientBuilder::new().build(https_connector); + /// + /// // This connector can then be given to a generated service Config + /// let config = my_service_client::Config::builder() + /// .endpoint_url("https://example.com") + /// .http_client(hyper_client) + /// .build(); + /// let client = my_service_client::Client::from_conf(config); /// ## } /// ## } /// ``` - pub fn http_connector(mut self, http_connector: impl Into<#{HttpConnector}>) -> Self { - self.set_http_connector(#{Some}(http_connector)); + pub fn http_client(mut self, http_client: impl #{IntoShared}<#{SharedHttpClient}>) -> Self { + self.set_http_client(#{Some}(#{IntoShared}::into_shared(http_client))); self } - /// Sets the HTTP connector to use when making requests. + /// Sets the HTTP client to use when making requests. /// /// ## Examples /// ```no_run @@ -168,30 +101,22 @@ private class HttpConnectorConfigCustomization( /// ## ##[test] /// ## fn example() { /// use std::time::Duration; - /// use aws_smithy_client::hyper_ext; - /// use aws_smithy_client::http_connector::ConnectorSettings; /// use $moduleUseName::config::{Builder, Config}; + /// use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; /// - /// fn override_http_connector(builder: &mut Builder) { + /// fn override_http_client(builder: &mut Builder) { /// let https_connector = hyper_rustls::HttpsConnectorBuilder::new() /// .with_webpki_roots() /// .https_only() /// .enable_http1() /// .enable_http2() /// .build(); - /// let smithy_connector = hyper_ext::Adapter::builder() - /// // Optionally set things like timeouts as well - /// .connector_settings( - /// ConnectorSettings::builder() - /// .connect_timeout(Duration::from_secs(5)) - /// .build() - /// ) - /// .build(https_connector); - /// builder.set_http_connector(Some(smithy_connector)); + /// let hyper_client = HyperClientBuilder::new().build(https_connector); + /// builder.set_http_client(Some(hyper_client)); /// } /// /// let mut builder = $moduleUseName::Config::builder(); - /// override_http_connector(&mut builder); + /// override_http_client(&mut builder); /// let config = builder.build(); /// ## } /// ## } @@ -201,8 +126,8 @@ private class HttpConnectorConfigCustomization( ) rustTemplate( """ - pub fn set_http_connector(&mut self, http_connector: Option>) -> &mut Self { - http_connector.map(|c| self.config.store_put(c.into())); + pub fn set_http_client(&mut self, http_client: Option<#{SharedHttpClient}>) -> &mut Self { + self.runtime_components.set_http_client(http_client); self } """, @@ -210,20 +135,6 @@ private class HttpConnectorConfigCustomization( ) } - is ServiceConfig.BuilderBuild -> writable { - rustTemplate( - "#{set_connector}(&mut resolver);", - "set_connector" to setConnectorFn(), - ) - } - - is ServiceConfig.OperationConfigOverride -> writable { - rustTemplate( - "#{set_connector}(&mut resolver);", - "set_connector" to setConnectorFn(), - ) - } - else -> emptySection } } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt index 87586fbcb73..799aff043dc 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/ResiliencyConfigCustomization.kt @@ -32,6 +32,7 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon "ClientRateLimiter" to retries.resolve("ClientRateLimiter"), "ClientRateLimiterPartition" to retries.resolve("ClientRateLimiterPartition"), "debug" to RuntimeType.Tracing.resolve("debug"), + "IntoShared" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("shared::IntoShared"), "RetryConfig" to retryConfig.resolve("RetryConfig"), "RetryMode" to RuntimeType.smithyTypes(runtimeConfig).resolve("retry::RetryMode"), "RetryPartition" to retries.resolve("RetryPartition"), @@ -150,8 +151,8 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon /// let sleep_impl = SharedAsyncSleep::new(ForeverSleep); /// let config = Config::builder().sleep_impl(sleep_impl).build(); /// ``` - pub fn sleep_impl(mut self, sleep_impl: #{SharedAsyncSleep}) -> Self { - self.set_sleep_impl(Some(sleep_impl)); + pub fn sleep_impl(mut self, sleep_impl: impl #{IntoShared}<#{SharedAsyncSleep}>) -> Self { + self.set_sleep_impl(Some(sleep_impl.into_shared())); self } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt index f3402c950df..4930e0e45ec 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/TimeSourceCustomization.kt @@ -18,6 +18,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.pre class TimeSourceCustomization(codegenContext: ClientCodegenContext) : ConfigCustomization() { private val codegenScope = arrayOf( *preludeScope, + "IntoShared" to RuntimeType.smithyRuntimeApi(codegenContext.runtimeConfig).resolve("shared::IntoShared"), "SharedTimeSource" to RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::SharedTimeSource"), "StaticTimeSource" to RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::StaticTimeSource"), "UNIX_EPOCH" to RuntimeType.std.resolve("time::UNIX_EPOCH"), @@ -46,9 +47,9 @@ class TimeSourceCustomization(codegenContext: ClientCodegenContext) : ConfigCust /// Sets the time source used for this service pub fn time_source( mut self, - time_source: impl #{Into}<#{SharedTimeSource}>, + time_source: impl #{IntoShared}<#{SharedTimeSource}>, ) -> Self { - self.set_time_source(#{Some}(time_source.into())); + self.set_time_source(#{Some}(time_source.into_shared())); self } """, diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt index 78686b2877e..3852d09fb24 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customize/RequiredCustomizations.kt @@ -69,7 +69,13 @@ class RequiredCustomizations : ClientCodegenDecorator { val rc = codegenContext.runtimeConfig // Add rt-tokio feature for `ByteStream::from_path` - rustCrate.mergeFeature(Feature("rt-tokio", true, listOf("aws-smithy-http/rt-tokio"))) + rustCrate.mergeFeature( + Feature( + "rt-tokio", + true, + listOf("aws-smithy-async/rt-tokio", "aws-smithy-http/rt-tokio"), + ), + ) rustCrate.mergeFeature(TestUtilFeature) diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt index 5234f6fe596..dfe7ca58024 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientDecorator.kt @@ -39,7 +39,7 @@ class FluentClientDecorator : ClientCodegenDecorator { customizations = listOf(GenericFluentClient(codegenContext)), ).render(rustCrate) - rustCrate.mergeFeature(Feature("rustls", default = true, listOf("aws-smithy-client/rustls"))) + rustCrate.mergeFeature(Feature("rustls", default = true, listOf("aws-smithy-runtime/tls-rustls"))) } override fun libRsCustomizations( diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt index 614d89bd488..239b912b6bd 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt @@ -480,6 +480,7 @@ private fun baseClientRuntimePluginsFn(runtimeConfig: RuntimeConfig): RuntimeTyp let mut configured_plugins = #{Vec}::new(); ::std::mem::swap(&mut config.runtime_plugins, &mut configured_plugins); let mut plugins = #{RuntimePlugins}::new() + .with_client_plugin(#{default_http_client_plugin}()) .with_client_plugin( #{StaticRuntimePlugin}::new() .with_config(config.config.clone()) @@ -499,6 +500,8 @@ private fun baseClientRuntimePluginsFn(runtimeConfig: RuntimeConfig): RuntimeTyp .resolve("client::auth::no_auth::NoAuthRuntimePlugin"), "StaticRuntimePlugin" to RuntimeType.smithyRuntimeApi(runtimeConfig) .resolve("client::runtime_plugin::StaticRuntimePlugin"), + "default_http_client_plugin" to RuntimeType.smithyRuntime(runtimeConfig) + .resolve("client::http::default_http_client_plugin"), ) } diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt index 44ff13d45e7..4a4f810789c 100644 --- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt +++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt @@ -49,7 +49,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType as RT data class ClientCreationParams( val codegenContext: ClientCodegenContext, - val connectorName: String, + val httpClientName: String, val configBuilderName: String, val clientName: String, ) @@ -75,7 +75,7 @@ class DefaultProtocolTestGenerator( """ let ${params.clientName} = #{Client}::from_conf( ${params.configBuilderName} - .http_connector(${params.connectorName}) + .http_client(${params.httpClientName}) .build() ); """, @@ -206,20 +206,17 @@ class DefaultProtocolTestGenerator( } ?: writable { } rustTemplate( """ - let (conn, request_receiver) = #{capture_request}(None); + let (http_client, request_receiver) = #{capture_request}(None); let config_builder = #{config}::Config::builder().with_test_defaults().endpoint_resolver("https://example.com"); #{customParams} """, - "capture_request" to CargoDependency.smithyClient(rc) - .toDevDependency() - .withFeature("test-util") - .toType() - .resolve("test_connection::capture_request"), + "capture_request" to CargoDependency.smithyRuntimeTestUtil(rc).toType() + .resolve("client::http::test_util::capture_request"), "config" to ClientRustModule.config, "customParams" to customParams, ) - renderClientCreation(this, ClientCreationParams(codegenContext, "conn", "config_builder", "client")) + renderClientCreation(this, ClientCreationParams(codegenContext, "http_client", "config_builder", "client")) writeInline("let result = ") instantiator.renderFluentCall(this, "client", operationShape, inputShape, httpRequestTestCase.params) diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt index e53b66fc148..d81a808645e 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/HttpAuthDecoratorTest.kt @@ -17,10 +17,15 @@ import software.amazon.smithy.rust.codegen.core.testutil.integrationTest class HttpAuthDecoratorTest { private fun codegenScope(runtimeConfig: RuntimeConfig): Array> = arrayOf( - "TestConnection" to CargoDependency.smithyClient(runtimeConfig) + "ConnectionEvent" to CargoDependency.smithyRuntime(runtimeConfig) .toDevDependency().withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), + .resolve("client::http::test_util::ConnectionEvent"), + "EventClient" to CargoDependency.smithyRuntime(runtimeConfig) + .toDevDependency().withFeature("test-util").toType() + .resolve("client::http::test_util::EventClient"), "SdkBody" to RuntimeType.sdkBody(runtimeConfig), + "TokioSleep" to CargoDependency.smithyAsync(runtimeConfig).withFeature("rt-tokio").toType() + .resolve("rt::sleep::TokioSleep"), ) @Test @@ -34,25 +39,28 @@ class HttpAuthDecoratorTest { async fn use_api_key_auth_when_api_key_provided() { use aws_smithy_runtime_api::client::identity::http::Token; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .api_key(Token::new("some-api-key", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -63,26 +71,29 @@ class HttpAuthDecoratorTest { async fn use_basic_auth_when_basic_auth_login_provided() { use aws_smithy_runtime_api::client::identity::http::Login; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .basic_auth_login(Login::new("some-user", "some-pass", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -102,25 +113,28 @@ class HttpAuthDecoratorTest { async fn api_key_applied_to_query_string() { use aws_smithy_runtime_api::client::identity::http::Token; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .uri("http://localhost:1234/SomeOperation?api_key=some-api-key") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .api_key(Token::new("some-api-key", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -140,26 +154,29 @@ class HttpAuthDecoratorTest { async fn api_key_applied_to_headers() { use aws_smithy_runtime_api::client::identity::http::Token; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .header("authorization", "ApiKey some-api-key") - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .header("authorization", "ApiKey some-api-key") + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .api_key(Token::new("some-api-key", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -179,26 +196,29 @@ class HttpAuthDecoratorTest { async fn basic_auth() { use aws_smithy_runtime_api::client::identity::http::Login; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .header("authorization", "Basic c29tZS11c2VyOnNvbWUtcGFzcw==") + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .basic_auth_login(Login::new("some-user", "some-pass", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -218,26 +238,29 @@ class HttpAuthDecoratorTest { async fn basic_auth() { use aws_smithy_runtime_api::client::identity::http::Token; - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .header("authorization", "Bearer some-token") - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .header("authorization", "Bearer some-token") + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .bearer_token(Token::new("some-token", None)) .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), @@ -255,24 +278,27 @@ class HttpAuthDecoratorTest { rustTemplate( """ async fn optional_auth() { - let connector = #{TestConnection}::new(vec![( - http::Request::builder() - .uri("http://localhost:1234/SomeOperation") - .body(#{SdkBody}::empty()) - .unwrap(), - http::Response::builder().status(200).body("").unwrap(), - )]); + let http_client = #{EventClient}::new( + vec![#{ConnectionEvent}::new( + http::Request::builder() + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + #{TokioSleep}::new(), + ); let config = $moduleName::Config::builder() .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.some_operation() .send() .await .expect("success"); - connector.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } """, *codegenScope(codegenContext.runtimeConfig), diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/MetadataCustomizationTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/MetadataCustomizationTest.kt index 1bcc30e0c3b..9010b946594 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/MetadataCustomizationTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/MetadataCustomizationTest.kt @@ -81,10 +81,10 @@ class MetadataCustomizationTest { let (tx, rx) = ::std::sync::mpsc::channel(); - let (conn, _captured_request) = #{capture_request}(#{None}); + let (http_client, _captured_request) = #{capture_request}(#{None}); let client_config = crate::config::Config::builder() .endpoint_resolver("http://localhost:1234/") - .http_connector(conn) + .http_client(http_client) .build(); let client = crate::client::Client::from_conf(client_config); let _ = client diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/SensitiveOutputDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/SensitiveOutputDecoratorTest.kt index ec4d1521519..bf634170ca0 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/SensitiveOutputDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/customizations/SensitiveOutputDecoratorTest.kt @@ -8,7 +8,6 @@ package software.amazon.smithy.rust.codegen.client.smithy.customizations import org.junit.jupiter.api.Test import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.core.rustlang.Attribute -import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType @@ -18,9 +17,6 @@ import software.amazon.smithy.rust.codegen.core.testutil.integrationTest class SensitiveOutputDecoratorTest { private fun codegenScope(runtimeConfig: RuntimeConfig): Array> = arrayOf( "capture_request" to RuntimeType.captureRequest(runtimeConfig), - "TestConnection" to CargoDependency.smithyClient(runtimeConfig) - .toDevDependency().withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), "SdkBody" to RuntimeType.sdkBody(runtimeConfig), ) @@ -56,7 +52,7 @@ class SensitiveOutputDecoratorTest { rustTemplate( """ async fn redacting_sensitive_response_body() { - let (conn, _r) = #{capture_request}(Some( + let (http_client, _r) = #{capture_request}(Some( http::Response::builder() .status(200) .body(#{SdkBody}::from("")) @@ -65,7 +61,7 @@ class SensitiveOutputDecoratorTest { let config = $moduleName::Config::builder() .endpoint_resolver("http://localhost:1234") - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = $moduleName::Client::from_conf(config); let _ = client.say_hello() diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt index b55faa439f4..d57272b1490 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/EndpointsDecoratorTest.kt @@ -10,7 +10,8 @@ import io.kotest.matchers.string.shouldContain import org.junit.jupiter.api.Test import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.core.rustlang.Attribute -import software.amazon.smithy.rust.codegen.core.rustlang.rust +import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate import software.amazon.smithy.rust.codegen.core.testutil.IntegrationTestParams import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest @@ -132,11 +133,11 @@ class EndpointsDecoratorTest { rustCrate.integrationTest("endpoint_params_test") { val moduleName = clientCodegenContext.moduleUseName() Attribute.TokioTest.render(this) - rust( + rustTemplate( """ async fn endpoint_params_are_set() { - use aws_smithy_async::rt::sleep::TokioSleep; - use aws_smithy_client::never::NeverConnector; + use #{NeverClient}; + use #{TokioSleep}; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::endpoint::EndpointResolverParams; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; @@ -194,7 +195,7 @@ class EndpointsDecoratorTest { let interceptor = TestInterceptor::default(); let config = Config::builder() - .http_connector(NeverConnector::new()) + .http_client(NeverClient::new()) .interceptor(interceptor.clone()) .timeout_config( TimeoutConfig::builder() @@ -218,6 +219,10 @@ class EndpointsDecoratorTest { assert_eq!(format!("{}", err), "failed to construct request"); } """, + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(clientCodegenContext.runtimeConfig) + .toType().resolve("client::http::test_util::NeverClient"), + "TokioSleep" to CargoDependency.smithyAsync(clientCodegenContext.runtimeConfig) + .withFeature("rt-tokio").toType().resolve("rt::sleep::TokioSleep"), ) } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ConfigOverrideRuntimePluginGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ConfigOverrideRuntimePluginGeneratorTest.kt index 9534f7a79ea..e7a4ed760f3 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ConfigOverrideRuntimePluginGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/ConfigOverrideRuntimePluginGeneratorTest.kt @@ -90,16 +90,16 @@ internal class ConfigOverrideRuntimePluginGeneratorTest { ) rustCrate.testModule { addDependency(CargoDependency.Tokio.toDevDependency().withFeature("test-util")) - tokioTest("test_operation_overrides_http_connection") { + tokioTest("test_operation_overrides_http_client") { rustTemplate( """ use #{AsyncSleep}; - let (conn, captured_request) = #{capture_request}(#{None}); + let (http_client, captured_request) = #{capture_request}(#{None}); let expected_url = "http://localhost:1234/"; let client_config = crate::config::Config::builder() .endpoint_resolver(expected_url) - .http_connector(#{NeverConnector}::new()) + .http_client(#{NeverClient}::new()) .build(); let client = crate::client::Client::from_conf(client_config.clone()); let sleep = #{TokioSleep}::new(); @@ -120,7 +120,7 @@ internal class ConfigOverrideRuntimePluginGeneratorTest { .customize() .await .unwrap() - .config_override(crate::config::Config::builder().http_connector(conn)) + .config_override(crate::config::Config::builder().http_client(http_client)) .send(); let timeout = #{Timeout}::new( @@ -144,11 +144,11 @@ internal class ConfigOverrideRuntimePluginGeneratorTest { *codegenScope, "AsyncSleep" to RuntimeType.smithyAsync(runtimeConfig).resolve("rt::sleep::AsyncSleep"), "capture_request" to RuntimeType.captureRequest(runtimeConfig), - "NeverConnector" to RuntimeType.smithyClient(runtimeConfig) - .resolve("never::NeverConnector"), + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(runtimeConfig).toType() + .resolve("client::http::test_util::NeverClient"), "Timeout" to RuntimeType.smithyAsync(runtimeConfig).resolve("future::timeout::Timeout"), - "TokioSleep" to RuntimeType.smithyAsync(runtimeConfig) - .resolve("rt::sleep::TokioSleep"), + "TokioSleep" to CargoDependency.smithyAsync(runtimeConfig).withFeature("rt-tokio") + .toType().resolve("rt::sleep::TokioSleep"), ) } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt index 430ff4737ed..3d1dcd009d7 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt @@ -160,7 +160,7 @@ internal class EndpointTraitBindingsTest { rustTemplate( """ async fn test_endpoint_prefix() { - use #{aws_smithy_client}::test_connection::capture_request; + use #{capture_request}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::endpoint::EndpointPrefix; use aws_smithy_runtime_api::box_error::BoxError; @@ -202,7 +202,7 @@ internal class EndpointTraitBindingsTest { } } - let (conn, _r) = capture_request(Some( + let (http_client, _r) = capture_request(Some( http::Response::builder() .status(200) .body(SdkBody::from("")) @@ -210,7 +210,7 @@ internal class EndpointTraitBindingsTest { )); let interceptor = TestInterceptor::default(); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .interceptor(interceptor.clone()) .build(); let client = Client::from_conf(config); @@ -246,8 +246,8 @@ internal class EndpointTraitBindingsTest { ); } """, - "aws_smithy_client" to CargoDependency.smithyClient(clientCodegenContext.runtimeConfig) - .toDevDependency().withFeature("test-util").toType(), + "capture_request" to CargoDependency.smithyRuntimeTestUtil(clientCodegenContext.runtimeConfig) + .toType().resolve("client::http::test_util::capture_request"), ) } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGeneratorTest.kt index 8fb6bb888f3..2abb4229c8c 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/CustomizableOperationGeneratorTest.kt @@ -10,7 +10,6 @@ import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.smithy.RustCrate import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest @@ -44,20 +43,16 @@ class CustomizableOperationGeneratorTest { ##[test] fn test() { - let connector = #{TestConnection}::<#{SdkBody}>::new(Vec::new()); let config = $moduleName::Config::builder() - .http_connector(connector.clone()) + .http_client(#{NeverClient}::new()) .endpoint_resolver("http://localhost:1234") .build(); let client = $moduleName::Client::from_conf(config); check_send_and_sync(client.say_hello().customize()); } """, - "TestConnection" to CargoDependency.smithyClient(codegenContext.runtimeConfig) - .toDevDependency() - .withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), - "SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig), + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::NeverClient"), ) } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGeneratorTest.kt index 121b1fd6a5e..9fa909bda96 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGeneratorTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGeneratorTest.kt @@ -12,7 +12,6 @@ import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate -import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel import software.amazon.smithy.rust.codegen.core.testutil.integrationTest import software.amazon.smithy.rust.codegen.core.util.lookup @@ -77,22 +76,16 @@ class FluentClientGeneratorTest { ##[test] fn test() { - let connector = #{TestConnection}::<#{SdkBody}>::new(Vec::new()); let config = $moduleName::Config::builder() .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(#{NeverClient}::new()) .build(); let client = $moduleName::Client::from_conf(config); check_send(client.say_hello().send()); } """, - "TestConnection" to software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency.smithyClient( - codegenContext.runtimeConfig, - ) - .toDevDependency() - .withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), - "SdkBody" to software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.sdkBody(codegenContext.runtimeConfig), + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::NeverClient"), ) } } @@ -107,10 +100,9 @@ class FluentClientGeneratorTest { """ ##[test] fn test() { - let connector = #{TestConnection}::<#{SdkBody}>::new(Vec::new()); let config = $moduleName::Config::builder() .endpoint_resolver("http://localhost:1234") - .http_connector(connector.clone()) + .http_client(#{NeverClient}::new()) .build(); let client = $moduleName::Client::from_conf(config); @@ -120,11 +112,8 @@ class FluentClientGeneratorTest { assert_eq!(*input.get_byte_value(), Some(4)); } """, - "TestConnection" to CargoDependency.smithyClient(codegenContext.runtimeConfig) - .toDevDependency() - .withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), - "SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig), + "NeverClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::NeverClient"), ) } } diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt index 332e331cb2d..64c7cae3ba8 100644 --- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt +++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/AwsQueryCompatibleTest.kt @@ -58,7 +58,6 @@ class AwsQueryCompatibleTest { ##[cfg(test)] ##[#{tokio}::test] async fn should_parse_code_and_type_fields() { - use #{smithy_client}::test_connection::infallible_connection_fn; use aws_smithy_http::body::SdkBody; let response = |_: http::Request| { @@ -80,7 +79,7 @@ class AwsQueryCompatibleTest { }; let client = crate::Client::from_conf( crate::Config::builder() - .http_connector(infallible_connection_fn(response)) + .http_client(#{infallible_client_fn}(response)) .endpoint_url("http://localhost:1234") .build() ); @@ -92,8 +91,8 @@ class AwsQueryCompatibleTest { assert_eq!(Some("Sender"), error.meta().extra("type")); } """, - "smithy_client" to CargoDependency.smithyClient(context.runtimeConfig) - .toDevDependency().withFeature("test-util").toType(), + "infallible_client_fn" to CargoDependency.smithyRuntimeTestUtil(context.runtimeConfig) + .toType().resolve("client::http::test_util::infallible_client_fn"), "tokio" to CargoDependency.Tokio.toType(), ) } @@ -139,7 +138,6 @@ class AwsQueryCompatibleTest { ##[cfg(test)] ##[#{tokio}::test] async fn should_parse_code_from_payload() { - use #{smithy_client}::test_connection::infallible_connection_fn; use aws_smithy_http::body::SdkBody; let response = |_: http::Request| { @@ -157,7 +155,7 @@ class AwsQueryCompatibleTest { }; let client = crate::Client::from_conf( crate::Config::builder() - .http_connector(infallible_connection_fn(response)) + .http_client(#{infallible_client_fn}(response)) .endpoint_url("http://localhost:1234") .build() ); @@ -166,8 +164,8 @@ class AwsQueryCompatibleTest { assert_eq!(None, error.meta().extra("type")); } """, - "smithy_client" to CargoDependency.smithyClient(context.runtimeConfig) - .toDevDependency().withFeature("test-util").toType(), + "infallible_client_fn" to CargoDependency.smithyRuntimeTestUtil(context.runtimeConfig) + .toType().resolve("client::http::test_util::infallible_client_fn"), "tokio" to CargoDependency.Tokio.toType(), ) } diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt index 73a5e9ebbe6..9dbb43f497e 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/CargoDependency.kt @@ -298,6 +298,7 @@ data class CargoDependency( fun smithyQuery(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-query") fun smithyRuntime(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-runtime") .withFeature("client") + fun smithyRuntimeTestUtil(runtimeConfig: RuntimeConfig) = smithyRuntime(runtimeConfig).toDevDependency().withFeature("test-util") fun smithyRuntimeApi(runtimeConfig: RuntimeConfig) = runtimeConfig.smithyRuntimeCrate("smithy-runtime-api") .withFeature("client") fun smithyRuntimeApiTestUtil(runtimeConfig: RuntimeConfig) = diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt index f7e463b0a7d..639964f762d 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt @@ -476,8 +476,8 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null) return smithyTypes(runtimeConfig).resolve("date_time::Format::$timestampFormat") } - fun captureRequest(runtimeConfig: RuntimeConfig) = - CargoDependency.smithyClientTestUtil(runtimeConfig).toType().resolve("test_connection::capture_request") + fun captureRequest(runtimeConfig: RuntimeConfig) = CargoDependency.smithyRuntimeTestUtil(runtimeConfig).toType() + .resolve("client::http::test_util::capture_request") fun forInlineDependency(inlineDependency: InlineDependency) = RuntimeType("crate::${inlineDependency.name}", inlineDependency) diff --git a/rust-runtime/aws-smithy-runtime-api/src/client.rs b/rust-runtime/aws-smithy-runtime-api/src/client.rs index 9f8a05686eb..2afc2546c56 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client.rs @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +pub mod dns; + pub mod endpoint; /// Smithy identity used by auth and signing. @@ -20,6 +22,6 @@ pub mod runtime_plugin; pub mod auth; -pub mod connectors; +pub mod http; pub mod ser_de; diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/connectors.rs b/rust-runtime/aws-smithy-runtime-api/src/client/connectors.rs deleted file mode 100644 index 9399fa05bff..00000000000 --- a/rust-runtime/aws-smithy-runtime-api/src/client/connectors.rs +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Smithy connectors and related code. -//! -//! # What is a connector? -//! -//! When we talk about connectors, we are referring to the [`HttpConnector`] trait, and implementations of -//! that trait. This trait simply takes a HTTP request, and returns a future with the response for that -//! request. -//! -//! This is slightly different from what a connector is in other libraries such as -//! [`hyper`](https://crates.io/crates/hyper). In hyper 0.x, the connector is a -//! [`tower`](https://crates.io/crates/tower) `Service` that takes a `Uri` and returns -//! a future with something that implements `AsyncRead + AsyncWrite`. -//! -//! The [`HttpConnector`](crate::client::connectors::HttpConnector) is designed to be a layer on top of -//! whole HTTP libraries, such as hyper, which allows Smithy clients to be agnostic to the underlying HTTP -//! transport layer. This also makes it easy to write tests with a fake HTTP connector, and several -//! such test connector implementations are availble in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime). -//! -//! # Responsibilities of a connector -//! -//! A connector primarily makes HTTP requests, but can also be used to implement connect and read -//! timeouts. The `HyperConnector` in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime) -//! is an example where timeouts are implemented as part of the connector. -//! -//! Connectors are also responsible for DNS lookup, TLS, connection reuse, pooling, and eviction. -//! The Smithy clients have no knowledge of such concepts. - -use crate::client::orchestrator::{HttpRequest, HttpResponse}; -use crate::impl_shared_conversions; -use aws_smithy_async::future::now_or_later::NowOrLater; -use aws_smithy_http::result::ConnectorError; -use pin_project_lite::pin_project; -use std::fmt; -use std::future::Future as StdFuture; -use std::pin::Pin; -use std::sync::Arc; -use std::task::Poll; - -type BoxFuture = Pin> + Send>>; - -pin_project! { - /// Future for [`HttpConnector::call`]. - pub struct HttpConnectorFuture { - #[pin] - inner: NowOrLater, BoxFuture>, - } -} - -impl HttpConnectorFuture { - /// Create a new `HttpConnectorFuture` with the given future. - pub fn new(future: F) -> Self - where - F: StdFuture> + Send + 'static, - { - Self { - inner: NowOrLater::new(Box::pin(future)), - } - } - - /// Create a new `HttpConnectorFuture` with the given boxed future. - /// - /// Use this if you already have a boxed future to avoid double boxing it. - pub fn new_boxed( - future: Pin> + Send>>, - ) -> Self { - Self { - inner: NowOrLater::new(future), - } - } - - /// Create a `HttpConnectorFuture` that is immediately ready with the given result. - pub fn ready(result: Result) -> Self { - Self { - inner: NowOrLater::ready(result), - } - } -} - -impl StdFuture for HttpConnectorFuture { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - let this = self.project(); - this.inner.poll(cx) - } -} - -/// Trait with a `call` function that asynchronously converts a request into a response. -/// -/// Ordinarily, a connector would use an underlying HTTP library such as [hyper](https://crates.io/crates/hyper), -/// and any associated HTTPS implementation alongside it to service requests. -/// -/// However, it can also be useful to create fake connectors implementing this trait -/// for testing. -pub trait HttpConnector: Send + Sync + fmt::Debug { - /// Asynchronously converts a request into a response. - fn call(&self, request: HttpRequest) -> HttpConnectorFuture; -} - -/// A shared [`HttpConnector`] implementation. -#[derive(Clone, Debug)] -pub struct SharedHttpConnector(Arc); - -impl SharedHttpConnector { - /// Returns a new [`SharedHttpConnector`]. - pub fn new(connection: impl HttpConnector + 'static) -> Self { - Self(Arc::new(connection)) - } -} - -impl HttpConnector for SharedHttpConnector { - fn call(&self, request: HttpRequest) -> HttpConnectorFuture { - (*self.0).call(request) - } -} - -impl_shared_conversions!(convert SharedHttpConnector from HttpConnector using SharedHttpConnector::new); diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs b/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs new file mode 100644 index 00000000000..5dc237b82aa --- /dev/null +++ b/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Interfaces for resolving DNS + +use crate::box_error::BoxError; +use crate::client::orchestrator::BoxFuture; +use crate::impl_shared_conversions; +use aws_smithy_async::future::now_or_later::NowOrLater; +use std::fmt; +use std::future::Future; +use std::net::IpAddr; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +/// New-type for the future returned by the [`DnsResolver`] trait. +pub struct DnsFuture(NowOrLater, BoxError>, BoxFuture>>); +impl DnsFuture { + /// Create a new `DnsFuture` + pub fn new( + future: impl Future, BoxError>> + Send + 'static, + ) -> Self { + Self(NowOrLater::new(Box::pin(future))) + } + + /// Create a `DnsFuture` that is immediately ready + pub fn ready(result: Result, BoxError>) -> Self { + Self(NowOrLater::ready(result)) + } +} +impl Future for DnsFuture { + type Output = Result, BoxError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.as_mut(); + let inner = Pin::new(&mut this.0); + Future::poll(inner, cx) + } +} + +/// Trait for resolving domain names +pub trait DnsResolver: fmt::Debug + Send + Sync { + /// Asynchronously resolve the given domain name + fn resolve_dns(&self, name: String) -> DnsFuture; +} + +/// Shared DNS resolver +#[derive(Clone, Debug)] +pub struct SharedDnsResolver(Arc); + +impl SharedDnsResolver { + /// Create a new `SharedDnsResolver`. + pub fn new(resolver: impl DnsResolver + 'static) -> Self { + Self(Arc::new(resolver)) + } +} + +impl DnsResolver for SharedDnsResolver { + fn resolve_dns(&self, name: String) -> DnsFuture { + self.0.resolve_dns(name) + } +} + +impl_shared_conversions!(convert SharedDnsResolver from DnsResolver using SharedDnsResolver::new); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_send() { + fn is_send() {} + is_send::(); + } +} diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/http.rs b/rust-runtime/aws-smithy-runtime-api/src/client/http.rs new file mode 100644 index 00000000000..f5b3adb092a --- /dev/null +++ b/rust-runtime/aws-smithy-runtime-api/src/client/http.rs @@ -0,0 +1,288 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Smithy connectors and related code. +//! +//! # What is a connector? +//! +//! When we talk about connectors, we are referring to the [`HttpConnector`] trait, and implementations of +//! that trait. This trait simply takes a HTTP request, and returns a future with the response for that +//! request. +//! +//! This is slightly different from what a connector is in other libraries such as +//! [`hyper`](https://crates.io/crates/hyper). In hyper 0.x, the connector is a +//! [`tower`](https://crates.io/crates/tower) `Service` that takes a `Uri` and returns +//! a future with something that implements `AsyncRead + AsyncWrite`. +//! +//! The [`HttpConnector`](crate::client::connectors::HttpConnector) is designed to be a layer on top of +//! whole HTTP libraries, such as hyper, which allows Smithy clients to be agnostic to the underlying HTTP +//! transport layer. This also makes it easy to write tests with a fake HTTP connector, and several +//! such test connector implementations are availble in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime). +//! +//! # Responsibilities of a connector +//! +//! A connector primarily makes HTTP requests, but can also be used to implement connect and read +//! timeouts. The `HyperConnector` in [`aws-smithy-runtime`](https://crates.io/crates/aws-smithy-runtime) +//! is an example where timeouts are implemented as part of the connector. +//! +//! Connectors are also responsible for DNS lookup, TLS, connection reuse, pooling, and eviction. +//! The Smithy clients have no knowledge of such concepts. + +use crate::client::orchestrator::{HttpRequest, HttpResponse}; +use crate::client::runtime_components::RuntimeComponents; +use crate::impl_shared_conversions; +use aws_smithy_async::future::now_or_later::NowOrLater; +use aws_smithy_http::result::ConnectorError; +use pin_project_lite::pin_project; +use std::fmt; +use std::future::Future as StdFuture; +use std::pin::Pin; +use std::sync::Arc; +use std::task::Poll; +use std::time::Duration; + +type BoxFuture = Pin> + Send>>; + +pin_project! { + /// Future for [`HttpConnector::call`]. + pub struct HttpConnectorFuture { + #[pin] + inner: NowOrLater, BoxFuture>, + } +} + +impl HttpConnectorFuture { + /// Create a new `HttpConnectorFuture` with the given future. + pub fn new(future: F) -> Self + where + F: StdFuture> + Send + 'static, + { + Self { + inner: NowOrLater::new(Box::pin(future)), + } + } + + /// Create a new `HttpConnectorFuture` with the given boxed future. + /// + /// Use this if you already have a boxed future to avoid double boxing it. + pub fn new_boxed( + future: Pin> + Send>>, + ) -> Self { + Self { + inner: NowOrLater::new(future), + } + } + + /// Create a `HttpConnectorFuture` that is immediately ready with the given result. + pub fn ready(result: Result) -> Self { + Self { + inner: NowOrLater::ready(result), + } + } +} + +impl StdFuture for HttpConnectorFuture { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let this = self.project(); + this.inner.poll(cx) + } +} + +/// Trait with a `call` function that asynchronously converts a request into a response. +/// +/// Ordinarily, a connector would use an underlying HTTP library such as [hyper](https://crates.io/crates/hyper), +/// and any associated HTTPS implementation alongside it to service requests. +/// +/// However, it can also be useful to create fake connectors implementing this trait +/// for testing. +pub trait HttpConnector: Send + Sync + fmt::Debug { + /// Asynchronously converts a request into a response. + fn call(&self, request: HttpRequest) -> HttpConnectorFuture; +} + +/// A shared [`HttpConnector`] implementation. +#[derive(Clone, Debug)] +pub struct SharedHttpConnector(Arc); + +impl SharedHttpConnector { + /// Returns a new [`SharedHttpConnector`]. + pub fn new(connection: impl HttpConnector + 'static) -> Self { + Self(Arc::new(connection)) + } +} + +impl HttpConnector for SharedHttpConnector { + fn call(&self, request: HttpRequest) -> HttpConnectorFuture { + (*self.0).call(request) + } +} + +impl_shared_conversions!(convert SharedHttpConnector from HttpConnector using SharedHttpConnector::new); + +/// Returns a [`SharedHttpClient`] that calls the given `connector` function to select a HTTP connector. +pub fn http_client_fn(connector: F) -> SharedHttpClient +where + F: Fn(&HttpConnectorSettings, &RuntimeComponents) -> SharedHttpConnector + + Send + + Sync + + 'static, +{ + struct ConnectorFn(T); + impl fmt::Debug for ConnectorFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ConnectorFn") + } + } + impl HttpClient for ConnectorFn + where + T: (Fn(&HttpConnectorSettings, &RuntimeComponents) -> SharedHttpConnector) + Send + Sync, + { + fn http_connector( + &self, + settings: &HttpConnectorSettings, + components: &RuntimeComponents, + ) -> SharedHttpConnector { + (self.0)(settings, components) + } + } + + SharedHttpClient::new(ConnectorFn(connector)) +} + +/// HTTP client abstraction. +/// +/// A HTTP client implementation must apply connect/read timeout settings, +/// and must maintain a connection pool. +pub trait HttpClient: Send + Sync + fmt::Debug { + /// Returns a HTTP connector based on the requested connector settings. + /// + /// The settings include connector timeouts, which should be incorporated + /// into the connector. The `HttpClient` is responsible for caching + /// the connector across requests. + /// + /// In the future, the settings may have additional parameters added, + /// such as HTTP version, or TLS certificate paths. + fn http_connector( + &self, + settings: &HttpConnectorSettings, + components: &RuntimeComponents, + ) -> SharedHttpConnector; +} + +/// Shared HTTP client for use across multiple clients and requests. +#[derive(Clone, Debug)] +pub struct SharedHttpClient { + selector: Arc, +} + +impl SharedHttpClient { + /// Creates a new `SharedHttpClient` + pub fn new(selector: impl HttpClient + 'static) -> Self { + Self { + selector: Arc::new(selector), + } + } +} + +impl HttpClient for SharedHttpClient { + fn http_connector( + &self, + settings: &HttpConnectorSettings, + components: &RuntimeComponents, + ) -> SharedHttpConnector { + self.selector.http_connector(settings, components) + } +} + +impl_shared_conversions!(convert SharedHttpClient from HttpClient using SharedHttpClient::new); + +/// Builder for [`HttpConnectorSettings`]. +#[non_exhaustive] +#[derive(Default, Debug)] +pub struct HttpConnectorSettingsBuilder { + connect_timeout: Option, + read_timeout: Option, +} + +impl HttpConnectorSettingsBuilder { + /// Creates a new builder. + pub fn new() -> Self { + Default::default() + } + + /// Sets the connect timeout that should be used. + /// + /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection. + pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self { + self.connect_timeout = Some(connect_timeout); + self + } + + /// Sets the connect timeout that should be used. + /// + /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection. + pub fn set_connect_timeout(&mut self, connect_timeout: Option) -> &mut Self { + self.connect_timeout = connect_timeout; + self + } + + /// Sets the read timeout that should be used. + /// + /// The read timeout is the limit on the amount of time it takes to read the first byte of a response + /// from the time the request is initiated. + pub fn read_timeout(mut self, read_timeout: Duration) -> Self { + self.read_timeout = Some(read_timeout); + self + } + + /// Sets the read timeout that should be used. + /// + /// The read timeout is the limit on the amount of time it takes to read the first byte of a response + /// from the time the request is initiated. + pub fn set_read_timeout(&mut self, read_timeout: Option) -> &mut Self { + self.read_timeout = read_timeout; + self + } + + /// Builds the [`HttpConnectorSettings`]. + pub fn build(self) -> HttpConnectorSettings { + HttpConnectorSettings { + connect_timeout: self.connect_timeout, + read_timeout: self.read_timeout, + } + } +} + +/// Settings for HTTP Connectors +#[non_exhaustive] +#[derive(Clone, Default, Debug)] +pub struct HttpConnectorSettings { + connect_timeout: Option, + read_timeout: Option, +} + +impl HttpConnectorSettings { + /// Returns a builder for `HttpConnectorSettings`. + pub fn builder() -> HttpConnectorSettingsBuilder { + Default::default() + } + + /// Returns the connect timeout that should be used. + /// + /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection. + pub fn connect_timeout(&self) -> Option { + self.connect_timeout + } + + /// Returns the read timeout that should be used. + /// + /// The read timeout is the limit on the amount of time it takes to read the first byte of a response + /// from the time the request is initiated. + pub fn read_timeout(&self) -> Option { + self.read_timeout + } +} diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs index 4a92ec6988d..a0ef4a8023e 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_components.rs @@ -14,8 +14,8 @@ use crate::client::auth::{ AuthScheme, AuthSchemeId, SharedAuthScheme, SharedAuthSchemeOptionResolver, }; -use crate::client::connectors::SharedHttpConnector; use crate::client::endpoint::SharedEndpointResolver; +use crate::client::http::SharedHttpClient; use crate::client::identity::{ConfiguredIdentityResolver, SharedIdentityResolver}; use crate::client::interceptors::SharedInterceptor; use crate::client::retries::{RetryClassifiers, SharedRetryStrategy}; @@ -184,7 +184,7 @@ declare_runtime_components! { auth_scheme_option_resolver: Option, // A connector is not required since a client could technically only be used for presigning - http_connector: Option, + http_client: Option, #[required] endpoint_resolver: Option, @@ -219,9 +219,9 @@ impl RuntimeComponents { self.auth_scheme_option_resolver.value.clone() } - /// Returns the connector. - pub fn http_connector(&self) -> Option { - self.http_connector.as_ref().map(|s| s.value.clone()) + /// Returns the HTTP client. + pub fn http_client(&self) -> Option { + self.http_client.as_ref().map(|s| s.value.clone()) } /// Returns the endpoint resolver. @@ -290,26 +290,26 @@ impl RuntimeComponentsBuilder { self } - /// Returns the HTTP connector. - pub fn http_connector(&self) -> Option { - self.http_connector.as_ref().map(|s| s.value.clone()) + /// Returns the HTTP client. + pub fn http_client(&self) -> Option { + self.http_client.as_ref().map(|s| s.value.clone()) } - /// Sets the HTTP connector. - pub fn set_http_connector( + /// Sets the HTTP client. + pub fn set_http_client( &mut self, - connector: Option>, + connector: Option>, ) -> &mut Self { - self.http_connector = connector.map(|c| Tracked::new(self.builder_name, c.into_shared())); + self.http_client = connector.map(|c| Tracked::new(self.builder_name, c.into_shared())); self } - /// Sets the HTTP connector. - pub fn with_http_connector( + /// Sets the HTTP client. + pub fn with_http_client( mut self, - connector: Option>, + connector: Option>, ) -> Self { - self.set_http_connector(connector); + self.set_http_client(connector); self } @@ -489,8 +489,11 @@ impl RuntimeComponentsBuilder { } /// Sets the async sleep implementation. - pub fn with_sleep_impl(mut self, sleep_impl: Option) -> Self { - self.sleep_impl = sleep_impl.map(|s| Tracked::new(self.builder_name, s)); + pub fn with_sleep_impl( + mut self, + sleep_impl: Option>, + ) -> Self { + self.sleep_impl = sleep_impl.map(|s| Tracked::new(self.builder_name, s.into_shared())); self } @@ -506,8 +509,11 @@ impl RuntimeComponentsBuilder { } /// Sets the time source. - pub fn with_time_source(mut self, time_source: Option) -> Self { - self.time_source = time_source.map(|s| Tracked::new(self.builder_name, s)); + pub fn with_time_source( + mut self, + time_source: Option>, + ) -> Self { + self.time_source = time_source.map(|s| Tracked::new(self.builder_name, s.into_shared())); self } } @@ -533,11 +539,11 @@ impl RuntimeComponentsBuilder { #[cfg(feature = "test-util")] pub fn for_tests() -> Self { use crate::client::auth::AuthSchemeOptionResolver; - use crate::client::connectors::{HttpConnector, HttpConnectorFuture}; use crate::client::endpoint::{EndpointResolver, EndpointResolverParams}; + use crate::client::http::HttpClient; use crate::client::identity::Identity; use crate::client::identity::IdentityResolver; - use crate::client::orchestrator::{Future, HttpRequest}; + use crate::client::orchestrator::Future; use crate::client::retries::RetryStrategy; use aws_smithy_async::rt::sleep::AsyncSleep; use aws_smithy_async::time::TimeSource; @@ -557,10 +563,14 @@ impl RuntimeComponentsBuilder { } #[derive(Debug)] - struct FakeConnector; - impl HttpConnector for FakeConnector { - fn call(&self, _: HttpRequest) -> HttpConnectorFuture { - unreachable!("fake connector must be overridden for this test") + struct FakeClient; + impl HttpClient for FakeClient { + fn http_connector( + &self, + _: &crate::client::http::HttpConnectorSettings, + _: &RuntimeComponents, + ) -> crate::client::http::SharedHttpConnector { + unreachable!("fake client must be overridden for this test") } } @@ -642,7 +652,7 @@ impl RuntimeComponentsBuilder { .with_auth_scheme(FakeAuthScheme) .with_auth_scheme_option_resolver(Some(FakeAuthSchemeOptionResolver)) .with_endpoint_resolver(Some(FakeEndpointResolver)) - .with_http_connector(Some(FakeConnector)) + .with_http_client(Some(FakeClient)) .with_identity_resolver(AuthSchemeId::new("fake"), FakeIdentityResolver) .with_retry_classifiers(Some(RetryClassifiers::new())) .with_retry_strategy(Some(FakeRetryStrategy)) diff --git a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs index 34548c0ab71..d0e529b67f8 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/runtime_plugin.rs @@ -268,10 +268,13 @@ impl RuntimePlugins { #[cfg(test)] mod tests { use super::{RuntimePlugin, RuntimePlugins}; - use crate::client::connectors::{HttpConnector, HttpConnectorFuture, SharedHttpConnector}; + use crate::client::http::{ + http_client_fn, HttpClient, HttpConnector, HttpConnectorFuture, SharedHttpConnector, + }; use crate::client::orchestrator::HttpRequest; use crate::client::runtime_components::RuntimeComponentsBuilder; use crate::client::runtime_plugin::{Order, SharedRuntimePlugin}; + use crate::shared::IntoShared; use aws_smithy_http::body::SdkBody; use aws_smithy_types::config_bag::ConfigBag; use http::HeaderValue; @@ -392,7 +395,7 @@ mod tests { ) -> Cow<'_, RuntimeComponentsBuilder> { Cow::Owned( RuntimeComponentsBuilder::new("Plugin1") - .with_http_connector(Some(SharedHttpConnector::new(Connector1))), + .with_http_client(Some(http_client_fn(|_, _| Connector1.into_shared()))), ) } } @@ -409,11 +412,13 @@ mod tests { &self, current_components: &RuntimeComponentsBuilder, ) -> Cow<'_, RuntimeComponentsBuilder> { + let current = current_components.http_client().unwrap(); Cow::Owned( - RuntimeComponentsBuilder::new("Plugin2").with_http_connector(Some( - SharedHttpConnector::new(Connector2( - current_components.http_connector().unwrap(), - )), + RuntimeComponentsBuilder::new("Plugin2").with_http_client(Some( + http_client_fn(move |settings, components| { + let connector = current.http_connector(settings, components); + SharedHttpConnector::new(Connector2(connector)) + }), )), ) } @@ -426,11 +431,13 @@ mod tests { .with_client_plugin(Plugin1); let mut cfg = ConfigBag::base(); let components = plugins.apply_client_configuration(&mut cfg).unwrap(); + let fake_components = RuntimeComponentsBuilder::for_tests().build().unwrap(); // Use the resulting HTTP connector to make a response let resp = components - .http_connector() + .http_client() .unwrap() + .http_connector(&Default::default(), &fake_components) .call( http::Request::builder() .method("GET") diff --git a/rust-runtime/aws-smithy-runtime-api/src/shared.rs b/rust-runtime/aws-smithy-runtime-api/src/shared.rs index c45506c9f3c..365fcd7ecfe 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/shared.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/shared.rs @@ -180,6 +180,14 @@ macro_rules! impl_shared_conversions { }; } +// TODO(TODO): Move these impls once aws-smithy-async is merged into aws-smithy-runtime-api +mod async_impls { + use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; + use aws_smithy_async::time::{SharedTimeSource, TimeSource}; + impl_shared_conversions!(convert SharedAsyncSleep from AsyncSleep using SharedAsyncSleep::new); + impl_shared_conversions!(convert SharedTimeSource from TimeSource using SharedTimeSource::new); +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust-runtime/aws-smithy-runtime/Cargo.toml b/rust-runtime/aws-smithy-runtime/Cargo.toml index 743d5d2dae4..904e10e4ce3 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -13,8 +13,9 @@ repository = "https://github.com/awslabs/smithy-rs" client = ["aws-smithy-runtime-api/client"] http-auth = ["aws-smithy-runtime-api/http-auth"] test-util = ["aws-smithy-runtime-api/test-util", "dep:aws-smithy-protocol-test", "dep:tracing-subscriber", "dep:serde", "dep:serde_json"] -connector-hyper = ["dep:hyper", "hyper?/client", "hyper?/http2", "hyper?/http1", "hyper?/tcp"] -tls-rustls = ["dep:hyper-rustls", "dep:rustls", "connector-hyper"] +connector-hyper-0-14-x = ["dep:hyper", "hyper?/client", "hyper?/http2", "hyper?/http1", "hyper?/tcp"] +tls-rustls = ["dep:hyper-rustls", "dep:rustls"] +rt-tokio = ["tokio/rt"] [dependencies] aws-smithy-async = { path = "../aws-smithy-async" } diff --git a/rust-runtime/aws-smithy-runtime/external-types.toml b/rust-runtime/aws-smithy-runtime/external-types.toml index f735d80eef0..fe24b1a1b04 100644 --- a/rust-runtime/aws-smithy-runtime/external-types.toml +++ b/rust-runtime/aws-smithy-runtime/external-types.toml @@ -3,8 +3,6 @@ allowed_external_types = [ "aws_smithy_async::*", "aws_smithy_http::*", "aws_smithy_types::*", - "aws_smithy_client::erase::DynConnector", - "aws_smithy_client::http_connector::ConnectorSettings", # TODO(audit-external-type-usage) We should newtype these or otherwise avoid exposing them "http::header::name::HeaderName", @@ -13,7 +11,7 @@ allowed_external_types = [ "http::uri::Uri", # Used for creating hyper connectors - "tower_service::Service", + "tower_service::Service", # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `test-util` feature "aws_smithy_protocol_test::MediaType", @@ -22,7 +20,7 @@ allowed_external_types = [ "serde::de::Deserialize", "hyper::client::connect::dns::Name", - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `connector-hyper` feature + # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `connector-hyper-0-14-x` feature "hyper::client::client::Builder", "hyper::client::connect::Connection", "tokio::io::async_read::AsyncRead", diff --git a/rust-runtime/aws-smithy-runtime/src/client.rs b/rust-runtime/aws-smithy-runtime/src/client.rs index 71d1a0ea0fa..392dc120232 100644 --- a/rust-runtime/aws-smithy-runtime/src/client.rs +++ b/rust-runtime/aws-smithy-runtime/src/client.rs @@ -6,11 +6,13 @@ /// Smithy auth scheme implementations. pub mod auth; -/// Built-in Smithy connectors. +pub mod dns; + +/// Built-in Smithy HTTP clients and connectors. /// -/// See the [module docs in `aws-smithy-runtime-api`](aws_smithy_runtime_api::client::connectors) -/// for more information about connectors. -pub mod connectors; +/// See the [module docs in `aws-smithy-runtime-api`](aws_smithy_runtime_api::client::http) +/// for more information about clients and connectors. +pub mod http; /// Utility to simplify config building for config and config overrides. pub mod config_override; diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors.rs b/rust-runtime/aws-smithy-runtime/src/client/connectors.rs deleted file mode 100644 index 4666c04bc2b..00000000000 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors.rs +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/// Interceptor for connection poisoning. -pub mod connection_poisoning; - -#[cfg(feature = "test-util")] -pub mod test_util; - -/// Default HTTP and TLS connectors that use hyper and rustls. -#[cfg(feature = "connector-hyper")] -pub mod hyper_connector; - -// TODO(enableNewSmithyRuntimeCleanup): Delete this module -/// Unstable API for interfacing the old middleware connectors with the newer orchestrator connectors. -/// -/// Important: This module and its contents will be removed in the next release. -pub mod adapter { - use aws_smithy_client::erase::DynConnector; - use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; - use aws_smithy_runtime_api::client::orchestrator::HttpRequest; - use std::sync::{Arc, Mutex}; - - /// Adapts a [`DynConnector`] to the [`HttpConnector`] trait. - /// - /// This is a temporary adapter that allows the old-style tower-based connectors to - /// work with the new non-tower based architecture of the generated clients. - /// It will be removed in a future release. - #[derive(Debug)] - pub struct DynConnectorAdapter { - // `DynConnector` requires `&mut self`, so we need interior mutability to adapt to it - dyn_connector: Arc>, - } - - impl DynConnectorAdapter { - /// Creates a new `DynConnectorAdapter`. - pub fn new(dyn_connector: DynConnector) -> Self { - Self { - dyn_connector: Arc::new(Mutex::new(dyn_connector)), - } - } - } - - impl HttpConnector for DynConnectorAdapter { - fn call(&self, request: HttpRequest) -> HttpConnectorFuture { - let future = self.dyn_connector.lock().unwrap().call_lite(request); - HttpConnectorFuture::new(future) - } - } -} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/never.rs b/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/never.rs deleted file mode 100644 index dbd1d7c4cf3..00000000000 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/never.rs +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Test connectors that never return data - -use aws_smithy_async::future::never::Never; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; -use aws_smithy_runtime_api::client::orchestrator::HttpRequest; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; - -/// A connector that will never respond. -/// -/// Returned futures will return Pending forever -#[derive(Clone, Debug, Default)] -pub struct NeverConnector { - invocations: Arc, -} - -impl NeverConnector { - /// Create a new never connector. - pub fn new() -> Self { - Default::default() - } - - /// Returns the number of invocations made to this connector. - pub fn num_calls(&self) -> usize { - self.invocations.load(Ordering::SeqCst) - } -} - -impl HttpConnector for NeverConnector { - fn call(&self, _request: HttpRequest) -> HttpConnectorFuture { - self.invocations.fetch_add(1, Ordering::SeqCst); - HttpConnectorFuture::new(async move { - Never::new().await; - unreachable!() - }) - } -} diff --git a/rust-runtime/aws-smithy-runtime/src/client/dns.rs b/rust-runtime/aws-smithy-runtime/src/client/dns.rs new file mode 100644 index 00000000000..8f4da84b6c8 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/dns.rs @@ -0,0 +1,45 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Built-in DNS resolver implementations. + +#[cfg(all(feature = "rt-tokio", not(target_family = "wasm")))] +mod tokio { + use aws_smithy_runtime_api::client::dns::{DnsFuture, DnsResolver}; + use std::io::{Error as IoError, ErrorKind as IoErrorKind}; + use std::net::ToSocketAddrs; + + /// DNS resolver that uses `tokio::spawn_blocking` to resolve DNS using the standard library. + /// + /// This implementation isn't available for WASM targets. + #[non_exhaustive] + #[derive(Debug, Default)] + pub struct TokioDnsResolver; + + impl TokioDnsResolver { + /// Creates a new Tokio DNS resolver + pub fn new() -> Self { + Self + } + } + + impl DnsResolver for TokioDnsResolver { + fn resolve_dns(&self, name: String) -> DnsFuture { + DnsFuture::new(async move { + let result = tokio::task::spawn_blocking(move || (name, 0).to_socket_addrs()).await; + match result { + Err(join_failure) => Err(IoError::new(IoErrorKind::Other, join_failure).into()), + Ok(Ok(dns_result)) => { + Ok(dns_result.into_iter().map(|addr| addr.ip()).collect()) + } + Ok(Err(dns_failure)) => Err(dns_failure.into()), + } + }) + } + } +} + +#[cfg(feature = "rt-tokio")] +pub use self::tokio::TokioDnsResolver; diff --git a/rust-runtime/aws-smithy-runtime/src/client/http.rs b/rust-runtime/aws-smithy-runtime/src/client/http.rs new file mode 100644 index 00000000000..b04cb918245 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/http.rs @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_smithy_runtime_api::client::http::SharedHttpClient; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; +use aws_smithy_runtime_api::client::runtime_plugin::{ + Order, SharedRuntimePlugin, StaticRuntimePlugin, +}; + +/// Interceptor for connection poisoning. +pub mod connection_poisoning; + +#[cfg(feature = "test-util")] +pub mod test_util; + +/// Default HTTP and TLS connectors that use hyper 0.14.x and rustls. +/// +/// This module is named after the hyper version number since we anticipate +/// needing to provide equivalent functionality for hyper 1.x in the future. +#[cfg(feature = "connector-hyper-0-14-x")] +pub mod hyper_014; + +/// Runtime plugin that provides a default connector. Intended to be used by the generated code. +#[doc(hidden)] +pub fn default_http_client_plugin() -> SharedRuntimePlugin { + let _default: Option = None; + #[cfg(feature = "connector-hyper-0-14-x")] + let _default = hyper_014::default_client(); + + let plugin = StaticRuntimePlugin::new() + .with_order(Order::Defaults) + .with_runtime_components( + RuntimeComponentsBuilder::new("default_http_client_plugin").with_http_client(_default), + ); + SharedRuntimePlugin::new(plugin) +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/connection_poisoning.rs b/rust-runtime/aws-smithy-runtime/src/client/http/connection_poisoning.rs similarity index 100% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/connection_poisoning.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/connection_poisoning.rs diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/hyper_connector.rs b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs similarity index 82% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/hyper_connector.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs index 9285394020d..39304e0a1fc 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/hyper_connector.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/hyper_014.rs @@ -5,28 +5,34 @@ use aws_smithy_async::future::timeout::TimedOutError; use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; -use aws_smithy_client::http_connector::ConnectorSettings; use aws_smithy_http::body::SdkBody; use aws_smithy_http::connection::{CaptureSmithyConnection, ConnectionMetadata}; use aws_smithy_http::result::ConnectorError; use aws_smithy_runtime_api::box_error::BoxError; -use aws_smithy_runtime_api::client::connectors::SharedHttpConnector; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpClient, + SharedHttpConnector, +}; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::retry::ErrorKind; use http::{Extensions, Uri}; use hyper::client::connect::{capture_connection, CaptureConnection, Connection, HttpInfo}; use hyper::service::Service; +use std::collections::HashMap; use std::error::Error; use std::fmt; use std::fmt::Debug; +use std::sync::RwLock; +use std::time::Duration; use tokio::io::{AsyncRead, AsyncWrite}; #[cfg(feature = "tls-rustls")] mod default_connector { use aws_smithy_async::rt::sleep::SharedAsyncSleep; - use aws_smithy_client::http_connector::ConnectorSettings; + use aws_smithy_runtime_api::client::http::HttpConnectorSettings; // Creating a `with_native_roots` HTTP client takes 300ms on OS X. Cache this so that we // don't need to repeatedly incur that cost. @@ -61,7 +67,7 @@ mod default_connector { }); pub(super) fn base( - settings: &ConnectorSettings, + settings: &HttpConnectorSettings, sleep: Option, ) -> super::HyperConnectorBuilder { let mut hyper = super::HyperConnector::builder().connector_settings(settings.clone()); @@ -80,9 +86,9 @@ mod default_connector { } } -/// Given `ConnectorSettings` and an `SharedAsyncSleep`, create a `SharedHttpConnector` from defaults depending on what cargo features are activated. +/// Given `HttpConnectorSettings` and an `SharedAsyncSleep`, create a `SharedHttpConnector` from defaults depending on what cargo features are activated. pub fn default_connector( - settings: &ConnectorSettings, + settings: &HttpConnectorSettings, sleep: Option, ) -> Option { #[cfg(feature = "tls-rustls")] @@ -98,6 +104,20 @@ pub fn default_connector( } } +/// Creates a hyper-backed HTTPS client from defaults depending on what cargo features are activated. +pub fn default_client() -> Option { + #[cfg(feature = "tls-rustls")] + { + tracing::trace!("creating a new default hyper 0.14.x client"); + Some(HyperClientBuilder::new().build_https()) + } + #[cfg(not(feature = "tls-rustls"))] + { + tracing::trace!("no default connector available"); + None + } +} + /// [`HttpConnector`] that uses [`hyper`] to make HTTP requests. /// /// This connector also implements socket connect and read timeouts. @@ -170,7 +190,7 @@ impl HttpConnector for HyperConnector { /// Builder for [`HyperConnector`]. #[derive(Default, Debug)] pub struct HyperConnectorBuilder { - connector_settings: Option, + connector_settings: Option, sleep_impl: Option, client_builder: Option, } @@ -228,8 +248,8 @@ impl HyperConnectorBuilder { /// /// Calling this is only necessary for testing or to use something other than /// [`default_async_sleep`]. - pub fn sleep_impl(mut self, sleep_impl: SharedAsyncSleep) -> Self { - self.sleep_impl = Some(sleep_impl); + pub fn sleep_impl(mut self, sleep_impl: impl IntoShared) -> Self { + self.sleep_impl = Some(sleep_impl.into_shared()); self } @@ -243,7 +263,7 @@ impl HyperConnectorBuilder { } /// Configure the HTTP settings for the `HyperAdapter` - pub fn connector_settings(mut self, connector_settings: ConnectorSettings) -> Self { + pub fn connector_settings(mut self, connector_settings: HttpConnectorSettings) -> Self { self.connector_settings = Some(connector_settings); self } @@ -251,7 +271,7 @@ impl HyperConnectorBuilder { /// Configure the HTTP settings for the `HyperAdapter` pub fn set_connector_settings( &mut self, - connector_settings: Option, + connector_settings: Option, ) -> &mut Self { self.connector_settings = connector_settings; self @@ -391,6 +411,129 @@ fn find_source<'a, E: Error + 'static>(err: &'a (dyn Error + 'static)) -> Option None } +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +struct CacheKey { + connect_timeout: Option, + read_timeout: Option, +} + +impl From<&HttpConnectorSettings> for CacheKey { + fn from(value: &HttpConnectorSettings) -> Self { + Self { + connect_timeout: value.connect_timeout(), + read_timeout: value.read_timeout(), + } + } +} + +struct HyperClient { + connector_cache: RwLock>, + client_builder: hyper::client::Builder, + tcp_connector: C, +} + +impl fmt::Debug for HyperClient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HyperClient") + .field("connector_cache", &self.connector_cache) + .field("client_builder", &self.client_builder) + .finish() + } +} + +impl HttpClient for HyperClient +where + C: Clone + Send + Sync + 'static, + C: Service, + C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, + C::Future: Unpin + Send + 'static, + C::Error: Into, +{ + fn http_connector( + &self, + settings: &HttpConnectorSettings, + components: &RuntimeComponents, + ) -> SharedHttpConnector { + let key = CacheKey::from(settings); + let mut connector = self.connector_cache.read().unwrap().get(&key).cloned(); + if connector.is_none() { + let mut cache = self.connector_cache.write().unwrap(); + // Short-circuit if another thread already wrote a connector to the cache for this key + if !cache.contains_key(&key) { + let mut builder = HyperConnector::builder() + .hyper_builder(self.client_builder.clone()) + .connector_settings(settings.clone()); + builder.set_sleep_impl(components.sleep_impl()); + + let connector = SharedHttpConnector::new(builder.build(self.tcp_connector.clone())); + cache.insert(key.clone(), connector); + } + connector = cache.get(&key).cloned(); + } + + connector.expect("cache populated above") + } +} + +/// Builder for a hyper-backed [`HttpClient`] implementation. +/// +/// This builder can be used to customize the underlying TCP connector used, as well as +/// hyper client configuration. +#[derive(Clone, Default, Debug)] +pub struct HyperClientBuilder { + client_builder: Option, +} + +impl HyperClientBuilder { + /// Creates a new builder. + pub fn new() -> Self { + Self::default() + } + + /// Override the Hyper client [`Builder`](hyper::client::Builder) used to construct this client. + /// + /// This enables changing settings like forcing HTTP2 and modifying other default client behavior. + pub fn hyper_builder(mut self, hyper_builder: hyper::client::Builder) -> Self { + self.client_builder = Some(hyper_builder); + self + } + + /// Override the Hyper client [`Builder`](hyper::client::Builder) used to construct this client. + /// + /// This enables changing settings like forcing HTTP2 and modifying other default client behavior. + pub fn set_hyper_builder( + &mut self, + hyper_builder: Option, + ) -> &mut Self { + self.client_builder = hyper_builder; + self + } + + /// Create a [`HyperConnector`] with the default rustls HTTPS implementation. + #[cfg(feature = "tls-rustls")] + pub fn build_https(self) -> SharedHttpClient { + self.build(default_connector::https()) + } + + /// Create a [`SharedSelectHttpConnector`] from this builder and a given connector. + /// + /// Use [`build_https`](HyperConnectorSelectorBuilder::build_https) if you don't want to provide a custom TCP connector. + pub fn build(self, tcp_connector: C) -> SharedHttpClient + where + C: Clone + Send + Sync + 'static, + C: Service, + C::Response: Connection + AsyncRead + AsyncWrite + Send + Unpin + 'static, + C::Future: Unpin + Send + 'static, + C::Error: Into, + { + SharedHttpClient::new(HyperClient { + connector_cache: RwLock::new(HashMap::new()), + client_builder: self.client_builder.unwrap_or_default(), + tcp_connector, + }) + } +} + mod timeout_middleware { use aws_smithy_async::future::timeout::{TimedOutError, Timeout}; use aws_smithy_async::rt::sleep::Sleep; @@ -600,7 +743,6 @@ mod timeout_middleware { use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep}; use aws_smithy_http::body::SdkBody; use aws_smithy_types::error::display::DisplayErrorContext; - use aws_smithy_types::timeout::TimeoutConfig; use hyper::client::connect::Connected; use std::time::Duration; use tokio::io::ReadBuf; @@ -699,11 +841,9 @@ mod timeout_middleware { #[tokio::test] async fn http_connect_timeout_works() { let tcp_connector = NeverConnects::default(); - let connector_settings = ConnectorSettings::from_timeout_config( - &TimeoutConfig::builder() - .connect_timeout(Duration::from_secs(1)) - .build(), - ); + let connector_settings = HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(1)) + .build(); let hyper = HyperConnector::builder() .connector_settings(connector_settings) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) @@ -738,12 +878,10 @@ mod timeout_middleware { #[tokio::test] async fn http_read_timeout_works() { let tcp_connector = NeverReplies::default(); - let connector_settings = ConnectorSettings::from_timeout_config( - &TimeoutConfig::builder() - .connect_timeout(Duration::from_secs(1)) - .read_timeout(Duration::from_secs(2)) - .build(), - ); + let connector_settings = HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(1)) + .read_timeout(Duration::from_secs(2)) + .build(); let hyper = HyperConnector::builder() .connector_settings(connector_settings) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs similarity index 88% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs index 686b7101976..0fe5bf78007 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs @@ -28,14 +28,17 @@ mod capture_request; pub use capture_request::{capture_request, CaptureRequestHandler, CaptureRequestReceiver}; -#[cfg(feature = "connector-hyper")] +#[cfg(feature = "connector-hyper-0-14-x")] pub mod dvr; -mod event_connector; -pub use event_connector::{ConnectionEvent, EventConnector}; +mod event; +pub use event::{ConnectionEvent, EventClient}; mod infallible; -pub use infallible::infallible_connection_fn; +pub use infallible::infallible_client_fn; mod never; -pub use never::NeverConnector; +pub use never::NeverClient; + +#[cfg(feature = "connector-hyper-0-14-x")] +pub use never::NeverTcpConnector; diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/capture_request.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/capture_request.rs similarity index 83% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/capture_request.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/capture_request.rs index 7721af15dee..06915c942eb 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/capture_request.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/capture_request.rs @@ -4,8 +4,12 @@ */ use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, +}; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use std::fmt::Debug; use std::sync::{Arc, Mutex}; use tokio::sync::oneshot; @@ -36,6 +40,16 @@ impl HttpConnector for CaptureRequestHandler { } } +impl HttpClient for CaptureRequestHandler { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} + /// Receiver for [`CaptureRequestHandler`](CaptureRequestHandler) #[derive(Debug)] pub struct CaptureRequestReceiver { @@ -70,9 +84,9 @@ impl CaptureRequestReceiver { /// /// Example: /// ```compile_fail -/// let (server, request) = capture_request(None); +/// let (capture_client, request) = capture_request(None); /// let conf = aws_sdk_sts::Config::builder() -/// .http_connector(server) +/// .http_client(capture_client) /// .build(); /// let client = aws_sdk_sts::Client::from_conf(conf); /// let _ = client.assume_role_with_saml().send().await; diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr.rs similarity index 95% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr.rs index 90f37b95baf..dc9eaa638e1 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr.rs @@ -18,8 +18,8 @@ mod record; mod replay; pub use aws_smithy_protocol_test::MediaType; -pub use record::RecordingConnector; -pub use replay::ReplayingConnector; +pub use record::RecordingClient; +pub use replay::ReplayingClient; /// A complete traffic recording /// @@ -232,7 +232,7 @@ mod tests { use super::*; use aws_smithy_http::body::SdkBody; use aws_smithy_http::byte_stream::ByteStream; - use aws_smithy_runtime_api::client::connectors::{HttpConnector, SharedHttpConnector}; + use aws_smithy_runtime_api::client::http::{HttpConnector, SharedHttpConnector}; use bytes::Bytes; use http::Uri; use std::error::Error; @@ -244,8 +244,8 @@ mod tests { // make a request, then verify that the same traffic was recorded. let network_traffic = fs::read_to_string("test-data/example.com.json")?; let network_traffic: NetworkTraffic = serde_json::from_str(&network_traffic)?; - let inner = ReplayingConnector::new(network_traffic.events.clone()); - let connection = RecordingConnector::new(SharedHttpConnector::new(inner.clone())); + let inner = ReplayingClient::new(network_traffic.events.clone()); + let connection = RecordingClient::new(SharedHttpConnector::new(inner.clone())); let req = http::Request::post("https://www.example.com") .body(SdkBody::from("hello world")) .unwrap(); diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/record.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs similarity index 87% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/record.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs index c175b24d3cf..8ab4e56d3e7 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/record.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/record.rs @@ -8,10 +8,12 @@ use super::{ Version, }; use aws_smithy_http::body::SdkBody; -use aws_smithy_runtime_api::client::connectors::{ - HttpConnector, HttpConnectorFuture, SharedHttpConnector, +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, }; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use http_body::Body; use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -19,21 +21,21 @@ use std::sync::{Arc, Mutex, MutexGuard}; use std::{fs, io}; use tokio::task::JoinHandle; -/// Recording Connection Wrapper +/// Recording client /// -/// RecordingConnector wraps an inner connection and records all traffic, enabling traffic replay. +/// `RecordingClient` wraps an inner connection and records all traffic, enabling traffic replay. #[derive(Clone, Debug)] -pub struct RecordingConnector { +pub struct RecordingClient { pub(crate) data: Arc>>, pub(crate) num_events: Arc, pub(crate) inner: SharedHttpConnector, } #[cfg(all(feature = "tls-rustls"))] -impl RecordingConnector { - /// Construct a recording connection wrapping a default HTTPS implementation +impl RecordingClient { + /// Construct a recording connection wrapping a default HTTPS implementation without any timeouts. pub fn https() -> Self { - use crate::client::connectors::hyper_connector::HyperConnector; + use crate::client::http::hyper_014::HyperConnector; Self { data: Default::default(), num_events: Arc::new(AtomicUsize::new(0)), @@ -42,13 +44,13 @@ impl RecordingConnector { } } -impl RecordingConnector { +impl RecordingClient { /// Create a new recording connection from a connection - pub fn new(underlying_connector: SharedHttpConnector) -> Self { + pub fn new(underlying_connector: impl IntoShared) -> Self { Self { data: Default::default(), num_events: Arc::new(AtomicUsize::new(0)), - inner: underlying_connector, + inner: underlying_connector.into_shared(), } } @@ -141,7 +143,7 @@ fn record_body( }) } -impl HttpConnector for RecordingConnector { +impl HttpConnector for RecordingClient { fn call(&self, mut request: HttpRequest) -> HttpConnectorFuture { let event_id = self.next_id(); // A request has three phases: @@ -200,3 +202,13 @@ impl HttpConnector for RecordingConnector { HttpConnectorFuture::new(fut) } } + +impl HttpClient for RecordingClient { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/replay.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs similarity index 91% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/replay.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs index 4388e939bed..f9386e624d0 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/dvr/replay.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/dvr/replay.rs @@ -7,14 +7,19 @@ use super::{Action, ConnectionId, Direction, Event, NetworkTraffic}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; use aws_smithy_protocol_test::MediaType; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, +}; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use bytes::{Bytes, BytesMut}; use http::{Request, Version}; use http_body::Body; use std::collections::{HashMap, VecDeque}; use std::error::Error; +use std::fmt; use std::ops::DerefMut; use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -46,16 +51,25 @@ impl Waitable { } } -/// Replay traffic recorded by a [`RecordingConnector`](super::RecordingConnector) -#[derive(Clone, Debug)] -pub struct ReplayingConnector { +/// Replay traffic recorded by a [`RecordingClient`](super::RecordingClient) +#[derive(Clone)] +pub struct ReplayingClient { live_events: Arc>>>, verifiable_events: Arc>>, num_events: Arc, recorded_requests: Arc>>>>, } -impl ReplayingConnector { +// Ideally, this would just derive Debug, but that makes the tests in aws-config think they found AWS secrets +// when really it's just the test response data they're seeing from the Debug impl of this client. +// This is just a quick workaround. A better fix can be considered later. +impl fmt::Debug for ReplayingClient { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("test_util::dvr::ReplayingClient") + } +} + +impl ReplayingClient { fn next_id(&self) -> ConnectionId { ConnectionId(self.num_events.fetch_add(1, Ordering::Relaxed)) } @@ -204,7 +218,7 @@ impl ReplayingConnector { .collect(); let verifiable_events = Arc::new(verifiable_events); - ReplayingConnector { + ReplayingClient { live_events: Arc::new(Mutex::new(event_map)), num_events: Arc::new(AtomicUsize::new(0)), recorded_requests: Default::default(), @@ -263,7 +277,7 @@ fn convert_version(version: &str) -> Version { } } -impl HttpConnector for ReplayingConnector { +impl HttpConnector for ReplayingClient { fn call(&self, mut request: HttpRequest) -> HttpConnectorFuture { let event_id = self.next_id(); tracing::debug!("received event {}: {request:?}", event_id.0); @@ -349,3 +363,13 @@ impl HttpConnector for ReplayingConnector { HttpConnectorFuture::new(fut) } } + +impl HttpClient for ReplayingClient { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/event.rs similarity index 67% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/event.rs index 2150235c034..a0448acab5b 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/event.rs @@ -7,12 +7,15 @@ use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; -use aws_smithy_runtime_api::client::connectors::{HttpConnector, HttpConnectorFuture}; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, +}; use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse}; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use http::header::{HeaderName, CONTENT_TYPE}; -use std::fmt::Debug; use std::ops::Deref; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; use std::time::Duration; type ConnectionEvents = Vec; @@ -112,26 +115,68 @@ impl ValidateRequest { } } -/// Request/response event-driven connector for use in tests. +/// Request/response event-driven client for use in tests. /// /// A basic test connection. It will: /// - Respond to requests with a preloaded series of responses /// - Record requests for future examination -#[derive(Debug, Clone)] -pub struct EventConnector { +#[derive(Clone, Debug)] +pub struct EventClient { data: Arc>, requests: Arc>>, sleep_impl: SharedAsyncSleep, } -impl EventConnector { +impl EventClient { /// Creates a new event connector. - pub fn new(mut data: ConnectionEvents, sleep_impl: impl Into) -> Self { + pub fn new(mut data: ConnectionEvents, sleep_impl: impl IntoShared) -> Self { data.reverse(); - EventConnector { + EventClient { data: Arc::new(Mutex::new(data)), requests: Default::default(), - sleep_impl: sleep_impl.into(), + sleep_impl: sleep_impl.into_shared(), + } + } + + /// Returns an iterator over the actual requests that were made. + pub fn actual_requests(&self) -> impl Iterator + '_ { + // The iterator trait doesn't allow us to specify a lifetime on `self` in the `next()` method, + // so we have to do some unsafe code in order to actually implement this iterator without + // angering the borrow checker. + struct Iter<'a> { + // We store an exclusive lock to the data so that the data is completely immutable + _guard: MutexGuard<'a, Vec>, + // We store a pointer into the immutable data for accessing it later + values: *const ValidateRequest, + len: usize, + next_index: usize, + } + impl<'a> Iterator for Iter<'a> { + type Item = &'a HttpRequest; + + fn next(&mut self) -> Option { + // Safety: check the next index is in bounds + if self.next_index >= self.len { + None + } else { + // Safety: It is OK to offset into the pointer and dereference since we did a bounds check. + // It is OK to assign lifetime 'a to the reference since we hold the mutex guard for all of lifetime 'a. + let next = unsafe { + let offset = self.values.add(self.next_index); + &*offset + }; + self.next_index += 1; + Some(&next.actual) + } + } + } + + let guard = self.requests.lock().unwrap(); + Iter { + values: guard.as_ptr(), + len: guard.len(), + _guard: guard, + next_index: 0, } } @@ -162,7 +207,7 @@ impl EventConnector { } } -impl HttpConnector for EventConnector { +impl HttpConnector for EventClient { fn call(&self, request: HttpRequest) -> HttpConnectorFuture { let (res, simulated_latency) = if let Some(event) = self.data.lock().unwrap().pop() { self.requests.lock().unwrap().push(ValidateRequest { @@ -173,7 +218,10 @@ impl HttpConnector for EventConnector { (Ok(event.res.map(SdkBody::from)), event.latency) } else { ( - Err(ConnectorError::other("No more data".into(), None)), + Err(ConnectorError::other( + "EventClient: no more test data available to respond with".into(), + None, + )), Duration::from_secs(0), ) }; @@ -185,3 +233,13 @@ impl HttpConnector for EventConnector { }) } } + +impl HttpClient for EventClient { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/infallible.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/infallible.rs similarity index 51% rename from rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/infallible.rs rename to rust-runtime/aws-smithy-runtime/src/client/http/test_util/infallible.rs index b311464774f..491f26c0e2f 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/infallible.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/infallible.rs @@ -5,32 +5,35 @@ use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; -use aws_smithy_runtime_api::client::connectors::{ - HttpConnector, HttpConnectorFuture, SharedHttpConnector, +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpClient, + SharedHttpConnector, }; use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; use std::fmt; use std::sync::Arc; -/// Create a [`SharedHttpConnector`] from `Fn(http:Request) -> http::Response` +/// Create a [`SharedHttpClient`] from `Fn(http:Request) -> http::Response` /// /// # Examples /// /// ```rust -/// use aws_smithy_runtime::client::connectors::test_util::infallible_connection_fn; -/// let connector = infallible_connection_fn(|_req| http::Response::builder().status(200).body("OK!").unwrap()); +/// use aws_smithy_runtime::client::http::test_util::infallible_client_fn; +/// let http_client = infallible_client_fn(|_req| http::Response::builder().status(200).body("OK!").unwrap()); /// ``` -pub fn infallible_connection_fn( +pub fn infallible_client_fn( f: impl Fn(http::Request) -> http::Response + Send + Sync + 'static, -) -> SharedHttpConnector +) -> SharedHttpClient where B: Into, { - SharedHttpConnector::new(InfallibleConnectorFn::new(f)) + InfallibleClientFn::new(f).into_shared() } #[derive(Clone)] -struct InfallibleConnectorFn { +struct InfallibleClientFn { #[allow(clippy::type_complexity)] response: Arc< dyn Fn(http::Request) -> Result, ConnectorError> @@ -39,13 +42,13 @@ struct InfallibleConnectorFn { >, } -impl fmt::Debug for InfallibleConnectorFn { +impl fmt::Debug for InfallibleClientFn { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("InfallibleConnectorFn").finish() + f.debug_struct("InfallibleClientFn").finish() } } -impl InfallibleConnectorFn { +impl InfallibleClientFn { fn new>( f: impl Fn(http::Request) -> http::Response + Send + Sync + 'static, ) -> Self { @@ -55,8 +58,18 @@ impl InfallibleConnectorFn { } } -impl HttpConnector for InfallibleConnectorFn { +impl HttpConnector for InfallibleClientFn { fn call(&self, request: HttpRequest) -> HttpConnectorFuture { HttpConnectorFuture::ready((self.response)(request)) } } + +impl HttpClient for InfallibleClientFn { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/never.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/never.rs new file mode 100644 index 00000000000..939cf2926d4 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/never.rs @@ -0,0 +1,176 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Test connectors that never return data + +use aws_smithy_async::future::never::Never; +use aws_smithy_runtime_api::client::http::{ + HttpClient, HttpConnector, HttpConnectorFuture, HttpConnectorSettings, SharedHttpConnector, +}; +use aws_smithy_runtime_api::client::orchestrator::HttpRequest; +use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; +use aws_smithy_runtime_api::shared::IntoShared; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +/// A client that will never respond. +/// +/// Returned futures will return `Pending` forever +#[derive(Clone, Debug, Default)] +pub struct NeverClient { + invocations: Arc, +} + +impl NeverClient { + /// Create a new never connector. + pub fn new() -> Self { + Default::default() + } + + /// Returns the number of invocations made to this connector. + pub fn num_calls(&self) -> usize { + self.invocations.load(Ordering::SeqCst) + } +} + +impl HttpConnector for NeverClient { + fn call(&self, _request: HttpRequest) -> HttpConnectorFuture { + self.invocations.fetch_add(1, Ordering::SeqCst); + HttpConnectorFuture::new(async move { + Never::new().await; + unreachable!() + }) + } +} + +impl HttpClient for NeverClient { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} + +/// A TCP connector that never connects. +// In the future, this can be available for multiple hyper version feature flags, with the impls gated between individual features +#[cfg(feature = "connector-hyper-0-14-x")] +#[derive(Clone, Debug, Default)] +pub struct NeverTcpConnector; + +#[cfg(feature = "connector-hyper-0-14-x")] +impl NeverTcpConnector { + /// Creates a new `NeverTcpConnector`. + pub fn new() -> Self { + Self + } +} + +#[cfg(feature = "connector-hyper-0-14-x")] +impl hyper::service::Service for NeverTcpConnector { + type Response = stream::NeverStream; + type Error = aws_smithy_runtime_api::box_error::BoxError; + type Future = std::pin::Pin< + Box> + Send + Sync>, + >; + + fn poll_ready( + &mut self, + _: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + fn call(&mut self, _: http::Uri) -> Self::Future { + Box::pin(async { + Never::new().await; + unreachable!() + }) + } +} + +#[cfg(feature = "connector-hyper-0-14-x")] +mod stream { + use hyper::client::connect::{Connected, Connection}; + use std::io::Error; + use std::pin::Pin; + use std::task::{Context, Poll}; + use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + + /// A stream type that appeases hyper's trait bounds for a TCP connector + #[non_exhaustive] + #[derive(Debug, Default)] + pub struct NeverStream; + + impl Connection for NeverStream { + fn connected(&self) -> Connected { + unreachable!() + } + } + + impl AsyncRead for NeverStream { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &mut ReadBuf<'_>, + ) -> Poll> { + unreachable!() + } + } + + impl AsyncWrite for NeverStream { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &[u8], + ) -> Poll> { + unreachable!() + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + unreachable!() + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + unreachable!() + } + } +} + +#[cfg(all(test, feature = "connector-hyper-0-14-x"))] +#[tokio::test] +async fn never_tcp_connector_plugs_into_hyper_014() { + use super::*; + use crate::client::http::hyper_014::HyperClientBuilder; + use aws_smithy_async::rt::sleep::TokioSleep; + use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; + use std::time::Duration; + + // it should compile + let client = HyperClientBuilder::new().build(NeverTcpConnector::new()); + let components = RuntimeComponentsBuilder::for_tests() + .with_sleep_impl(Some(TokioSleep::new())) + .build() + .unwrap(); + let http_connector = client.http_connector( + &HttpConnectorSettings::builder() + .connect_timeout(Duration::from_millis(100)) + .build(), + &components, + ); + + let err = http_connector + .call( + http::Request::builder() + .uri("https://example.com/") + .body(SdkBody::empty()) + .unwrap(), + ) + .await + .expect_err("it should time out"); + assert!(dbg!(err).is_timeout()); +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs index 85fdd345647..e66e3b268d0 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs @@ -16,7 +16,7 @@ use aws_smithy_http::body::SdkBody; use aws_smithy_http::byte_stream::ByteStream; use aws_smithy_http::result::SdkError; use aws_smithy_runtime_api::box_error::BoxError; -use aws_smithy_runtime_api::client::connectors::HttpConnector; +use aws_smithy_runtime_api::client::http::{HttpClient, HttpConnector, HttpConnectorSettings}; use aws_smithy_runtime_api::client::interceptors::context::{ Error, Input, InterceptorContext, Output, RewindResult, }; @@ -30,6 +30,7 @@ use aws_smithy_runtime_api::client::ser_de::{ RequestSerializer, ResponseDeserializer, SharedRequestSerializer, SharedResponseDeserializer, }; use aws_smithy_types::config_bag::ConfigBag; +use aws_smithy_types::timeout::TimeoutConfig; use std::mem; use tracing::{debug, debug_span, instrument, trace, Instrument}; @@ -356,10 +357,18 @@ async fn try_attempt( let response = halt_on_err!([ctx] => { let request = ctx.take_request().expect("set during serialization"); trace!(request = ?request, "transmitting request"); - let connector = halt_on_err!([ctx] => runtime_components.http_connector().ok_or_else(|| + let http_client = halt_on_err!([ctx] => runtime_components.http_client().ok_or_else(|| OrchestratorError::other("No HTTP connector was available to send this request. \ Enable the `rustls` crate feature or set a connector to fix this.") )); + let timeout_config = cfg.load::().expect("timeout config must be set"); + let settings = { + let mut builder = HttpConnectorSettings::builder(); + builder.set_connect_timeout(timeout_config.connect_timeout()); + builder.set_read_timeout(timeout_config.read_timeout()); + builder.build() + }; + let connector = http_client.http_connector(&settings, runtime_components); connector.call(request).await.map_err(OrchestratorError::connector) }); trace!(response = ?response, "received response from service"); @@ -442,12 +451,12 @@ mod tests { use aws_smithy_runtime_api::client::auth::{ AuthSchemeOptionResolverParams, SharedAuthSchemeOptionResolver, }; - use aws_smithy_runtime_api::client::connectors::{ - HttpConnector, HttpConnectorFuture, SharedHttpConnector, - }; use aws_smithy_runtime_api::client::endpoint::{ EndpointResolverParams, SharedEndpointResolver, }; + use aws_smithy_runtime_api::client::http::{ + http_client_fn, HttpConnector, HttpConnectorFuture, + }; use aws_smithy_runtime_api::client::interceptors::context::{ AfterDeserializationInterceptorContextRef, BeforeDeserializationInterceptorContextMut, BeforeDeserializationInterceptorContextRef, BeforeSerializationInterceptorContextMut, @@ -460,6 +469,7 @@ mod tests { use aws_smithy_runtime_api::client::retries::SharedRetryStrategy; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, RuntimePlugins}; + use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::config_bag::{ConfigBag, FrozenLayer, Layer}; use std::borrow::Cow; use std::sync::atomic::{AtomicBool, Ordering}; @@ -515,7 +525,9 @@ mod tests { .with_endpoint_resolver(Some(SharedEndpointResolver::new( StaticUriEndpointResolver::http_localhost(8080), ))) - .with_http_connector(Some(SharedHttpConnector::new(OkConnector::new()))) + .with_http_client(Some(http_client_fn(|_, _| { + OkConnector::new().into_shared() + }))) .with_auth_scheme_option_resolver(Some(SharedAuthSchemeOptionResolver::new( StaticAuthSchemeOptionResolver::new(vec![NO_AUTH_SCHEME_ID]), ))), @@ -530,6 +542,7 @@ mod tests { layer.store_put(EndpointResolverParams::new("dontcare")); layer.store_put(SharedRequestSerializer::new(new_request_serializer())); layer.store_put(SharedResponseDeserializer::new(new_response_deserializer())); + layer.store_put(TimeoutConfig::builder().build()); Some(layer.freeze()) } diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs index ac61dc72228..c7638ef5d81 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs @@ -4,6 +4,7 @@ */ use crate::client::auth::no_auth::{NoAuthScheme, NO_AUTH_SCHEME_ID}; +use crate::client::http::default_http_client_plugin; use crate::client::identity::no_auth::NoAuthIdentityResolver; use crate::client::orchestrator::endpoints::StaticUriEndpointResolver; use crate::client::retries::strategy::{NeverRetryStrategy, StandardRetryStrategy}; @@ -15,8 +16,8 @@ use aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptio use aws_smithy_runtime_api::client::auth::{ AuthSchemeOptionResolverParams, SharedAuthScheme, SharedAuthSchemeOptionResolver, }; -use aws_smithy_runtime_api::client::connectors::SharedHttpConnector; use aws_smithy_runtime_api::client::endpoint::{EndpointResolverParams, SharedEndpointResolver}; +use aws_smithy_runtime_api::client::http::SharedHttpClient; use aws_smithy_runtime_api::client::identity::SharedIdentityResolver; use aws_smithy_runtime_api::client::interceptors::context::{Error, Input, Output}; use aws_smithy_runtime_api::client::interceptors::SharedInterceptor; @@ -33,6 +34,7 @@ use aws_smithy_runtime_api::client::ser_de::{ use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::config_bag::{ConfigBag, Layer}; use aws_smithy_types::retry::RetryConfig; +use aws_smithy_types::timeout::TimeoutConfig; use std::borrow::Cow; use std::fmt; use std::marker::PhantomData; @@ -189,8 +191,8 @@ impl OperationBuilder { self } - pub fn http_connector(mut self, connector: impl IntoShared) -> Self { - self.runtime_components.set_http_connector(Some(connector)); + pub fn http_client(mut self, connector: impl IntoShared) -> Self { + self.runtime_components.set_http_client(Some(connector)); self } @@ -223,6 +225,11 @@ impl OperationBuilder { self } + pub fn timeout_config(mut self, timeout_config: TimeoutConfig) -> Self { + self.config.store_put(timeout_config); + self + } + pub fn no_auth(mut self) -> Self { self.config .store_put(AuthSchemeOptionResolverParams::new(())); @@ -239,13 +246,15 @@ impl OperationBuilder { self } - pub fn sleep_impl(mut self, async_sleep: SharedAsyncSleep) -> Self { - self.runtime_components.set_sleep_impl(Some(async_sleep)); + pub fn sleep_impl(mut self, async_sleep: impl IntoShared) -> Self { + self.runtime_components + .set_sleep_impl(Some(async_sleep.into_shared())); self } - pub fn time_source(mut self, time_source: SharedTimeSource) -> Self { - self.runtime_components.set_time_source(Some(time_source)); + pub fn time_source(mut self, time_source: impl IntoShared) -> Self { + self.runtime_components + .set_time_source(Some(time_source.into_shared())); self } @@ -306,11 +315,13 @@ impl OperationBuilder { pub fn build(self) -> Operation { let service_name = self.service_name.expect("service_name required"); let operation_name = self.operation_name.expect("operation_name required"); - let mut runtime_plugins = RuntimePlugins::new().with_client_plugin( - StaticRuntimePlugin::new() - .with_config(self.config.freeze()) - .with_runtime_components(self.runtime_components), - ); + let mut runtime_plugins = RuntimePlugins::new() + .with_client_plugin(default_http_client_plugin()) + .with_client_plugin( + StaticRuntimePlugin::new() + .with_config(self.config.freeze()) + .with_runtime_components(self.runtime_components), + ); for runtime_plugin in self.runtime_plugins { runtime_plugins = runtime_plugins.with_client_plugin(runtime_plugin); } @@ -323,8 +334,8 @@ impl OperationBuilder { .expect("the runtime plugins should succeed"); assert!( - components.http_connector().is_some(), - "a http_connector is required" + components.http_client().is_some(), + "a http_client is required" ); assert!( components.endpoint_resolver().is_some(), @@ -346,6 +357,10 @@ impl OperationBuilder { config.load::().is_some(), "endpoint resolver params are required" ); + assert!( + config.load::().is_some(), + "timeout config is required" + ); } Operation { @@ -360,7 +375,7 @@ impl OperationBuilder { #[cfg(all(test, feature = "test-util"))] mod tests { use super::*; - use crate::client::connectors::test_util::{capture_request, ConnectionEvent, EventConnector}; + use crate::client::http::test_util::{capture_request, ConnectionEvent, EventClient}; use crate::client::retries::classifier::HttpStatusCodeClassifier; use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep}; use aws_smithy_http::body::SdkBody; @@ -378,10 +393,11 @@ mod tests { let operation = Operation::builder() .service_name("test") .operation_name("test") - .http_connector(SharedHttpConnector::new(connector)) + .http_client(connector) .endpoint_url("http://localhost:1234") .no_auth() .no_retry() + .timeout_config(TimeoutConfig::disabled()) .serializer(|input: String| { Ok(http::Request::builder() .body(SdkBody::from(input.as_bytes())) @@ -408,7 +424,7 @@ mod tests { #[tokio::test] async fn operation_retries() { - let connector = EventConnector::new( + let connector = EventClient::new( vec![ ConnectionEvent::new( http::Request::builder() @@ -436,13 +452,14 @@ mod tests { let operation = Operation::builder() .service_name("test") .operation_name("test") - .http_connector(SharedHttpConnector::new(connector.clone())) + .http_client(connector.clone()) .endpoint_url("http://localhost:1234") .no_auth() .retry_classifiers( RetryClassifiers::new().with_classifier(HttpStatusCodeClassifier::default()), ) .standard_retry(&RetryConfig::standard()) + .timeout_config(TimeoutConfig::disabled()) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) .serializer(|input: String| { Ok(http::Request::builder() diff --git a/rust-runtime/aws-smithy-runtime/src/client/timeout.rs b/rust-runtime/aws-smithy-runtime/src/client/timeout.rs index c149878dda8..e2e26418ebb 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/timeout.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/timeout.rs @@ -5,7 +5,7 @@ use aws_smithy_async::future::timeout::Timeout; use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep, Sleep}; -use aws_smithy_client::SdkError; +use aws_smithy_http::result::SdkError; use aws_smithy_runtime_api::client::orchestrator::HttpResponse; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; use aws_smithy_types::config_bag::ConfigBag;