From 26a914ece072bba2dd9b5b49003204b70e7666ac Mon Sep 17 00:00:00 2001 From: John DiSanti Date: Fri, 6 Oct 2023 10:32:35 -0700 Subject: [PATCH] Replace `DynConnector` and `HttpConnector` with `HttpClient` (#3011) This PR removes the last usages of `DynConnector` and `HttpConnector` from the `aws_smithy_client` crate with their counterparts in aws-smithy-runtime-api and aws-smithy-runtime. It also introduces `HttpClient` to make HTTP connector selection a smoother process, and adds a runtime plugin that configures a default `HttpClient` so that `Config` doesn't have to do that. The `DnsService` from aws-config is also moved into aws-smithy-runtime and refactored to be consistent with the other configurable traits in the orchestrator since it will likely be used in the future for more than just the ECS credentials provider. ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._ --- CHANGELOG.next.toml | 12 + aws/rust-runtime/aws-config/Cargo.toml | 15 +- .../aws-config/external-types.toml | 12 +- aws/rust-runtime/aws-config/src/connector.rs | 32 -- .../src/default_provider/app_name.rs | 10 +- .../src/default_provider/credentials.rs | 16 +- aws/rust-runtime/aws-config/src/ecs.rs | 241 +++++----- .../src/http_credential_provider.rs | 78 ++-- .../aws-config/src/imds/client.rs | 220 +++++---- .../aws-config/src/imds/client/error.rs | 2 +- .../aws-config/src/imds/credentials.rs | 98 ++-- .../aws-config/src/imds/region.rs | 21 +- aws/rust-runtime/aws-config/src/lib.rs | 139 +++--- .../aws-config/src/profile/credentials.rs | 2 +- .../src/profile/credentials/exec.rs | 4 +- .../aws-config/src/profile/region.rs | 8 +- .../aws-config/src/provider_config.rs | 131 ++---- aws/rust-runtime/aws-config/src/sso.rs | 7 +- aws/rust-runtime/aws-config/src/sts.rs | 3 +- .../aws-config/src/sts/assume_role.rs | 25 +- aws/rust-runtime/aws-config/src/test_case.rs | 55 +-- .../aws-config/src/web_identity_token.rs | 18 +- .../aws-credential-types/external-types.toml | 1 + .../src/cache/lazy_caching.rs | 21 +- aws/rust-runtime/aws-endpoint/Cargo.toml | 1 - aws/rust-runtime/aws-http/Cargo.toml | 1 - aws/rust-runtime/aws-inlineable/Cargo.toml | 4 - .../aws-inlineable/external-types.toml | 17 +- .../aws-inlineable/src/endpoint_discovery.rs | 2 +- .../aws-inlineable/src/glacier_checksums.rs | 227 ---------- .../src/http_body_checksum_middleware.rs | 417 ------------------ aws/rust-runtime/aws-inlineable/src/lib.rs | 7 - .../aws-inlineable/src/s3_request_id.rs | 4 +- aws/rust-runtime/aws-runtime/additional-ci | 12 + .../aws-runtime/src/invocation_id.rs | 3 +- aws/rust-runtime/aws-types/Cargo.toml | 5 +- .../aws-types/external-types.toml | 9 +- aws/rust-runtime/aws-types/src/lib.rs | 2 - aws/rust-runtime/aws-types/src/sdk_config.rs | 109 +++-- .../AwsCustomizableOperationDecorator.kt | 3 +- .../rustsdk/AwsFluentClientDecorator.kt | 5 +- .../amazon/smithy/rustsdk/CredentialCaches.kt | 2 +- .../rustsdk/IntegrationTestDependencies.kt | 5 +- .../smithy/rustsdk/SdkConfigDecorator.kt | 2 +- .../endpoints/OperationInputTestGenerator.kt | 13 +- .../rustsdk/EndpointBuiltInsDecoratorTest.kt | 27 +- .../rustsdk/EndpointsCredentialsTest.kt | 8 +- .../rustsdk/InvocationIdDecoratorTest.kt | 4 +- aws/sdk/integration-tests/dynamodb/Cargo.toml | 1 - .../dynamodb/tests/endpoints.rs | 5 +- .../dynamodb/tests/movies.rs | 390 ++++++++-------- .../dynamodb/tests/paginators.rs | 74 ++-- .../retries-with-client-rate-limiting.rs | 48 +- .../dynamodb/tests/shared-config.rs | 5 +- .../dynamodb/tests/timeouts.rs | 14 +- aws/sdk/integration-tests/ec2/Cargo.toml | 5 +- .../integration-tests/ec2/tests/paginators.rs | 25 +- aws/sdk/integration-tests/glacier/Cargo.toml | 2 +- .../glacier/tests/custom-headers.rs | 14 +- aws/sdk/integration-tests/iam/Cargo.toml | 2 +- .../iam/tests/resolve-global-endpoint.rs | 6 +- aws/sdk/integration-tests/kms/Cargo.toml | 2 +- .../kms/tests/integration.rs | 36 +- .../kms/tests/retryable_errors.rs | 6 +- aws/sdk/integration-tests/lambda/Cargo.toml | 4 +- .../lambda/tests/request_id.rs | 6 +- .../tests/client-construction.rs | 10 +- aws/sdk/integration-tests/polly/Cargo.toml | 1 - .../integration-tests/qldbsession/Cargo.toml | 3 +- .../qldbsession/tests/integration.rs | 12 +- aws/sdk/integration-tests/s3/Cargo.toml | 3 +- .../s3/tests/alternative-async-runtime.rs | 12 +- .../s3/tests/bucket-required.rs | 6 +- .../integration-tests/s3/tests/checksums.rs | 30 +- .../s3/tests/config-override.rs | 6 +- .../s3/tests/customizable-operation.rs | 7 +- .../integration-tests/s3/tests/endpoints.rs | 6 +- .../s3/tests/ignore-invalid-xml-body-root.rs | 12 +- .../s3/tests/interceptors.rs | 11 +- .../s3/tests/naughty-string-metadata.rs | 6 +- aws/sdk/integration-tests/s3/tests/no_auth.rs | 31 +- .../s3/tests/normalize-uri-path.rs | 6 +- .../query-strings-are-correctly-encoded.rs | 6 +- .../integration-tests/s3/tests/reconnects.rs | 14 +- .../s3/tests/recursion-detection.rs | 6 +- .../integration-tests/s3/tests/request_id.rs | 22 +- .../s3/tests/request_information_headers.rs | 18 +- .../s3/tests/required-query-params.rs | 10 +- .../s3/tests/select-object-content.rs | 6 +- ...erride.rs => service_timeout_overrides.rs} | 41 -- .../integration-tests/s3/tests/signing-it.rs | 10 +- .../s3/tests/status-200-errors.rs | 7 +- .../integration-tests/s3/tests/timeouts.rs | 4 +- .../s3/tests/user-agent-app-name.rs | 6 +- .../integration-tests/s3control/Cargo.toml | 3 +- .../s3control/tests/signing-it.rs | 10 +- aws/sdk/integration-tests/sts/Cargo.toml | 2 +- .../integration-tests/sts/tests/signing-it.rs | 18 +- .../timestreamquery/Cargo.toml | 1 - .../timestreamquery/tests/endpoint_disco.rs | 30 +- .../transcribestreaming/Cargo.toml | 2 +- .../transcribestreaming/tests/test.rs | 8 +- .../integration-tests/webassembly/Cargo.toml | 3 +- .../webassembly/src/adapter/http_client.rs | 29 -- .../webassembly/src/adapter/mod.rs | 44 -- .../webassembly/src/default_config.rs | 5 +- .../integration-tests/webassembly/src/http.rs | 62 +++ .../integration-tests/webassembly/src/lib.rs | 2 +- aws/sdk/sdk-external-types.toml | 12 +- .../ConnectionPoisoningConfigCustomization.kt | 2 +- .../HttpConnectorConfigDecorator.kt | 169 +++---- .../ResiliencyConfigCustomization.kt | 6 +- .../customizations/TimeSourceCustomization.kt | 6 +- .../customize/RequiredCustomizations.kt | 8 +- .../client/FluentClientDecorator.kt | 2 +- .../client/FluentClientGenerator.kt | 3 + .../protocol/ProtocolTestGenerator.kt | 15 +- .../customizations/HttpAuthDecoratorTest.kt | 155 ++++--- .../MetadataCustomizationTest.kt | 4 +- .../SensitiveOutputDecoratorTest.kt | 8 +- .../smithy/endpoint/EndpointsDecoratorTest.kt | 15 +- ...onfigOverrideRuntimePluginGeneratorTest.kt | 16 +- .../generators/EndpointTraitBindingsTest.kt | 10 +- .../CustomizableOperationGeneratorTest.kt | 11 +- .../client/FluentClientGeneratorTest.kt | 23 +- .../protocols/AwsQueryCompatibleTest.kt | 14 +- .../codegen/core/rustlang/CargoDependency.kt | 1 + .../rust/codegen/core/smithy/RuntimeType.kt | 4 +- examples/pokemon-service-common/Cargo.toml | 4 +- examples/pokemon-service-common/src/lib.rs | 6 +- .../tests/plugins_execution_order.rs | 6 +- examples/pokemon-service-tls/Cargo.toml | 2 +- .../pokemon-service-tls/tests/common/mod.rs | 6 +- .../python/pokemon-service-test/Cargo.toml | 2 +- .../pokemon-service-test/tests/helpers.rs | 6 +- rust-runtime/aws-smithy-async/Cargo.toml | 2 +- .../aws-smithy-async/src/future/mod.rs | 6 + .../aws-smithy-async/src/test_util.rs | 8 +- .../aws-smithy-runtime-api/src/client.rs | 4 +- .../src/client/connectors.rs | 122 ----- .../aws-smithy-runtime-api/src/client/dns.rs | 107 +++++ .../aws-smithy-runtime-api/src/client/http.rs | 308 +++++++++++++ .../src/client/orchestrator.rs | 4 +- .../src/client/runtime_components.rs | 113 +++-- .../src/client/runtime_plugin.rs | 39 +- .../aws-smithy-runtime-api/src/shared.rs | 23 +- rust-runtime/aws-smithy-runtime/Cargo.toml | 10 +- .../aws-smithy-runtime/external-types.toml | 6 +- rust-runtime/aws-smithy-runtime/src/client.rs | 10 +- .../src/client/connectors.rs | 52 --- .../src/client/connectors/test_util.rs | 41 -- .../connectors/test_util/event_connector.rs | 187 -------- .../src/client/connectors/test_util/never.rs | 42 -- .../aws-smithy-runtime/src/client/dns.rs | 48 ++ .../aws-smithy-runtime/src/client/http.rs | 37 ++ .../connection_poisoning.rs | 0 .../hyper_connector.rs => http/hyper_014.rs} | 269 +++++++++-- .../src/client/http/test_util.rs | 58 +++ .../test_util/capture_request.rs | 20 +- .../{connectors => http}/test_util/dvr.rs | 12 +- .../test_util/dvr/record.rs | 36 +- .../test_util/dvr/replay.rs | 38 +- .../test_util/infallible.rs | 39 +- .../src/client/http/test_util/never.rs | 176 ++++++++ .../src/client/http/test_util/replay.rs | 260 +++++++++++ .../src/client/http/test_util/wire.rs | 353 +++++++++++++++ .../src/client/orchestrator.rs | 29 +- .../src/client/orchestrator/operation.rs | 113 +++-- .../aws-smithy-runtime/src/client/timeout.rs | 2 +- tools/ci-resources/tls-stub/Cargo.toml | 2 +- tools/ci-resources/tls-stub/src/main.rs | 7 +- 171 files changed, 3339 insertions(+), 2996 deletions(-) delete mode 100644 aws/rust-runtime/aws-config/src/connector.rs delete mode 100644 aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs delete mode 100644 aws/rust-runtime/aws-inlineable/src/http_body_checksum_middleware.rs create mode 100755 aws/rust-runtime/aws-runtime/additional-ci rename aws/sdk/integration-tests/s3/tests/{make-connector-override.rs => service_timeout_overrides.rs} (58%) delete mode 100644 aws/sdk/integration-tests/webassembly/src/adapter/http_client.rs delete mode 100644 aws/sdk/integration-tests/webassembly/src/adapter/mod.rs create mode 100644 aws/sdk/integration-tests/webassembly/src/http.rs delete mode 100644 rust-runtime/aws-smithy-runtime-api/src/client/connectors.rs create mode 100644 rust-runtime/aws-smithy-runtime-api/src/client/dns.rs create mode 100644 rust-runtime/aws-smithy-runtime-api/src/client/http.rs delete mode 100644 rust-runtime/aws-smithy-runtime/src/client/connectors.rs delete mode 100644 rust-runtime/aws-smithy-runtime/src/client/connectors/test_util.rs delete mode 100644 rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs delete mode 100644 rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/never.rs create mode 100644 rust-runtime/aws-smithy-runtime/src/client/dns.rs create mode 100644 rust-runtime/aws-smithy-runtime/src/client/http.rs rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/connection_poisoning.rs (100%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors/hyper_connector.rs => http/hyper_014.rs} (76%) create mode 100644 rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/capture_request.rs (83%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/dvr.rs (94%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/dvr/record.rs (87%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/dvr/replay.rs (91%) rename rust-runtime/aws-smithy-runtime/src/client/{connectors => http}/test_util/infallible.rs (51%) create mode 100644 rust-runtime/aws-smithy-runtime/src/client/http/test_util/never.rs create mode 100644 rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs create mode 100644 rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index d9ed616628..53ee76961a 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -11,6 +11,18 @@ # meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client | server | all"} # author = "rcoh" +[[smithy-rs]] +message = "HTTP connector configuration has changed significantly. See the [upgrade guidance](https://github.com/awslabs/smithy-rs/discussions/3022) for details." +references = ["smithy-rs#3011"] +meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } +author = "jdisanti" + +[[aws-sdk-rust]] +message = "HTTP connector configuration has changed significantly. See the [upgrade guidance](https://github.com/awslabs/smithy-rs/discussions/3022) for details." +references = ["smithy-rs#3011"] +meta = { "breaking" = true, "tada" = false, "bug" = false } +author = "jdisanti" + [[smithy-rs]] message = "It's now possible to nest runtime components with the `RuntimePlugin` trait. A `current_components` argument was added to the `runtime_components` method so that components configured from previous runtime plugins can be referenced in the current runtime plugin. Ordering of runtime plugins was also introduced via a new `RuntimePlugin::order` method." references = ["smithy-rs#2909"] diff --git a/aws/rust-runtime/aws-config/Cargo.toml b/aws/rust-runtime/aws-config/Cargo.toml index 2e9d00f08a..3c43472b1d 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 } @@ -51,7 +47,7 @@ hex = { version = "0.4.3", optional = true } zeroize = { version = "1", optional = true } [dev-dependencies] -aws-smithy-runtime = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } +aws-smithy-runtime = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x", "test-util"] } futures-util = { version = "0.3.16", default-features = false } tracing-test = "0.2.1" tracing-subscriber = { version = "0.3.16", features = ["fmt", "json"] } @@ -66,11 +62,10 @@ 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"] } -aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] } +aws-smithy-async = { path = "../../sdk/build/aws-sdk/sdk/aws-smithy-async", features = ["rt-tokio", "test-util"] } [package.metadata.docs.rs] all-features = true diff --git a/aws/rust-runtime/aws-config/external-types.toml b/aws/rust-runtime/aws-config/external-types.toml index b90a84885c..450f0b5c9d 100644 --- a/aws/rust-runtime/aws-config/external-types.toml +++ b/aws/rust-runtime/aws-config/external-types.toml @@ -9,17 +9,17 @@ 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::DnsResolver", + "aws_smithy_runtime_api::client::dns::SharedDnsResolver", + "aws_smithy_runtime_api::client::http::HttpClient", + "aws_smithy_runtime_api::client::http::SharedHttpClient", "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 33c820261f..0000000000 --- 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 041d0f8261..55e87f82bc 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 30fd0101ac..13636e7d14 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() { @@ -335,14 +335,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 f5ca10e54c..ef63015e69 100644 --- a/aws/rust-runtime/aws-config/src/ecs.rs +++ b/aws/rust-runtime/aws-config/src/ecs.rs @@ -46,24 +46,21 @@ //! } //! ``` -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::client::dns::{DnsResolver, ResolveDnsError, 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 +140,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 +174,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 +258,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 +272,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 DnsResolver + 'static) -> Self { + self.dns = Some(dns.into_shared()); self } @@ -319,9 +316,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 +329,7 @@ enum InvalidFullUriErrorKind { NotLoopback, /// DNS lookup failed when attempting to resolve the host to an IP Address for validation. - DnsLookupFailed(io::Error), + DnsLookupFailed(ResolveDnsError), } /// Invalid Full URI @@ -358,7 +355,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 +365,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 +377,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 +385,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 +398,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(|err| InvalidFullUriErrorKind::DnsLookupFailed(ResolveDnsError::new(err)))? + .iter() .all(|addr| { if !addr.is_loopback() { tracing::warn!( @@ -427,87 +420,49 @@ 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::{ReplayEvent, StaticReplayClient}; + use aws_smithy_runtime_api::client::dns::DnsFuture; + use aws_smithy_runtime_api::client::http::HttpClient; + use aws_smithy_runtime_api::shared::IntoShared; + use aws_types::os_shim_internal::Env; + 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 HttpClient + 'static) -> 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 +475,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 +501,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 +511,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 +525,7 @@ mod test { matches!( no_dns_error, InvalidFullUriError { - kind: InvalidFullUriErrorKind::NoDnsService + kind: InvalidFullUriErrorKind::NoDnsResolver } ), "expected no dns service, got: {}", @@ -603,12 +557,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 +572,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 +633,37 @@ mod test { ("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/credentials"), ("AWS_CONTAINER_AUTHORIZATION_TOKEN", "Basic password"), ]); - let connector = TestConnection::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( creds_request("http://169.254.170.2/credentials", Some("Basic password")), ok_creds_response(), )]); - let provider = provider(env, DynConnector::new(connector.clone())); + 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![ - ( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( creds_request("http://169.254.170.2/credentials", None), http::Response::builder() .status(500) .body(SdkBody::empty()) .unwrap(), ), - ( + ReplayEvent::new( creds_request("http://169.254.170.2/credentials", None), ok_creds_response(), ), ]); 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 +674,17 @@ 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![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( creds_request("http://169.254.170.2/credentials", None), ok_creds_response(), )]); - let provider = provider(env, DynConnector::new(connector.clone())); + 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 +693,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 +714,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 +751,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 2bb2cc25c7..0a39adf7b9 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,18 +103,24 @@ impl Builder { let mut builder = Operation::builder() .service_name("HttpCredentialProvider") .operation_name("LoadCredentials") - .http_connector(SharedHttpConnector::new(DynConnectorAdapter::new( - connector, - ))) .with_connection_poisoning() .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) @@ -221,24 +218,23 @@ 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_http::body::SdkBody; - use aws_smithy_runtime_api::client::orchestrator::HttpRequest; + use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use http::{Request, Response, Uri}; use std::time::SystemTime; async fn provide_creds( - connector: TestConnection, + http_client: StaticReplayClient, ) -> Result { - let provider_config = ProviderConfig::default().with_http_connector(connector.clone()); + 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() -> ReplayEvent { + ReplayEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -259,8 +255,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 = StaticReplayClient::new(vec![successful_req_resp()]); + 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()); @@ -268,13 +264,13 @@ 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![ - ( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -286,15 +282,15 @@ mod test { ), successful_req_resp(), ]); - let creds = provide_creds(connector.clone()).await.expect("success"); + 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![ - ( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -306,14 +302,14 @@ mod test { ), successful_req_resp(), ]); - let creds = provide_creds(connector.clone()).await.expect("success"); + 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 = StaticReplayClient::new(vec![ReplayEvent::new( Request::builder() .uri(Uri::from_static("http://localhost:1234/some-creds")) .body(SdkBody::empty()) @@ -325,13 +321,13 @@ mod test { )) .unwrap(), )]); - let err = provide_creds(connector.clone()) + 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 32aa772023..be93a1fe43 100644 --- a/aws/rust-runtime/aws-config/src/imds/client.rs +++ b/aws/rust-runtime/aws-config/src/imds/client.rs @@ -7,36 +7,24 @@ //! //! 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; use crate::PKG_VERSION; 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}; @@ -244,12 +232,10 @@ struct ImdsCommonRuntimePlugin { impl ImdsCommonRuntimePlugin { fn new( - connector: DynConnector, + config: &ProviderConfig, endpoint_resolver: ImdsEndpointResolver, retry_config: &RetryConfig, timeout_config: TimeoutConfig, - time_source: SharedTimeSource, - sleep_impl: Option, ) -> Self { let mut layer = Layer::new("ImdsCommonRuntimePlugin"); layer.store_put(AuthSchemeOptionResolverParams::new(())); @@ -261,19 +247,15 @@ 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(config.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_time_source(Some(time_source)) - .with_sleep_impl(sleep_impl), + .with_retry_strategy(Some(StandardRetryStrategy::new(retry_config))) + .with_time_source(Some(config.time_source())) + .with_sleep_impl(config.sleep_impl()), } } } @@ -427,11 +409,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 +419,10 @@ 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, endpoint_resolver, &retry_config, timeout_config, - config.time_source(), - config.sleep(), )); let operation = Operation::builder() .service_name("imds") @@ -608,16 +583,19 @@ pub(crate) mod test { use crate::imds::client::{Client, EndpointMode, ImdsResponseRetryClassifier}; 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_async::test_util::{instant_time_and_sleep, InstantSleep}; use aws_smithy_http::body::SdkBody; use aws_smithy_http::result::ConnectorError; + use aws_smithy_runtime::client::http::test_util::{ + capture_request, ReplayEvent, StaticReplayClient, + }; 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}; @@ -648,7 +626,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) @@ -657,15 +635,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") @@ -674,67 +652,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_imds_client(http_client: &StaticReplayClient) -> 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(InstantSleep::unlogged()) + .with_http_client(http_client.clone()), ) .build() } + fn mock_imds_client(events: Vec) -> (Client, StaticReplayClient) { + let http_client = StaticReplayClient::new(events); + let client = make_imds_client(&http_client); + (client, http_client) + } + #[tokio::test] async fn client_caches_token() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = mock_imds_client(vec![ + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output"#), ), - ( + ReplayEvent::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) = mock_imds_client(vec![ + ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), ), - ( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output1"#), ), - ( + ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_B), ), - ( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_B), imds_response(r#"test-imds-output2"#), ), @@ -743,9 +725,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)) @@ -755,7 +737,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()); } @@ -763,27 +745,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) = mock_imds_client(vec![ + ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_A), ), // t = 0 - ( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output1"#), ), // t = 400 (no refresh) - ( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_A), imds_response(r#"test-imds-output2"#), ), // t = 550 (within buffer) - ( + ReplayEvent::new( token_request("http://[fd00:ec2::254]", 600), token_response(600, TOKEN_B), ), - ( + ReplayEvent::new( imds_request("http://[fd00:ec2::254]/latest/metadata", TOKEN_B), imds_response(r#"test-imds-output3"#), ), @@ -792,8 +774,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) @@ -806,7 +788,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()); @@ -816,21 +798,23 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn retry_500() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = mock_imds_client(vec![ + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ReplayEvent::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(), ), - ( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response("ok"), ), ]); - let client = make_client(&connection); assert_eq!( "ok", client @@ -839,11 +823,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()); } } @@ -851,21 +835,23 @@ pub(crate) mod test { #[tokio::test] #[traced_test] async fn retry_token_failure() { - let connection = TestConnection::new(vec![ - ( + let (client, http_client) = mock_imds_client(vec![ + ReplayEvent::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(), ), - ( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), imds_response("ok"), ), ]); - let client = make_client(&connection); assert_eq!( "ok", client @@ -874,32 +860,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) = mock_imds_client(vec![ + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(0, TOKEN_A), ), - ( + ReplayEvent::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(), ), - ( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_B), ), - ( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_B), imds_response("ok"), ), ]); - let client = make_client(&connection); assert_eq!( "ok", client @@ -908,21 +896,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) = mock_imds_client(vec![ReplayEvent::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` @@ -945,24 +935,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) = mock_imds_client(vec![ReplayEvent::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) = mock_imds_client(vec![ + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A).map(SdkBody::from), ), - ( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/metadata", TOKEN_A), http::Response::builder() .status(200) @@ -970,10 +959,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 @@ -1043,12 +1031,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 4d32aee012..a97c3961c7 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 3bde8a4510..52cf0bb6af 100644 --- a/aws/rust-runtime/aws-config/src/imds/credentials.rs +++ b/aws/rust-runtime/aws-config/src/imds/credentials.rs @@ -284,13 +284,13 @@ impl ImdsCredentialsProvider { mod test { use super::*; use crate::imds::client::test::{ - imds_request, imds_response, make_client, token_request, token_response, + imds_request, imds_response, make_imds_client, token_request, token_response, }; use crate::provider_config::ProviderConfig; use aws_credential_types::provider::ProvideCredentials; 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::{ReplayEvent, StaticReplayClient}; use std::time::{Duration, UNIX_EPOCH}; use tracing_test::traced_test; @@ -298,51 +298,51 @@ mod test { #[tokio::test] async fn profile_is_not_cached() { - let connection = TestConnection::new(vec![ - ( - token_request("http://169.254.169.254", 21600), - token_response(21600, TOKEN_A), - ), - ( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), - imds_response(r#"profile-name"#), - ), - ( - 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}"), - ), - ( - imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), - imds_response(r#"different-profile"#), - ), - ( - 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}"), - ), - ]); + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( + token_request("http://169.254.169.254", 21600), + token_response(21600, TOKEN_A), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_response(r#"profile-name"#), + ), + ReplayEvent::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}"), + ), + ReplayEvent::new( + imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), + imds_response(r#"different-profile"#), + ), + ReplayEvent::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}"), + ), + ]); let client = ImdsCredentialsProvider::builder() - .imds_client(make_client(&connection)) + .imds_client(make_imds_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 = StaticReplayClient::new(vec![ + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ReplayEvent::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}"), ), @@ -354,8 +354,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 +370,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,16 +378,16 @@ mod test { #[tokio::test] #[traced_test] async fn expired_credentials_should_be_extended() { - let connection = TestConnection::new(vec![ - ( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ReplayEvent::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}"), ), @@ -398,8 +398,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) @@ -410,7 +410,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 +486,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 = StaticReplayClient::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. - ( + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, TOKEN_A), ), - ( + ReplayEvent::new( imds_request("http://169.254.169.254/latest/meta-data/iam/security-credentials/", TOKEN_A), imds_response(r#"profile-name"#), ), - ( + ReplayEvent::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. - ( + ReplayEvent::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(), ), ]); let provider = ImdsCredentialsProvider::builder() - .imds_client(make_client(&connection)) + .imds_client(make_imds_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 072dc97a87..bc19642a64 100644 --- a/aws/rust-runtime/aws-config/src/imds/region.rs +++ b/aws/rust-runtime/aws-config/src/imds/region.rs @@ -110,21 +110,20 @@ mod test { use crate::imds::client::test::{imds_request, imds_response, token_request, token_response}; use crate::imds::region::ImdsRegionProvider; 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::{ReplayEvent, StaticReplayClient}; + use aws_types::region::Region; use tracing_test::traced_test; #[tokio::test] async fn load_region() { - let conn = TestConnection::new(vec![ - ( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( token_request("http://169.254.169.254", 21600), token_response(21600, "token"), ), - ( + ReplayEvent::new( imds_request( "http://169.254.169.254/latest/meta-data/placement/region", "token", @@ -135,8 +134,8 @@ mod test { 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,7 +147,7 @@ mod test { #[traced_test] #[tokio::test] async fn no_region_imds_disabled() { - let conn = TestConnection::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( token_request("http://169.254.169.254", 21600), http::Response::builder() .status(403) @@ -158,8 +157,8 @@ mod test { 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 b39bcf571a..ba1cb953d8 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,26 @@ 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_runtime_api::client::http::HttpClient; + 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 +197,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, @@ -246,6 +243,7 @@ mod loader { } /// Override the timeout config used to build [`SdkConfig`](aws_types::SdkConfig). + /// /// **Note: This only sets timeouts for calls to AWS services.** Timeouts for the credentials /// provider chain are configured separately. /// @@ -270,63 +268,68 @@ mod loader { self } - /// Override the sleep implementation for this [`ConfigLoader`]. The sleep implementation - /// is used to create timeout futures. + /// Override the sleep implementation for this [`ConfigLoader`]. + /// + /// The sleep implementation is used to create timeout futures. + /// You generally won't need to change this unless you're using an async runtime other + /// than Tokio. pub fn sleep_impl(mut self, sleep: impl AsyncSleep + 'static) -> 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 + /// Set the time source used for tasks like signing requests. + /// + /// You generally won't need to change this unless you're compiling for a target + /// that can't provide a default, such as WASM, or unless you're writing a test against + /// the client that needs a fixed time. pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self { - self.time_source = Some(SharedTimeSource::new(time_source)); + 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. + /// Deprecated. Don't use. + #[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn http_connector(self, http_client: impl HttpClient + 'static) -> Self { + self.http_client(http_client) + } + + /// Override the [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) for this [`ConfigLoader`]. /// - /// **Note**: In order to take advantage of late-configured timeout settings, you MUST use - /// [`HttpConnector::ConnectorFn`] - /// when configuring this connector. + /// The HTTP client will be used for both AWS services and credentials providers. + /// + /// 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 HttpClient + 'static) -> Self { + self.http_client = Some(http_client.into_shared()); self } @@ -559,10 +562,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 +582,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); @@ -658,7 +660,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 { @@ -669,9 +671,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); @@ -698,12 +700,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}; @@ -711,10 +714,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() { @@ -730,7 +729,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() @@ -772,7 +771,7 @@ mod loader { fn base_conf() -> ConfigLoader { from_env() .sleep_impl(InstantSleep) - .http_connector(no_traffic_connector()) + .http_client(no_traffic_client()) } #[tokio::test] @@ -809,14 +808,14 @@ 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() .fs(Fs::from_slice(&[])) .env(Env::from_slice(&[])) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .load() .await; config @@ -841,7 +840,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.rs b/aws/rust-runtime/aws-config/src/profile/credentials.rs index f380902600..6469ececbc 100644 --- a/aws/rust-runtime/aws-config/src/profile/credentials.rs +++ b/aws/rust-runtime/aws-config/src/profile/credentials.rs @@ -442,7 +442,7 @@ impl Builder { ProfileFileCredentialsProvider { factory, - sdk_config: conf.client_config("profile file"), + sdk_config: conf.client_config(), provider_config: conf, } } 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 48a8d0c88a..c6108a18f1 100644 --- a/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs +++ b/aws/rust-runtime/aws-config/src/profile/credentials/exec.rs @@ -198,7 +198,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; @@ -222,7 +222,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 3cdcf8f7e4..a293733377 100644 --- a/aws/rust-runtime/aws-config/src/profile/region.rs +++ b/aws/rust-runtime/aws-config/src/profile/region.rs @@ -157,9 +157,9 @@ impl ProvideRegion for ProfileFileRegionProvider { mod test { use crate::profile::ProfileFileRegionProvider; use crate::provider_config::ProviderConfig; - use crate::test_case::no_traffic_connector; - use aws_sdk_sts::config::Region; + use crate::test_case::no_traffic_client; use aws_types::os_shim_internal::{Env, Fs}; + use aws_types::region::Region; use futures_util::FutureExt; use tracing_test::traced_test; @@ -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 ec543a7b66..dd13270bbc 100644 --- a/aws/rust-runtime/aws-config/src/provider_config.rs +++ b/aws/rust-runtime/aws-config/src/provider_config.rs @@ -5,21 +5,19 @@ //! Configuration Options for Credential Providers -use crate::connector::{default_connector, expect_connector}; use crate::profile; use crate::profile::profile_file::ProfileFiles; use crate::profile::{ProfileFileLoadError, ProfileSet}; use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; -use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::erase::DynConnector; +use aws_smithy_async::time::{SharedTimeSource, TimeSource}; +use aws_smithy_runtime_api::client::http::HttpClient; +use aws_smithy_runtime_api::shared::IntoShared; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::retry::RetryConfig; use aws_types::os_shim_internal::{Env, Fs}; -use aws_types::{ - http_connector::{ConnectorSettings, HttpConnector}, - region::Region, - SdkConfig, -}; +use aws_types::region::Region; +use aws_types::sdk_config::SharedHttpClient; +use aws_types::SdkConfig; use std::borrow::Cow; use std::fmt::{Debug, Formatter}; use std::sync::Arc; @@ -38,8 +36,8 @@ pub struct ProviderConfig { env: Env, fs: Fs, time_source: SharedTimeSource, - connector: HttpConnector, - sleep: Option, + http_client: Option, + sleep_impl: Option, region: Option, use_fips: Option, use_dual_stack: Option, @@ -56,28 +54,25 @@ 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("use_fips", &self.use_fips) .field("use_dual_stack", &self.use_dual_stack) + .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, use_fips: None, use_dual_stack: None, @@ -106,8 +101,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, use_fips: None, use_dual_stack: None, @@ -148,8 +143,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, use_fips: None, use_dual_stack: None, @@ -160,15 +155,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, use_fips: None, use_dual_stack: None, @@ -192,18 +190,15 @@ impl ProviderConfig { Self::without_region().load_default_region().await } - pub(crate) fn client_config(&self, feature_name: &str) -> SdkConfig { + pub(crate) fn client_config(&self) -> SdkConfig { let mut builder = SdkConfig::builder() - .http_connector(expect_connector( - &format!("The {feature_name} features of aws-config"), - self.connector(&Default::default()), - )) .retry_config(RetryConfig::standard()) .region(self.region()) .time_source(self.time_source()) .use_fips(self.use_fips().unwrap_or_default()) .use_dual_stack(self.use_dual_stack().unwrap_or_default()); - builder.set_sleep_impl(self.sleep()); + builder.set_http_client(self.http_client.clone()); + builder.set_sleep_impl(self.sleep_impl.clone()); builder.build() } @@ -225,19 +220,13 @@ impl ProviderConfig { } #[allow(dead_code)] - pub(crate) fn default_connector(&self) -> Option { - self.connector - .connector(&Default::default(), self.sleep.clone()) + pub(crate) fn http_client(&self) -> Option { + self.http_client.clone() } #[allow(dead_code)] - pub(crate) fn connector(&self, settings: &ConnectorSettings) -> Option { - self.connector.connector(settings, self.sleep.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)] @@ -350,65 +339,33 @@ 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 { + pub fn with_time_source(self, time_source: impl TimeSource + 'static) -> Self { ProviderConfig { - time_source: SharedTimeSource::new(time_source), + time_source: time_source.into_shared(), ..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 { - ProviderConfig { - connector: connector.into(), - ..self - } + /// Deprecated. Don't use. + #[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn with_tcp_connector(self, http_client: impl HttpClient + 'static) -> Self { + self.with_http_client(http_client) } - /// 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 HttpClient + 'static) -> 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 AsyncSleep + 'static) -> 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 7a601f7be4..fae2248b35 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; @@ -25,14 +24,12 @@ use aws_smithy_types::DateTime; use aws_types::os_shim_internal::{Env, Fs}; use aws_types::region::Region; use aws_types::SdkConfig; - +use ring::digest; use std::convert::TryInto; use std::error::Error; use std::fmt::{Display, Formatter}; use std::io; use std::path::PathBuf; - -use ring::digest; use zeroize::Zeroizing; /// SSO Credentials Provider @@ -66,7 +63,7 @@ impl SsoCredentialsProvider { fs, env, sso_provider_config, - sdk_config: provider_config.client_config("SSO"), + sdk_config: provider_config.client_config(), } } diff --git a/aws/rust-runtime/aws-config/src/sts.rs b/aws/rust-runtime/aws-config/src/sts.rs index f774c65906..7c3ea29062 100644 --- a/aws/rust-runtime/aws-config/src/sts.rs +++ b/aws/rust-runtime/aws-config/src/sts.rs @@ -5,8 +5,7 @@ //! Credential provider augmentation through the AWS Security Token Service (STS). -pub(crate) mod util; - pub use assume_role::{AssumeRoleProvider, AssumeRoleProviderBuilder}; mod assume_role; +pub(crate) mod util; 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 06e79bc354..b6b61ba714 100644 --- a/aws/rust-runtime/aws-config/src/sts/assume_role.rs +++ b/aws/rust-runtime/aws-config/src/sts/assume_role.rs @@ -349,9 +349,10 @@ mod test { use aws_smithy_async::rt::sleep::{SharedAsyncSleep, 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, ReplayEvent, StaticReplayClient, + }; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; use aws_types::os_shim_internal::Env; use aws_types::region::Region; @@ -361,13 +362,13 @@ mod test { #[tokio::test] async fn configures_session_length() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let sdk_config = SdkConfig::builder() .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) .time_source(StaticTimeSource::new( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), )) - .http_connector(DynConnector::new(server)) + .http_client(http_client) .region(Region::from_static("this-will-be-overridden")) .build(); let provider = AssumeRoleProvider::builder("myrole") @@ -387,13 +388,13 @@ mod test { #[tokio::test] async fn loads_region_from_sdk_config() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let sdk_config = SdkConfig::builder() .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) .time_source(StaticTimeSource::new( UNIX_EPOCH + Duration::from_secs(1234567890 - 120), )) - .http_connector(DynConnector::new(server)) + .http_client(http_client) .credentials_provider(SharedCredentialsProvider::new(provide_credentials_fn( || async { panic!("don't call me — will be overridden"); @@ -417,7 +418,7 @@ mod test { #[tokio::test] async fn build_method_from_sdk_config() { let _guard = capture_test_logs(); - let (server, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .status(404) .body(SdkBody::from("")) @@ -432,7 +433,7 @@ mod test { .use_dual_stack(true) .use_fips(true) .time_source(StaticTimeSource::from_secs(1234567890)) - .http_connector(server) + .http_client(http_client) .load() .await; let provider = AssumeRoleProvider::builder("role") @@ -459,12 +460,12 @@ 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 = StaticReplayClient::new(vec![ + ReplayEvent::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")), + ReplayEvent::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()), @@ -477,7 +478,7 @@ mod test { let sdk_config = SdkConfig::builder() .sleep_impl(SharedAsyncSleep::new(sleep)) .time_source(testing_time_source.clone()) - .http_connector(DynConnector::new(conn)) + .http_client(http_client) .build(); let credentials_list = std::sync::Arc::new(std::sync::Mutex::new(vec![ Credentials::new( diff --git a/aws/rust-runtime/aws-config/src/test_case.rs b/aws/rust-runtime/aws-config/src/test_case.rs index 54d39d112d..d567bae89e 100644 --- a/aws/rust-runtime/aws-config/src/test_case.rs +++ b/aws/rust-runtime/aws-config/src/test_case.rs @@ -3,20 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::default_provider::use_dual_stack::use_dual_stack_provider; +use crate::default_provider::use_fips::use_fips_provider; 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 crate::default_provider::use_dual_stack::use_dual_stack_provider; -use crate::default_provider::use_fips::use_fips_provider; -use aws_smithy_types::error::display::DisplayErrorContext; use std::collections::HashMap; use std::env; use std::error::Error; @@ -70,18 +69,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)] @@ -230,12 +229,12 @@ 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; @@ -248,7 +247,7 @@ impl TestEnvironment { Ok(TestEnvironment { base_dir: dir.into(), metadata, - connector, + http_client, provider_config, }) } @@ -266,6 +265,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 @@ -277,18 +277,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); @@ -304,16 +307,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); @@ -350,7 +353,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 50d7171228..7ae4e2f8b0 100644 --- a/aws/rust-runtime/aws-config/src/web_identity_token.rs +++ b/aws/rust-runtime/aws-config/src/web_identity_token.rs @@ -204,7 +204,7 @@ impl Builder { WebIdentityTokenCredentialsProvider { source, fs: conf.fs(), - sts_client: StsClient::new(&conf.client_config("STS")), + sts_client: StsClient::new(&conf.client_config()), time_source: conf.time_source(), } } @@ -241,24 +241,24 @@ 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, }; use aws_credential_types::provider::error::CredentialsError; - use aws_sdk_sts::config::Region; use aws_smithy_async::rt::sleep::TokioSleep; use aws_smithy_types::error::display::DisplayErrorContext; use aws_types::os_shim_internal::{Env, Fs}; + use aws_types::region::Region; use std::collections::HashMap; #[tokio::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/external-types.toml b/aws/rust-runtime/aws-credential-types/external-types.toml index e65c743b10..88a5088190 100644 --- a/aws/rust-runtime/aws-credential-types/external-types.toml +++ b/aws/rust-runtime/aws-credential-types/external-types.toml @@ -1,4 +1,5 @@ allowed_external_types = [ + "aws_smithy_async::rt::sleep::AsyncSleep", "aws_smithy_async::rt::sleep::SharedAsyncSleep", "aws_smithy_types::config_bag::storable::Storable", "aws_smithy_types::config_bag::storable::StoreReplace", 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 6169c2b930..d919278db8 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 @@ -136,13 +136,14 @@ mod builder { use crate::cache::{CredentialsCache, Inner}; use crate::provider::SharedCredentialsProvider; - use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep}; - use aws_smithy_async::time::SharedTimeSource; + use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; + use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use super::{ 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 AsyncSleep + 'static) -> 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 TimeSource + 'static) -> 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-endpoint/Cargo.toml b/aws/rust-runtime/aws-endpoint/Cargo.toml index 67cb4d4e6e..5a6846d2d6 100644 --- a/aws/rust-runtime/aws-endpoint/Cargo.toml +++ b/aws/rust-runtime/aws-endpoint/Cargo.toml @@ -12,7 +12,6 @@ aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types"} aws-types = { path = "../aws-types" } http = "0.2.3" -regex = { version = "1.5.5", default-features = false, features = ["std"] } tracing = "0.1" [package.metadata.docs.rs] diff --git a/aws/rust-runtime/aws-http/Cargo.toml b/aws/rust-runtime/aws-http/Cargo.toml index 7b01057016..fc25360575 100644 --- a/aws/rust-runtime/aws-http/Cargo.toml +++ b/aws/rust-runtime/aws-http/Cargo.toml @@ -15,7 +15,6 @@ aws-types = { path = "../aws-types" } bytes = "1.1" http = "0.2.3" http-body = "0.4.5" -lazy_static = "1.4.0" tracing = "0.1" percent-encoding = "2.1.0" pin-project-lite = "0.2.9" diff --git a/aws/rust-runtime/aws-inlineable/Cargo.toml b/aws/rust-runtime/aws-inlineable/Cargo.toml index 6c70fb8e5f..28f4aeb212 100644 --- a/aws/rust-runtime/aws-inlineable/Cargo.toml +++ b/aws/rust-runtime/aws-inlineable/Cargo.toml @@ -13,15 +13,11 @@ repository = "https://github.com/awslabs/smithy-rs" [dependencies] aws-credential-types = { path = "../aws-credential-types" } -aws-endpoint = { path = "../aws-endpoint" } aws-http = { path = "../aws-http" } aws-runtime = { path = "../aws-runtime" } aws-sigv4 = { path = "../aws-sigv4" } -aws-sig-auth = { path = "../aws-sig-auth" } aws-smithy-checksums = { path = "../../../rust-runtime/aws-smithy-checksums" } -aws-smithy-client = { path = "../../../rust-runtime/aws-smithy-client" } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http" } -aws-smithy-http-tower = { path = "../../../rust-runtime/aws-smithy-http-tower" } aws-smithy-runtime-api = { path = "../../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime", features = ["client"] } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types" } diff --git a/aws/rust-runtime/aws-inlineable/external-types.toml b/aws/rust-runtime/aws-inlineable/external-types.toml index a261a6c437..0d2b9f397c 100644 --- a/aws/rust-runtime/aws-inlineable/external-types.toml +++ b/aws/rust-runtime/aws-inlineable/external-types.toml @@ -1,22 +1,11 @@ allowed_external_types = [ "aws_credential_types::provider::ProvideCredentials", - "aws_endpoint::*", - "aws_http::*", - "aws_sig_auth::*", - "aws_smithy_client::*", "aws_smithy_http::*", - "aws_smithy_http_tower::*", - "aws_smithy_types::*", - "aws_types::*", + + "http::error::Error", "http::header::map::HeaderMap", "http::header::value::HeaderValue", + "http::method::Method", "http::request::Request", - "http::error::Error", "http::uri::Uri", - "http::method::Method", - - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Decide if we want to continue exposing tower_layer - "tower_layer::Layer", - "tower_layer::identity::Identity", - "tower_layer::stack::Stack", ] diff --git a/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs b/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs index 498844f1b3..298899e71b 100644 --- a/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs +++ b/aws/rust-runtime/aws-inlineable/src/endpoint_discovery.rs @@ -5,9 +5,9 @@ //! Maintain a cache of discovered endpoints +use aws_smithy_async::future::BoxFuture; use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::erase::boxclone::BoxFuture; use aws_smithy_http::endpoint::{ResolveEndpoint, ResolveEndpointError}; use aws_smithy_types::endpoint::Endpoint; use std::fmt::{Debug, Formatter}; diff --git a/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs b/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs deleted file mode 100644 index bf95910e00..0000000000 --- a/aws/rust-runtime/aws-inlineable/src/glacier_checksums.rs +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// TODO(enableNewSmithyRuntimeCleanup): Delete this file when cleaning up middleware - -use aws_sig_auth::signer::SignableBody; -use aws_smithy_http::body::SdkBody; -use aws_smithy_http::byte_stream::{self, ByteStream}; -use aws_smithy_http::operation::Request; - -use bytes::Buf; -use bytes_utils::SegmentedBuf; -use http::header::HeaderName; -use ring::digest::{Context, Digest, SHA256}; - -const TREE_HASH_HEADER: &str = "x-amz-sha256-tree-hash"; -const X_AMZ_CONTENT_SHA256: &str = "x-amz-content-sha256"; - -/// Adds a glacier tree hash checksum to the HTTP Request -/// -/// This handles two cases: -/// 1. A body which is retryable: the body will be streamed through a digest calculator, limiting memory usage. -/// 2. A body which is not retryable: the body will be converted into `Bytes`, then streamed through a digest calculator. -/// -/// The actual checksum algorithm will first compute a SHA256 checksum for each 1MB chunk. Then, a tree -/// will be assembled, recursively pairing neighboring chunks and computing their combined checksum. The 1 leftover -/// chunk (if it exists) is paired at the end. -/// -/// See for more information. -pub async fn add_checksum_treehash(request: &mut Request) -> Result<(), byte_stream::error::Error> { - let cloneable = request.http().body().try_clone(); - let http_request = request.http_mut(); - let body_to_process = if let Some(cloned_body) = cloneable { - // we can stream the body - cloned_body - } else { - let body = std::mem::replace(http_request.body_mut(), SdkBody::taken()); - let loaded_body = ByteStream::new(body).collect().await?.into_bytes(); - *http_request.body_mut() = SdkBody::from(loaded_body.clone()); - SdkBody::from(loaded_body) - }; - let (full_body, hashes) = compute_hashes(body_to_process, MEGABYTE).await?; - let tree_hash = hex::encode(compute_hash_tree(hashes)); - let complete_hash = hex::encode(full_body); - if !http_request.headers().contains_key(TREE_HASH_HEADER) { - http_request.headers_mut().insert( - HeaderName::from_static(TREE_HASH_HEADER), - tree_hash.parse().expect("hash must be valid header"), - ); - } - if !http_request.headers().contains_key(X_AMZ_CONTENT_SHA256) { - http_request.headers_mut().insert( - HeaderName::from_static(X_AMZ_CONTENT_SHA256), - complete_hash.parse().expect("hash must be valid header"), - ); - } - // if we end up hitting the signer later, no need to recompute the checksum - request - .properties_mut() - .insert(SignableBody::Precomputed(complete_hash)); - // for convenience & protocol tests, write it in directly here as well - Ok(()) -} - -const MEGABYTE: usize = 1024 * 1024; -async fn compute_hashes( - body: SdkBody, - chunk_size: usize, -) -> Result<(Digest, Vec), byte_stream::error::Error> { - let mut hashes = vec![]; - let mut remaining_in_chunk = chunk_size; - let mut body = ByteStream::new(body); - let mut local = Context::new(&SHA256); - let mut full_body = Context::new(&SHA256); - let mut segmented = SegmentedBuf::new(); - while let Some(data) = body.try_next().await? { - segmented.push(data); - while segmented.has_remaining() { - let next = segmented.chunk(); - let len = next.len().min(remaining_in_chunk); - local.update(&next[..len]); - full_body.update(&next[..len]); - segmented.advance(len); - remaining_in_chunk -= len; - if remaining_in_chunk == 0 { - hashes.push(local.finish()); - local = Context::new(&SHA256); - remaining_in_chunk = chunk_size; - } - } - } - if remaining_in_chunk != chunk_size || hashes.is_empty() { - hashes.push(local.finish()); - } - Ok((full_body.finish(), hashes)) -} - -/// Compute the glacier tree hash for a vector of hashes. -/// -/// Adjacent hashes are combined into a single hash. This process occurs recursively until only 1 hash remains. -/// -/// See for more information. -fn compute_hash_tree(mut hashes: Vec) -> Digest { - assert!( - !hashes.is_empty(), - "even an empty file will produce a digest. this function assumes that hashes is non-empty" - ); - while hashes.len() > 1 { - let next = hashes.chunks(2).map(|chunk| match *chunk { - [left, right] => { - let mut ctx = Context::new(&SHA256); - ctx.update(left.as_ref()); - ctx.update(right.as_ref()); - ctx.finish() - } - [last] => last, - _ => unreachable!(), - }); - hashes = next.collect(); - } - hashes[0] -} - -#[cfg(test)] -mod test { - use crate::glacier_checksums::{ - add_checksum_treehash, compute_hash_tree, compute_hashes, MEGABYTE, TREE_HASH_HEADER, - }; - use aws_smithy_http::body::SdkBody; - use aws_smithy_http::byte_stream::ByteStream; - use aws_smithy_http::operation::Request; - - #[tokio::test] - async fn compute_digests() { - { - let body = SdkBody::from("1234"); - let hashes = compute_hashes(body, 1).await.expect("succeeds").1; - assert_eq!(hashes.len(), 4); - } - { - let body = SdkBody::from("1234"); - let hashes = compute_hashes(body, 2).await.expect("succeeds").1; - assert_eq!(hashes.len(), 2); - } - { - let body = SdkBody::from("12345"); - let hashes = compute_hashes(body, 3).await.expect("succeeds").1; - assert_eq!(hashes.len(), 2); - } - { - let body = SdkBody::from("11221122"); - let hashes = compute_hashes(body, 2).await.expect("succeeds").1; - assert_eq!(hashes[0].as_ref(), hashes[2].as_ref()); - } - } - - #[tokio::test] - async fn empty_body_computes_digest() { - let body = SdkBody::from(""); - let (_, hashes) = compute_hashes(body, 2).await.expect("succeeds"); - assert_eq!(hashes.len(), 1); - } - - #[tokio::test] - async fn compute_tree_digest() { - macro_rules! hash { - ($($inp:expr),*) => { - { - let mut ctx = ring::digest::Context::new(&ring::digest::SHA256); - $( - ctx.update($inp.as_ref()); - )* - ctx.finish() - } - } - } - let body = SdkBody::from("1234567891011"); - let (complete, hashes) = compute_hashes(body, 3).await.expect("succeeds"); - assert_eq!(hashes.len(), 5); - assert_eq!(complete.as_ref(), hash!("1234567891011").as_ref()); - let final_digest = compute_hash_tree(hashes); - let expected_digest = hash!( - hash!( - hash!(hash!("123"), hash!("456")), - hash!(hash!("789"), hash!("101")) - ), - hash!("1") - ); - assert_eq!(expected_digest.as_ref(), final_digest.as_ref()); - } - - #[tokio::test] - async fn integration_test() { - // the test data consists of an 11 byte sequence, repeated. Since the sequence length is - // relatively prime with 1 megabyte, we can ensure that chunks will all have different hashes. - let base_seq = b"01245678912"; - let total_size = MEGABYTE * 101 + 500; - let mut test_data = vec![]; - while test_data.len() < total_size { - test_data.extend_from_slice(base_seq) - } - let target = tempfile::NamedTempFile::new().unwrap(); - tokio::fs::write(target.path(), test_data).await.unwrap(); - let body = ByteStream::from_path(target.path()) - .await - .expect("should be valid") - .into_inner(); - - let mut http_req = Request::new( - http::Request::builder() - .uri("http://example.com/hello") - .body(body) - .unwrap(), - ); - - add_checksum_treehash(&mut http_req) - .await - .expect("should succeed"); - // hash value verified with AWS CLI - assert_eq!( - http_req.http().headers().get(TREE_HASH_HEADER).unwrap(), - "3d417484359fc9f5a3bafd576dc47b8b2de2bf2d4fdac5aa2aff768f2210d386" - ); - } -} diff --git a/aws/rust-runtime/aws-inlineable/src/http_body_checksum_middleware.rs b/aws/rust-runtime/aws-inlineable/src/http_body_checksum_middleware.rs deleted file mode 100644 index f71e8708e8..0000000000 --- a/aws/rust-runtime/aws-inlineable/src/http_body_checksum_middleware.rs +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Functions for modifying requests and responses for the purposes of checksum validation - -use aws_smithy_http::operation::error::BuildError; - -/// Errors related to constructing checksum-validated HTTP requests -#[derive(Debug)] -#[allow(dead_code)] -pub(crate) enum Error { - /// Only request bodies with a known size can be checksum validated - UnsizedRequestBody, - ChecksumHeadersAreUnsupportedForStreamingBody, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::UnsizedRequestBody => write!( - f, - "Only request bodies with a known size can be checksum validated." - ), - Self::ChecksumHeadersAreUnsupportedForStreamingBody => write!( - f, - "Checksum header insertion is only supported for non-streaming HTTP bodies. \ - To checksum validate a streaming body, the checksums must be sent as trailers." - ), - } - } -} - -impl std::error::Error for Error {} - -/// Given a `&mut http::request::Request` and a `aws_smithy_checksums::ChecksumAlgorithm`, -/// calculate a checksum and modify the request to include the checksum as a header -/// (for in-memory request bodies) or a trailer (for streaming request bodies.) Streaming bodies -/// must be sized or this will return an error. -#[allow(dead_code)] -pub(crate) fn add_checksum_calculation_to_request( - request: &mut http::request::Request, - property_bag: &mut aws_smithy_http::property_bag::PropertyBag, - checksum_algorithm: aws_smithy_checksums::ChecksumAlgorithm, -) -> Result<(), BuildError> { - match request.body().bytes() { - // Body is in-memory: read it and insert the checksum as a header. - Some(data) => { - let mut checksum = checksum_algorithm.into_impl(); - checksum.update(data); - - request - .headers_mut() - .insert(checksum.header_name(), checksum.header_value()); - } - // Body is streaming: wrap the body so it will emit a checksum as a trailer. - None => { - wrap_streaming_request_body_in_checksum_calculating_body( - request, - property_bag, - checksum_algorithm, - )?; - } - } - - Ok(()) -} - -#[allow(dead_code)] -fn wrap_streaming_request_body_in_checksum_calculating_body( - request: &mut http::request::Request, - property_bag: &mut aws_smithy_http::property_bag::PropertyBag, - checksum_algorithm: aws_smithy_checksums::ChecksumAlgorithm, -) -> Result<(), BuildError> { - use aws_http::content_encoding::{AwsChunkedBody, AwsChunkedBodyOptions}; - use aws_smithy_checksums::{body::calculate, http::HttpChecksum}; - use http_body::Body; - - let original_body_size = request - .body() - .size_hint() - .exact() - .ok_or_else(|| BuildError::other(Error::UnsizedRequestBody))?; - - // Streaming request bodies with trailers require special signing - property_bag.insert(aws_sig_auth::signer::SignableBody::StreamingUnsignedPayloadTrailer); - - let mut body = { - let body = std::mem::replace(request.body_mut(), aws_smithy_http::body::SdkBody::taken()); - - body.map(move |body| { - let checksum = checksum_algorithm.into_impl(); - let trailer_len = HttpChecksum::size(checksum.as_ref()); - let body = calculate::ChecksumBody::new(body, checksum); - let aws_chunked_body_options = - AwsChunkedBodyOptions::new(original_body_size, vec![trailer_len]); - - let body = AwsChunkedBody::new(body, aws_chunked_body_options); - - aws_smithy_http::body::SdkBody::from_dyn(aws_smithy_http::body::BoxBody::new(body)) - }) - }; - - let encoded_content_length = body - .size_hint() - .exact() - .ok_or_else(|| BuildError::other(Error::UnsizedRequestBody))?; - - let headers = request.headers_mut(); - - headers.insert( - http::header::HeaderName::from_static("x-amz-trailer"), - // Convert into a `HeaderName` and then into a `HeaderValue` - http::header::HeaderName::from(checksum_algorithm).into(), - ); - - headers.insert( - http::header::CONTENT_LENGTH, - http::HeaderValue::from(encoded_content_length), - ); - headers.insert( - http::header::HeaderName::from_static("x-amz-decoded-content-length"), - http::HeaderValue::from(original_body_size), - ); - headers.insert( - http::header::CONTENT_ENCODING, - http::HeaderValue::from_str(aws_http::content_encoding::header_value::AWS_CHUNKED) - .map_err(BuildError::other) - .expect("\"aws-chunked\" will always be a valid HeaderValue"), - ); - - std::mem::swap(request.body_mut(), &mut body); - - Ok(()) -} - -/// Given an `SdkBody`, a `aws_smithy_checksums::ChecksumAlgorithm`, and a pre-calculated checksum, -/// return an `SdkBody` where the body will processed with the checksum algorithm and checked -/// against the pre-calculated checksum. -#[allow(dead_code)] -pub(crate) fn wrap_body_with_checksum_validator( - body: aws_smithy_http::body::SdkBody, - checksum_algorithm: aws_smithy_checksums::ChecksumAlgorithm, - precalculated_checksum: bytes::Bytes, -) -> aws_smithy_http::body::SdkBody { - use aws_smithy_checksums::body::validate; - use aws_smithy_http::body::{BoxBody, SdkBody}; - - body.map(move |body| { - SdkBody::from_dyn(BoxBody::new(validate::ChecksumBody::new( - body, - checksum_algorithm.into_impl(), - precalculated_checksum.clone(), - ))) - }) -} - -/// Given a `HeaderMap`, extract any checksum included in the headers as `Some(Bytes)`. -/// If no checksum header is set, return `None`. If multiple checksum headers are set, the one that -/// is fastest to compute will be chosen. -#[allow(dead_code)] -pub(crate) fn check_headers_for_precalculated_checksum( - headers: &http::HeaderMap, - response_algorithms: &[&str], -) -> Option<(aws_smithy_checksums::ChecksumAlgorithm, bytes::Bytes)> { - let checksum_algorithms_to_check = - aws_smithy_checksums::http::CHECKSUM_ALGORITHMS_IN_PRIORITY_ORDER - .into_iter() - // Process list of algorithms, from fastest to slowest, that may have been used to checksum - // the response body, ignoring any that aren't marked as supported algorithms by the model. - .flat_map(|algo| { - // For loop is necessary b/c the compiler doesn't infer the correct lifetimes for iter().find() - for res_algo in response_algorithms { - if algo.eq_ignore_ascii_case(res_algo) { - return Some(algo); - } - } - - None - }); - - for checksum_algorithm in checksum_algorithms_to_check { - let checksum_algorithm: aws_smithy_checksums::ChecksumAlgorithm = checksum_algorithm.parse().expect( - "CHECKSUM_ALGORITHMS_IN_PRIORITY_ORDER only contains valid checksum algorithm names", - ); - if let Some(precalculated_checksum) = - headers.get(http::HeaderName::from(checksum_algorithm)) - { - let base64_encoded_precalculated_checksum = precalculated_checksum - .to_str() - .expect("base64 uses ASCII characters"); - - // S3 needs special handling for checksums of objects uploaded with `MultiPartUpload`. - if is_part_level_checksum(base64_encoded_precalculated_checksum) { - tracing::warn!( - more_info = "See https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html#large-object-checksums for more information.", - "This checksum is a part-level checksum which can't be validated by the Rust SDK. Disable checksum validation for this request to fix this warning.", - ); - - return None; - } - - let precalculated_checksum = match aws_smithy_types::base64::decode( - base64_encoded_precalculated_checksum, - ) { - Ok(decoded_checksum) => decoded_checksum.into(), - Err(_) => { - tracing::error!("Checksum received from server could not be base64 decoded. No checksum validation will be performed."); - return None; - } - }; - - return Some((checksum_algorithm, precalculated_checksum)); - } - } - - None -} - -fn is_part_level_checksum(checksum: &str) -> bool { - let mut found_number = false; - let mut found_dash = false; - - for ch in checksum.chars().rev() { - // this could be bad - if ch.is_ascii_digit() { - found_number = true; - continue; - } - - // Yup, it's a part-level checksum - if ch == '-' { - if found_dash { - // Found a second dash?? This isn't a part-level checksum. - return false; - } - - found_dash = true; - continue; - } - - break; - } - - found_number && found_dash -} - -#[cfg(test)] -mod tests { - use super::{is_part_level_checksum, wrap_body_with_checksum_validator}; - use aws_smithy_checksums::ChecksumAlgorithm; - use aws_smithy_http::body::SdkBody; - use aws_smithy_http::byte_stream::ByteStream; - use aws_smithy_types::error::display::DisplayErrorContext; - use bytes::{Bytes, BytesMut}; - use http_body::Body; - use std::sync::Once; - use tempfile::NamedTempFile; - - static INIT_LOGGER: Once = Once::new(); - fn init_logger() { - INIT_LOGGER.call_once(|| { - tracing_subscriber::fmt::init(); - }); - } - - #[tokio::test] - async fn test_checksum_body_is_retryable() { - let input_text = "Hello world"; - let precalculated_checksum = Bytes::from_static(&[0x8b, 0xd6, 0x9e, 0x52]); - let body = SdkBody::retryable(move || SdkBody::from(input_text)); - - // ensure original SdkBody is retryable - assert!(body.try_clone().is_some()); - - let body = body.map(move |sdk_body| { - let checksum_algorithm: ChecksumAlgorithm = "crc32".parse().unwrap(); - wrap_body_with_checksum_validator( - sdk_body, - checksum_algorithm, - precalculated_checksum.clone(), - ) - }); - - // ensure wrapped SdkBody is retryable - let mut body = body.try_clone().expect("body is retryable"); - - let mut validated_body = BytesMut::new(); - - loop { - match body.data().await { - Some(Ok(data)) => validated_body.extend_from_slice(&data), - Some(Err(err)) => panic!("{}", err), - None => { - break; - } - } - } - - let body = std::str::from_utf8(&validated_body).unwrap(); - - // ensure that the wrapped body passes checksum validation - assert_eq!(input_text, body); - } - - #[tokio::test] - async fn test_checksum_body_from_file_is_retryable() { - use std::io::Write; - let mut file = NamedTempFile::new().unwrap(); - let checksum_algorithm: ChecksumAlgorithm = "crc32c".parse().unwrap(); - let mut crc32c_checksum = checksum_algorithm.into_impl(); - - for i in 0..10000 { - let line = format!("This is a large file created for testing purposes {}", i); - file.as_file_mut().write_all(line.as_bytes()).unwrap(); - crc32c_checksum.update(line.as_bytes()); - } - - let body = ByteStream::read_from() - .path(&file) - .buffer_size(1024) - .build() - .await - .unwrap(); - - let precalculated_checksum = crc32c_checksum.finalize(); - let expected_checksum = precalculated_checksum.clone(); - - let body = body.map(move |sdk_body| { - wrap_body_with_checksum_validator( - sdk_body, - checksum_algorithm, - precalculated_checksum.clone(), - ) - }); - - // ensure wrapped SdkBody is retryable - let mut body = body.into_inner().try_clone().expect("body is retryable"); - - let mut validated_body = BytesMut::new(); - - // If this loop completes, then it means the body's checksum was valid, but let's calculate - // a checksum again just in case. - let mut redundant_crc32c_checksum = checksum_algorithm.into_impl(); - loop { - match body.data().await { - Some(Ok(data)) => { - redundant_crc32c_checksum.update(&data); - validated_body.extend_from_slice(&data); - } - Some(Err(err)) => panic!("{}", err), - None => { - break; - } - } - } - - let actual_checksum = redundant_crc32c_checksum.finalize(); - assert_eq!(expected_checksum, actual_checksum); - - // Ensure the file's checksum isn't the same as an empty checksum. This way, we'll know that - // data was actually processed. - let unexpected_checksum = checksum_algorithm.into_impl().finalize(); - assert_ne!(unexpected_checksum, actual_checksum); - } - - #[tokio::test] - async fn test_build_checksum_validated_body_works() { - init_logger(); - - let checksum_algorithm = "crc32".parse().unwrap(); - let input_text = "Hello world"; - let precalculated_checksum = Bytes::from_static(&[0x8b, 0xd6, 0x9e, 0x52]); - let body = ByteStream::new(SdkBody::from(input_text)); - - let body = body.map(move |sdk_body| { - wrap_body_with_checksum_validator( - sdk_body, - checksum_algorithm, - precalculated_checksum.clone(), - ) - }); - - let mut validated_body = Vec::new(); - if let Err(e) = tokio::io::copy(&mut body.into_async_read(), &mut validated_body).await { - tracing::error!("{}", DisplayErrorContext(&e)); - panic!("checksum validation has failed"); - }; - let body = std::str::from_utf8(&validated_body).unwrap(); - - assert_eq!(input_text, body); - } - - #[test] - fn test_is_multipart_object_checksum() { - // These ARE NOT part-level checksums - assert!(!is_part_level_checksum("abcd")); - assert!(!is_part_level_checksum("abcd=")); - assert!(!is_part_level_checksum("abcd==")); - assert!(!is_part_level_checksum("1234")); - assert!(!is_part_level_checksum("1234=")); - assert!(!is_part_level_checksum("1234==")); - // These ARE part-level checksums - assert!(is_part_level_checksum("abcd-1")); - assert!(is_part_level_checksum("abcd=-12")); - assert!(is_part_level_checksum("abcd12-134")); - assert!(is_part_level_checksum("abcd==-10000")); - // These are gibberish and shouldn't be regarded as a part-level checksum - assert!(!is_part_level_checksum("")); - assert!(!is_part_level_checksum("Spaces? In my header values?")); - assert!(!is_part_level_checksum("abcd==-134!#{!#")); - assert!(!is_part_level_checksum("abcd==-")); - assert!(!is_part_level_checksum("abcd==--11")); - assert!(!is_part_level_checksum("abcd==-AA")); - } -} diff --git a/aws/rust-runtime/aws-inlineable/src/lib.rs b/aws/rust-runtime/aws-inlineable/src/lib.rs index 591d95741d..8ff2f5f478 100644 --- a/aws/rust-runtime/aws-inlineable/src/lib.rs +++ b/aws/rust-runtime/aws-inlineable/src/lib.rs @@ -34,9 +34,6 @@ pub mod presigning_interceptors; /// Special logic for extracting request IDs from S3's responses. pub mod s3_request_id; -/// Glacier-specific checksumming behavior -pub mod glacier_checksums; - /// Glacier-specific behavior pub mod glacier_interceptors; @@ -49,10 +46,6 @@ pub mod route53_resource_id_preprocessor; pub mod http_request_checksum; pub mod http_response_checksum; -// TODO(enableNewSmithyRuntimeCleanup): Delete this module -/// Convert a streaming `SdkBody` into an aws-chunked streaming body with checksum trailers -pub mod http_body_checksum_middleware; - #[allow(dead_code)] pub mod endpoint_discovery; diff --git a/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs index f19ad434d1..d2e64a7038 100644 --- a/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs +++ b/aws/rust-runtime/aws-inlineable/src/s3_request_id.rs @@ -3,9 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_smithy_client::SdkError; use aws_smithy_http::http::HttpHeaders; use aws_smithy_http::operation; +use aws_smithy_http::result::SdkError; use aws_smithy_types::error::metadata::{ Builder as ErrorMetadataBuilder, ErrorMetadata, ProvideErrorMetadata, }; @@ -101,8 +101,8 @@ fn extract_extended_request_id(headers: &HeaderMap) -> Option<&str> #[cfg(test)] mod test { use super::*; - use aws_smithy_client::SdkError; use aws_smithy_http::body::SdkBody; + use aws_smithy_http::result::SdkError; use http::Response; #[test] diff --git a/aws/rust-runtime/aws-runtime/additional-ci b/aws/rust-runtime/aws-runtime/additional-ci new file mode 100755 index 0000000000..b44c6c05be --- /dev/null +++ b/aws/rust-runtime/aws-runtime/additional-ci @@ -0,0 +1,12 @@ +#!/bin/bash +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +# This script contains additional CI checks to run for this specific package + +set -e + +echo "### Testing every combination of features (excluding --all-features)" +cargo hack test --feature-powerset --exclude-all-features diff --git a/aws/rust-runtime/aws-runtime/src/invocation_id.rs b/aws/rust-runtime/aws-runtime/src/invocation_id.rs index 18fcac7c4a..7a8b47feb0 100644 --- a/aws/rust-runtime/aws-runtime/src/invocation_id.rs +++ b/aws/rust-runtime/aws-runtime/src/invocation_id.rs @@ -219,7 +219,7 @@ mod tests { }; use aws_smithy_runtime_api::client::interceptors::Interceptor; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; - use aws_smithy_types::config_bag::{ConfigBag, Layer}; + use aws_smithy_types::config_bag::ConfigBag; use http::HeaderValue; fn expect_header<'a>( @@ -258,6 +258,7 @@ mod tests { #[cfg(feature = "test-util")] #[test] fn custom_id_generator() { + use aws_smithy_types::config_bag::Layer; let rc = RuntimeComponentsBuilder::for_tests().build().unwrap(); let mut ctx = InterceptorContext::new(Input::doesnt_matter()); ctx.enter_serialization_phase(); diff --git a/aws/rust-runtime/aws-types/Cargo.toml b/aws/rust-runtime/aws-types/Cargo.toml index eb0b527f42..2570ccff73 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/external-types.toml b/aws/rust-runtime/aws-types/external-types.toml index e43510d69e..ca85884065 100644 --- a/aws/rust-runtime/aws-types/external-types.toml +++ b/aws/rust-runtime/aws-types/external-types.toml @@ -1,18 +1,15 @@ allowed_external_types = [ "aws_credential_types::cache::CredentialsCache", "aws_credential_types::provider::SharedCredentialsProvider", + "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::http_connector", - "aws_smithy_client::http_connector::HttpConnector", - "aws_smithy_http::endpoint::Endpoint", - "aws_smithy_http::endpoint::EndpointPrefix", - "aws_smithy_http::endpoint::error::InvalidEndpointError", + "aws_smithy_runtime_api::client::http::HttpClient", + "aws_smithy_runtime_api::client::http::SharedHttpClient", "aws_smithy_types::config_bag::storable::Storable", "aws_smithy_types::config_bag::storable::StoreReplace", "aws_smithy_types::config_bag::storable::Storer", "aws_smithy_types::retry::RetryConfig", "aws_smithy_types::timeout::TimeoutConfig", - "http::uri::Uri", ] diff --git a/aws/rust-runtime/aws-types/src/lib.rs b/aws/rust-runtime/aws-types/src/lib.rs index bf54671e15..e6a0a69dcd 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 81ce7d35c2..e97a58cc3d 100644 --- a/aws/rust-runtime/aws-types/src/sdk_config.rs +++ b/aws/rust-runtime/aws-types/src/sdk_config.rs @@ -9,18 +9,21 @@ //! //! 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; +use aws_smithy_async::rt::sleep::AsyncSleep; +pub use aws_smithy_async::rt::sleep::SharedAsyncSleep; +pub use aws_smithy_async::time::{SharedTimeSource, TimeSource}; +use aws_smithy_runtime_api::client::http::HttpClient; +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 +59,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 +80,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, } @@ -230,8 +233,9 @@ impl Builder { self } - /// Set the sleep implementation for the builder. The sleep implementation is used to create - /// timeout futures. + /// Set the sleep implementation for the builder. + /// + /// The sleep implementation is used to create timeout futures. /// /// _Note:_ If you're using the Tokio runtime, a `TokioSleep` implementation is available in /// the `aws-smithy-async` crate. @@ -254,8 +258,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 AsyncSleep + 'static) -> Self { + self.set_sleep_impl(Some(sleep_impl.into_shared())); self } @@ -399,81 +403,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 HttpClient + 'static) -> 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 +523,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 +578,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 @@ -620,7 +619,7 @@ impl SdkConfig { sleep_impl: self.sleep_impl, time_source: self.time_source, 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, } 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 cd8fe1bef1..878c4a851f 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 7b69c5c72a..fb4b5fa58e 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_client(${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 53ca182cdf..51f88d2401 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 @@ -106,7 +106,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/IntegrationTestDependencies.kt b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt index 95553d8006..e6d4a3f442 100644 --- a/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt +++ b/aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/IntegrationTestDependencies.kt @@ -84,17 +84,14 @@ class IntegrationTestDependencies( if (hasTests) { val smithyAsync = CargoDependency.smithyAsync(codegenContext.runtimeConfig) .copy(features = setOf("test-util"), scope = DependencyScope.Dev) - val smithyClient = CargoDependency.smithyClient(codegenContext.runtimeConfig) - .copy(features = setOf("test-util", "wiremock"), scope = DependencyScope.Dev) val smithyTypes = CargoDependency.smithyTypes(codegenContext.runtimeConfig) .copy(features = setOf("test-util"), scope = DependencyScope.Dev) addDependency(awsRuntime(runtimeConfig).toDevDependency().withFeature("test-util")) addDependency(FuturesUtil) addDependency(SerdeJson) addDependency(smithyAsync) - addDependency(smithyClient) addDependency(smithyProtocolTestHelpers(codegenContext.runtimeConfig)) - addDependency(smithyRuntime(runtimeConfig).copy(features = setOf("test-util"), scope = DependencyScope.Dev)) + addDependency(smithyRuntime(runtimeConfig).copy(features = setOf("test-util", "wire-mock"), scope = DependencyScope.Dev)) addDependency(smithyRuntimeApi(runtimeConfig).copy(features = setOf("test-util"), scope = DependencyScope.Dev)) addDependency(smithyTypes) addDependency(Tokio) 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 7ad1e41246..7167910c08 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 b0ec8aac31..43210766e0 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 @@ -83,12 +83,12 @@ fun usesDeprecatedBuiltIns(testOperationInput: EndpointTestOperationInput): Bool * "AWS::S3::UseArnRegion": false * } */ * /* clientParams: {} */ - * let (conn, rcvr) = aws_smithy_client::test_connection::capture_request(None); + * let (http_client, rcvr) = aws_smithy_client::test_connection::capture_request(None); * let conf = { * #[allow(unused_mut)] * let mut builder = aws_sdk_s3::Config::builder() * .with_test_defaults() - * .http_connector(conn); + * .http_client(http_client); * let builder = builder.region(aws_types::region::Region::new("us-west-2")); * let builder = builder.use_arn_region(false); * builder.build() @@ -122,11 +122,6 @@ class OperationInputTestGenerator(_ctx: ClientCodegenContext, private val test: private val model = ctx.model private val instantiator = ClientInstantiator(ctx) - /** the Rust SDK doesn't support SigV4a — search endpoint.properties.authSchemes[].name */ - private fun EndpointTestCase.isSigV4a() = - expect.endpoint.orNull()?.properties?.get("authSchemes")?.asArrayNode()?.orNull() - ?.map { it.expectObjectNode().expectStringMember("name").value }?.contains("sigv4a") == true - fun generateInput(testOperationInput: EndpointTestOperationInput) = writable { val operationName = testOperationInput.operationName.toSnakeCase() tokioTest(safeName("operation_input_test_$operationName")) { @@ -134,7 +129,7 @@ class OperationInputTestGenerator(_ctx: ClientCodegenContext, private val test: """ /* builtIns: ${escape(Node.prettyPrintJson(testOperationInput.builtInParams))} */ /* clientParams: ${escape(Node.prettyPrintJson(testOperationInput.clientParams))} */ - let (conn, rcvr) = #{capture_request}(None); + let (http_client, rcvr) = #{capture_request}(None); let conf = #{conf}; let client = $moduleName::Client::from_conf(conf); let _result = dbg!(#{invoke_operation}); @@ -192,7 +187,7 @@ class OperationInputTestGenerator(_ctx: ClientCodegenContext, private val test: private fun config(operationInput: EndpointTestOperationInput) = writable { rustBlock("") { Attribute.AllowUnusedMut.render(this) - rust("let mut builder = $moduleName::Config::builder().with_test_defaults().http_connector(conn);") + rust("let mut builder = $moduleName::Config::builder().with_test_defaults().http_client(http_client);") operationInput.builtInParams.members.forEach { (builtIn, value) -> val setter = endpointCustomizations.firstNotNullOfOrNull { it.setBuiltInOnServiceConfig( 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 9c1a29ecae..68bfab92fe 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,27 +87,30 @@ 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 = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( + http::Request::builder() + .uri("https://RIGHT/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap() + )], + ); 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"), + "StaticReplayClient" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::StaticReplayClient"), + "ReplayEvent" to CargoDependency.smithyRuntimeTestUtil(codegenContext.runtimeConfig).toType() + .resolve("client::http::test_util::ReplayEvent"), "SdkBody" to RuntimeType.sdkBody(codegenContext.runtimeConfig), ) } 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 0708035541..19ad56d116 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 3a792e2a0a..be0861a045 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/aws/sdk/integration-tests/dynamodb/Cargo.toml b/aws/sdk/integration-tests/dynamodb/Cargo.toml index 61663dbbed..2a9d1725af 100644 --- a/aws/sdk/integration-tests/dynamodb/Cargo.toml +++ b/aws/sdk/integration-tests/dynamodb/Cargo.toml @@ -17,7 +17,6 @@ aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-dynamodb = { path = "../../build/aws-sdk/sdk/dynamodb" } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-smithy-protocol-test = { path = "../../build/aws-sdk/sdk/aws-smithy-protocol-test" } aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"]} diff --git a/aws/sdk/integration-tests/dynamodb/tests/endpoints.rs b/aws/sdk/integration-tests/dynamodb/tests/endpoints.rs index 25d65bb94c..2f0180b449 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/endpoints.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/endpoints.rs @@ -4,6 +4,7 @@ */ use aws_sdk_dynamodb::config::{self, Credentials, Region}; +use aws_smithy_runtime::client::http::test_util::capture_request; use aws_types::SdkConfig; use http::Uri; @@ -12,11 +13,11 @@ async fn expect_uri( uri: &'static str, customize: fn(config::Builder) -> config::Builder, ) { - let (conn, request) = aws_smithy_client::test_connection::capture_request(None); + let (http_client, request) = capture_request(None); let conf = customize( aws_sdk_dynamodb::config::Builder::from(&conf) .credentials_provider(Credentials::for_tests()) - .http_connector(conn), + .http_client(http_client), ) .build(); let svc = aws_sdk_dynamodb::Client::from_conf(conf); diff --git a/aws/sdk/integration-tests/dynamodb/tests/movies.rs b/aws/sdk/integration-tests/dynamodb/tests/movies.rs index 7b045c6f5b..981562c05a 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/movies.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/movies.rs @@ -4,8 +4,9 @@ */ use aws_sdk_dynamodb as dynamodb; -use aws_smithy_client::test_connection::TestConnection; +use aws_smithy_async::assert_elapsed; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use dynamodb::config::{Credentials, Region}; use dynamodb::operation::query::QueryOutput; use dynamodb::types::{ @@ -18,7 +19,6 @@ use http::Uri; use serde_json::Value; use std::collections::HashMap; use std::time::Duration; -use tokio::time::Instant; async fn create_table(client: &Client, table_name: &str) { client @@ -128,17 +128,6 @@ async fn wait_for_ready_table(client: &Client, table_name: &str) { } } -/// Validate that time has passed with a 5ms tolerance -/// -/// This is to account for some non-determinism in the Tokio timer -fn assert_time_passed(initial: Instant, passed: Duration) { - let now = tokio::time::Instant::now(); - let delta = now - initial; - if (delta.as_millis() as i128 - passed.as_millis() as i128).abs() > 5 { - assert_eq!(delta, passed) - } -} - /// A partial reimplementation of https://docs.amazonaws.cn/en_us/amazondynamodb/latest/developerguide/GettingStarted.Ruby.html /// in Rust /// @@ -151,10 +140,10 @@ async fn movies_it() { let table_name = "Movies-5"; // The waiter will retry 5 times tokio::time::pause(); - let conn = movies_it_test_connection(); // RecordingConnection::https(); + let http_client = movies_it_test_connection(); // RecordingConnection::https(); let conf = dynamodb::Config::builder() .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(Credentials::for_tests()) .build(); let client = Client::from_conf(conf); @@ -164,7 +153,11 @@ async fn movies_it() { let waiter_start = tokio::time::Instant::now(); wait_for_ready_table(&client, table_name).await; - assert_time_passed(waiter_start, Duration::from_secs(4)); + assert_elapsed!( + waiter_start, + Duration::from_secs(4), + Duration::from_millis(10) + ); // data.json contains 2 movies from 2013 let data = match serde_json::from_str(include_str!("data.json")).expect("should be valid JSON") { @@ -194,192 +187,193 @@ async fn movies_it() { ] ); - conn.assert_requests_match(&[AUTHORIZATION, HeaderName::from_static("x-amz-date")]); + http_client.assert_requests_match(&[AUTHORIZATION, HeaderName::from_static("x-amz-date")]); } /// Test connection for the movies IT /// headers are signed with actual creds, at some point we could replace them with verifiable test /// credentials, but there are plenty of other tests that target signing -fn movies_it_test_connection() -> TestConnection<&'static str> { - TestConnection::new(vec![( - http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.CreateTable") - .header("content-length", "313") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=4a832eba37651836b524b587986be607607b077ad133c57b4bf7300d2e02f476") - .header("x-amz-date", "20210308T155118Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"AttributeDefinitions":[{"AttributeName":"year","AttributeType":"N"},{"AttributeName":"title","AttributeType":"S"}],"TableName":"Movies-5","KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"ReadCapacityUnits":10,"WriteCapacityUnits":10}}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "572") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "RCII0AALE00UALC7LJ9AD600B7VV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "3715137447") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=01b0129a2a4fb3af14559fde8163d59de9c43907152a12479002b3a7c75fa0df") - .header("x-amz-date", "20210308T155119Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "561") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "O1C6QKCG8GT7D2K922T4QRL9N3VV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "46742265") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=7f3a743bb460f26296640ae775d282f0153eda750855ec00ace1815becfd2de5") - .header("x-amz-date", "20210308T155120Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")).body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:20 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "561") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "EN5N26BO1FAOEMUUSD7B7SUPPVVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "46742265") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=46a148c560139bc0da171bd915ea8c0b96a7012629f5db7b6bf70fcd1a66fd24") - .header("x-amz-date", "20210308T155121Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:21 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "561") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "PHCMGEVI6JLN9JNMKSSA3M76H3VV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "46742265") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=15bb7c9b2350747d62349091b3ea59d9e1800d1dca04029943329259bba85cb4") - .header("x-amz-date", "20210308T155122Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:22 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "561") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "1Q22O983HD3511TN6Q5RRTP0MFVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "46742265") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#).unwrap()), - (http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.DescribeTable") - .header("content-length", "24") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=6d0a78087bc112c68a91b4b2d457efd8c09149b85b8f998f8c4b3f9916c8a743") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "559") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "ONJBNV2A9GBNUT34KH73JLL23BVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "24113616") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"ACTIVE"}}"#).unwrap()), - (http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.PutItem") - .header("content-length", "619") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=85fc7d2064a0e6d9c38d64751d39d311ad415ae4079ef21ef254b23ecf093519") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"rating":{"N":"6.2"},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"release_date":{"S":"2013-01-18T00:00:00Z"},"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"running_time_secs":{"N":"5215"},"rank":{"N":"11"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]}}},"title":{"S":"Turn It Down, Or Else!"},"year":{"N":"2013"}}}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "2") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "E6TGS5HKHHV08HSQA31IO1IDMFVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "2745614147") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{}"#).unwrap()), - (http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.PutItem") - .header("content-length", "636") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=e4b1658c9f5129b3656381f6592a30e0061b1566263fbf27d982817ea79483f6") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"rating":{"N":"8.3"},"rank":{"N":"2"},"release_date":{"S":"2013-09-02T00:00:00Z"},"directors":{"L":[{"S":"Ron Howard"}]},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"running_time_secs":{"N":"7380"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]}}},"title":{"S":"Rush"},"year":{"N":"2013"}}}"#)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "2") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "B63D54LP2FOGQK9JE5KLJT49HJVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "2745614147") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{}"#).unwrap()), - (http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.Query") - .header("content-length", "156") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=c9a0fdd0c7c3a792faddabca1fc154c8fbb54ddee7b06a8082e1c587615198b5") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2222"}}}"##)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "39") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "AUAS9KJ0TK9BSR986TRPC2RGTRVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "3413411624") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Count":0,"Items":[],"ScannedCount":0}"#).unwrap()), - (http::Request::builder() - .header("content-type", "application/x-amz-json-1.0") - .header("x-amz-target", "DynamoDB_20120810.Query") - .header("content-length", "156") - .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=504d6b4de7093b20255b55057085937ec515f62f3c61da68c03bff3f0ce8a160") - .header("x-amz-date", "20210308T155123Z") - .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) - .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2013"}}}"##)).unwrap(), - http::Response::builder() - .header("server", "Server") - .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") - .header("content-type", "application/x-amz-json-1.0") - .header("content-length", "1231") - .header("connection", "keep-alive") - .header("x-amzn-requestid", "A5FGSJ9ET4OKB8183S9M47RQQBVV4KQNSO5AEMVJF66Q9ASUAAJG") - .header("x-amz-crc32", "624725176") - .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Count":2,"Items":[{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"release_date":{"S":"2013-09-02T00:00:00Z"},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]},"directors":{"L":[{"S":"Ron Howard"}]},"rating":{"N":"8.3"},"rank":{"N":"2"},"running_time_secs":{"N":"7380"}}},"title":{"S":"Rush"}},{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"release_date":{"S":"2013-01-18T00:00:00Z"},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]},"rating":{"N":"6.2"},"rank":{"N":"11"},"running_time_secs":{"N":"5215"}}},"title":{"S":"Turn It Down, Or Else!"}}],"ScannedCount":2}"#).unwrap()) +fn movies_it_test_connection() -> StaticReplayClient { + StaticReplayClient::new(vec![ + ReplayEvent::new( + http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.CreateTable") + .header("content-length", "313") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=4a832eba37651836b524b587986be607607b077ad133c57b4bf7300d2e02f476") + .header("x-amz-date", "20210308T155118Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"AttributeDefinitions":[{"AttributeName":"year","AttributeType":"N"},{"AttributeName":"title","AttributeType":"S"}],"TableName":"Movies-5","KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"ReadCapacityUnits":10,"WriteCapacityUnits":10}}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "572") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "RCII0AALE00UALC7LJ9AD600B7VV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "3715137447") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"TableDescription":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=01b0129a2a4fb3af14559fde8163d59de9c43907152a12479002b3a7c75fa0df") + .header("x-amz-date", "20210308T155119Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:18 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "O1C6QKCG8GT7D2K922T4QRL9N3VV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=7f3a743bb460f26296640ae775d282f0153eda750855ec00ace1815becfd2de5") + .header("x-amz-date", "20210308T155120Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")).body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:20 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "EN5N26BO1FAOEMUUSD7B7SUPPVVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=46a148c560139bc0da171bd915ea8c0b96a7012629f5db7b6bf70fcd1a66fd24") + .header("x-amz-date", "20210308T155121Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:21 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "PHCMGEVI6JLN9JNMKSSA3M76H3VV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=15bb7c9b2350747d62349091b3ea59d9e1800d1dca04029943329259bba85cb4") + .header("x-amz-date", "20210308T155122Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:22 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "561") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "1Q22O983HD3511TN6Q5RRTP0MFVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "46742265") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"CREATING"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.DescribeTable") + .header("content-length", "24") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=6d0a78087bc112c68a91b4b2d457efd8c09149b85b8f998f8c4b3f9916c8a743") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5"}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "559") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "ONJBNV2A9GBNUT34KH73JLL23BVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "24113616") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Table":{"AttributeDefinitions":[{"AttributeName":"title","AttributeType":"S"},{"AttributeName":"year","AttributeType":"N"}],"CreationDateTime":1.615218678973E9,"ItemCount":0,"KeySchema":[{"AttributeName":"year","KeyType":"HASH"},{"AttributeName":"title","KeyType":"RANGE"}],"ProvisionedThroughput":{"NumberOfDecreasesToday":0,"ReadCapacityUnits":10,"WriteCapacityUnits":10},"TableArn":"arn:aws:dynamodb:us-east-1:134095065856:table/Movies-5","TableId":"b08c406a-7dbc-4f7d-b7c6-672a43ec21cd","TableName":"Movies-5","TableSizeBytes":0,"TableStatus":"ACTIVE"}}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.PutItem") + .header("content-length", "619") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=85fc7d2064a0e6d9c38d64751d39d311ad415ae4079ef21ef254b23ecf093519") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"rating":{"N":"6.2"},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"release_date":{"S":"2013-01-18T00:00:00Z"},"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"running_time_secs":{"N":"5215"},"rank":{"N":"11"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]}}},"title":{"S":"Turn It Down, Or Else!"},"year":{"N":"2013"}}}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "2") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "E6TGS5HKHHV08HSQA31IO1IDMFVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "2745614147") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.PutItem") + .header("content-length", "636") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=e4b1658c9f5129b3656381f6592a30e0061b1566263fbf27d982817ea79483f6") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r#"{"TableName":"Movies-5","Item":{"info":{"M":{"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"rating":{"N":"8.3"},"rank":{"N":"2"},"release_date":{"S":"2013-09-02T00:00:00Z"},"directors":{"L":[{"S":"Ron Howard"}]},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"running_time_secs":{"N":"7380"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]}}},"title":{"S":"Rush"},"year":{"N":"2013"}}}"#)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "2") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "B63D54LP2FOGQK9JE5KLJT49HJVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "2745614147") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.Query") + .header("content-length", "156") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=c9a0fdd0c7c3a792faddabca1fc154c8fbb54ddee7b06a8082e1c587615198b5") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2222"}}}"##)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "39") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "AUAS9KJ0TK9BSR986TRPC2RGTRVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "3413411624") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Count":0,"Items":[],"ScannedCount":0}"#)).unwrap()), + ReplayEvent::new(http::Request::builder() + .header("content-type", "application/x-amz-json-1.0") + .header("x-amz-target", "DynamoDB_20120810.Query") + .header("content-length", "156") + .header("authorization", "AWS4-HMAC-SHA256 Credential=ASIAR6OFQKMAFQIIYZ5T/20210308/us-east-1/dynamodb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-target, Signature=504d6b4de7093b20255b55057085937ec515f62f3c61da68c03bff3f0ce8a160") + .header("x-amz-date", "20210308T155123Z") + .uri(Uri::from_static("https://dynamodb.us-east-1.amazonaws.com/")) + .body(SdkBody::from(r##"{"TableName":"Movies-5","KeyConditionExpression":"#yr = :yyyy","ExpressionAttributeNames":{"#yr":"year"},"ExpressionAttributeValues":{":yyyy":{"N":"2013"}}}"##)).unwrap(), + http::Response::builder() + .header("server", "Server") + .header("date", "Mon, 08 Mar 2021 15:51:23 GMT") + .header("content-type", "application/x-amz-json-1.0") + .header("content-length", "1231") + .header("connection", "keep-alive") + .header("x-amzn-requestid", "A5FGSJ9ET4OKB8183S9M47RQQBVV4KQNSO5AEMVJF66Q9ASUAAJG") + .header("x-amz-crc32", "624725176") + .status(http::StatusCode::from_u16(200).unwrap()) + .body(SdkBody::from(r#"{"Count":2,"Items":[{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"Daniel Bruhl"},{"S":"Chris Hemsworth"},{"S":"Olivia Wilde"}]},"plot":{"S":"A re-creation of the merciless 1970s rivalry between Formula One rivals James Hunt and Niki Lauda."},"release_date":{"S":"2013-09-02T00:00:00Z"},"image_url":{"S":"http://ia.media-imdb.com/images/M/MV5BMTQyMDE0MTY0OV5BMl5BanBnXkFtZTcwMjI2OTI0OQ@@._V1_SX400_.jpg"},"genres":{"L":[{"S":"Action"},{"S":"Biography"},{"S":"Drama"},{"S":"Sport"}]},"directors":{"L":[{"S":"Ron Howard"}]},"rating":{"N":"8.3"},"rank":{"N":"2"},"running_time_secs":{"N":"7380"}}},"title":{"S":"Rush"}},{"year":{"N":"2013"},"info":{"M":{"actors":{"L":[{"S":"David Matthewman"},{"S":"Ann Thomas"},{"S":"Jonathan G. Neff"}]},"release_date":{"S":"2013-01-18T00:00:00Z"},"plot":{"S":"A rock band plays their music at high volumes, annoying the neighbors."},"genres":{"L":[{"S":"Comedy"},{"S":"Drama"}]},"image_url":{"S":"http://ia.media-imdb.com/images/N/O9ERWAU7FS797AJ7LU8HN09AMUP908RLlo5JF90EWR7LJKQ7@@._V1_SX400_.jpg"},"directors":{"L":[{"S":"Alice Smith"},{"S":"Bob Jones"}]},"rating":{"N":"6.2"},"rank":{"N":"11"},"running_time_secs":{"N":"5215"}}},"title":{"S":"Turn It Down, Or Else!"}}],"ScannedCount":2}"#)).unwrap()) ]) } diff --git a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs index a3d0c62473..711feb1e01 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/paginators.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/paginators.rs @@ -9,25 +9,27 @@ use std::iter::FromIterator; use aws_credential_types::Credentials; use aws_sdk_dynamodb::types::AttributeValue; use aws_sdk_dynamodb::{Client, Config}; -use aws_smithy_client::http_connector::HttpConnector; -use aws_smithy_client::test_connection::{capture_request, TestConnection}; use aws_smithy_http::body::SdkBody; use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; +use aws_smithy_runtime::client::http::test_util::{ + capture_request, ReplayEvent, StaticReplayClient, +}; +use aws_smithy_runtime_api::client::http::HttpClient; use aws_types::region::Region; -fn stub_config(conn: impl Into) -> Config { +fn stub_config(http_client: impl HttpClient + 'static) -> Config { Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build() } /// Validate that arguments are passed on to the paginator #[tokio::test] async fn paginators_pass_args() { - let (conn, request) = capture_request(None); - let client = Client::from_conf(stub_config(conn)); + let (http_client, request) = capture_request(None); + let client = Client::from_conf(stub_config(http_client)); let mut paginator = client .scan() .table_name("test-table") @@ -57,8 +59,8 @@ fn mk_response(body: &'static str) -> http::Response { #[tokio::test(flavor = "current_thread")] async fn paginators_loop_until_completion() { - let conn = TestConnection::new(vec![ - ( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( mk_request(r#"{"TableName":"test-table","Limit":32}"#), mk_response( r#"{ @@ -74,7 +76,7 @@ async fn paginators_loop_until_completion() { }"#, ), ), - ( + ReplayEvent::new( mk_request( r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, ), @@ -90,14 +92,14 @@ async fn paginators_loop_until_completion() { ), ), ]); - let client = Client::from_conf(stub_config(conn.clone())); + let client = Client::from_conf(stub_config(http_client.clone())); let mut paginator = client .scan() .table_name("test-table") .into_paginator() .page_size(32) .send(); - assert_eq!(conn.requests().len(), 0); + assert_eq!(http_client.actual_requests().count(), 0); let first_page = paginator .try_next() .await @@ -110,7 +112,7 @@ async fn paginators_loop_until_completion() { AttributeValue::S("joe@example.com".to_string()) )])] ); - assert_eq!(conn.requests().len(), 1); + assert_eq!(http_client.actual_requests().count(), 1); let second_page = paginator .try_next() .await @@ -123,36 +125,36 @@ async fn paginators_loop_until_completion() { AttributeValue::S("jack@example.com".to_string()) )])] ); - assert_eq!(conn.requests().len(), 2); + assert_eq!(http_client.actual_requests().count(), 2); assert!( paginator.next().await.is_none(), "no more pages should exist" ); // we shouldn't make another request, we know we're at the end - assert_eq!(conn.requests().len(), 2); - conn.assert_requests_match(&[]); + assert_eq!(http_client.actual_requests().count(), 2); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn paginators_handle_errors() { // LastEvaluatedKey is set but there is only one response in the test connection - let conn = TestConnection::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( mk_request(r#"{"TableName":"test-table","Limit":32}"#), mk_response( r#"{ - "Count": 1, - "Items": [{ - "PostedBy": { - "S": "joe@example.com" - } - }], - "LastEvaluatedKey": { - "PostedBy": { "S": "joe@example.com" } - } - }"#, + "Count": 1, + "Items": [{ + "PostedBy": { + "S": "joe@example.com" + } + }], + "LastEvaluatedKey": { + "PostedBy": { "S": "joe@example.com" } + } + }"#, ), )]); - let client = Client::from_conf(stub_config(conn.clone())); + let client = Client::from_conf(stub_config(http_client.clone())); let mut rows = client .scan() .table_name("test-table") @@ -186,19 +188,19 @@ async fn paginators_stop_on_duplicate_token_by_default() { } }"#; // send the same response twice with the same pagination token - let conn = TestConnection::new(vec![ - ( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( mk_request(r#"{"TableName":"test-table","Limit":32}"#), mk_response(response), ), - ( + ReplayEvent::new( mk_request( r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, ), mk_response(response), ), ]); - let client = Client::from_conf(stub_config(conn.clone())); + let client = Client::from_conf(stub_config(http_client.clone())); let mut rows = client .scan() .table_name("test-table") @@ -239,25 +241,25 @@ async fn paginators_can_continue_on_duplicate_token() { } }"#; // send the same response twice with the same pagination token - let conn = TestConnection::new(vec![ - ( + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new( mk_request(r#"{"TableName":"test-table","Limit":32}"#), mk_response(response), ), - ( + ReplayEvent::new( mk_request( r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, ), mk_response(response), ), - ( + ReplayEvent::new( mk_request( r#"{"TableName":"test-table","Limit":32,"ExclusiveStartKey":{"PostedBy":{"S":"joe@example.com"}}}"#, ), mk_response(response), ), ]); - let client = Client::from_conf(stub_config(conn.clone())); + let client = Client::from_conf(stub_config(http_client.clone())); let mut rows = client .scan() .table_name("test-table") diff --git a/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs b/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs index 211e8630a0..3e95a87c35 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/retries-with-client-rate-limiting.rs @@ -7,8 +7,8 @@ use aws_sdk_dynamodb::config::{Credentials, Region, SharedAsyncSleep}; use aws_sdk_dynamodb::{config::retry::RetryConfig, error::ProvideErrorMetadata}; use aws_smithy_async::test_util::instant_time_and_sleep; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_smithy_runtime::client::retries::RetryPartition; use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, HttpResponse}; use std::time::{Duration, SystemTime}; @@ -51,20 +51,20 @@ async fn test_adaptive_retries_with_no_throttling_errors() { let events = vec![ // First operation - (req(), err()), - (req(), err()), - (req(), ok()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), ok()), // Second operation - (req(), err()), - (req(), ok()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), ok()), // Third operation will fail, only errors - (req(), err()), - (req(), err()), - (req(), err()), - (req(), err()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), err()), ]; - let conn = TestConnection::new(events); + let http_client = StaticReplayClient::new(events); let config = aws_sdk_dynamodb::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) @@ -78,7 +78,7 @@ async fn test_adaptive_retries_with_no_throttling_errors() { .retry_partition(RetryPartition::new( "test_adaptive_retries_with_no_throttling_errors", )) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let expected_table_names = vec!["Test".to_owned()]; @@ -88,21 +88,21 @@ async fn test_adaptive_retries_with_no_throttling_errors() { assert_eq!(sleep_impl.total_duration(), Duration::from_secs(3)); assert_eq!(res.table_names(), expected_table_names.as_slice()); // Three requests should have been made, two failing & one success - assert_eq!(conn.requests().len(), 3); + assert_eq!(http_client.actual_requests().count(), 3); let client = aws_sdk_dynamodb::Client::from_conf(config.clone()); let res = client.list_tables().send().await.unwrap(); assert_eq!(sleep_impl.total_duration(), Duration::from_secs(3 + 1)); assert_eq!(res.table_names(), expected_table_names.as_slice()); // Two requests should have been made, one failing & one success (plus previous requests) - assert_eq!(conn.requests().len(), 5); + assert_eq!(http_client.actual_requests().count(), 5); let client = aws_sdk_dynamodb::Client::from_conf(config); let err = client.list_tables().send().await.unwrap_err(); assert_eq!(sleep_impl.total_duration(), Duration::from_secs(3 + 1 + 7),); assert_eq!(err.code(), Some("InternalServerError")); // four requests should have been made, all failing (plus previous requests) - assert_eq!(conn.requests().len(), 9); + assert_eq!(http_client.actual_requests().count(), 9); } #[tokio::test] @@ -111,15 +111,15 @@ async fn test_adaptive_retries_with_throttling_errors() { let events = vec![ // First operation - (req(), throttling_err()), - (req(), throttling_err()), - (req(), ok()), + ReplayEvent::new(req(), throttling_err()), + ReplayEvent::new(req(), throttling_err()), + ReplayEvent::new(req(), ok()), // Second operation - (req(), err()), - (req(), ok()), + ReplayEvent::new(req(), err()), + ReplayEvent::new(req(), ok()), ]; - let conn = TestConnection::new(events); + let http_client = StaticReplayClient::new(events); let config = aws_sdk_dynamodb::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) @@ -133,7 +133,7 @@ async fn test_adaptive_retries_with_throttling_errors() { .retry_partition(RetryPartition::new( "test_adaptive_retries_with_throttling_errors", )) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let expected_table_names = vec!["Test".to_owned()]; @@ -143,7 +143,7 @@ async fn test_adaptive_retries_with_throttling_errors() { assert_eq!(sleep_impl.total_duration(), Duration::from_secs(40)); assert_eq!(res.table_names(), expected_table_names.as_slice()); // Three requests should have been made, two failing & one success - assert_eq!(conn.requests().len(), 3); + assert_eq!(http_client.actual_requests().count(), 3); let client = aws_sdk_dynamodb::Client::from_conf(config.clone()); let res = client.list_tables().send().await.unwrap(); @@ -151,5 +151,5 @@ async fn test_adaptive_retries_with_throttling_errors() { assert!(Duration::from_secs(49) > sleep_impl.total_duration()); assert_eq!(res.table_names(), expected_table_names.as_slice()); // Two requests should have been made, one failing & one success (plus previous requests) - assert_eq!(conn.requests().len(), 5); + assert_eq!(http_client.actual_requests().count(), 5); } diff --git a/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs b/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs index 3d5edf8cb2..0ce9d0c9de 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/shared-config.rs @@ -4,6 +4,7 @@ */ use aws_sdk_dynamodb::config::{Credentials, Region}; +use aws_smithy_runtime::client::http::test_util::capture_request; use http::Uri; /// Iterative test of loading clients from shared configuration @@ -12,10 +13,10 @@ async fn shared_config_testbed() { let shared_config = aws_types::SdkConfig::builder() .region(Region::new("us-east-4")) .build(); - let (conn, request) = aws_smithy_client::test_connection::capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_dynamodb::config::Builder::from(&shared_config) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .endpoint_url("http://localhost:8000") .build(); let svc = aws_sdk_dynamodb::Client::from_conf(conf); diff --git a/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs b/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs index d1a9b9369e..abd63673a5 100644 --- a/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs +++ b/aws/sdk/integration-tests/dynamodb/tests/timeouts.rs @@ -9,7 +9,7 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_credential_types::Credentials; use aws_sdk_dynamodb::error::SdkError; use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep, Sleep}; -use aws_smithy_client::never::NeverConnector; +use aws_smithy_runtime::client::http::test_util::NeverClient; use aws_smithy_types::retry::RetryConfig; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::region::Region; @@ -25,10 +25,10 @@ impl AsyncSleep for InstantSleep { #[tokio::test] async fn api_call_timeout_retries() { - let conn = NeverConnector::new(); + let http_client = NeverClient::new(); let conf = SdkConfig::builder() .region(Region::new("us-east-2")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .timeout_config( TimeoutConfig::builder() @@ -45,7 +45,7 @@ async fn api_call_timeout_retries() { .await .expect_err("call should fail"); assert_eq!( - conn.num_calls(), + http_client.num_calls(), 3, "client level timeouts should be retried" ); @@ -58,10 +58,10 @@ async fn api_call_timeout_retries() { #[tokio::test] async fn no_retries_on_operation_timeout() { - let conn = NeverConnector::new(); + let http_client = NeverClient::new(); let conf = SdkConfig::builder() .region(Region::new("us-east-2")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .timeout_config( TimeoutConfig::builder() @@ -78,7 +78,7 @@ async fn no_retries_on_operation_timeout() { .await .expect_err("call should fail"); assert_eq!( - conn.num_calls(), + http_client.num_calls(), 1, "operation level timeouts should not be retried" ); diff --git a/aws/sdk/integration-tests/ec2/Cargo.toml b/aws/sdk/integration-tests/ec2/Cargo.toml index 853b1b594f..9e2757bea2 100644 --- a/aws/sdk/integration-tests/ec2/Cargo.toml +++ b/aws/sdk/integration-tests/ec2/Cargo.toml @@ -8,8 +8,11 @@ publish = false [dev-dependencies] aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } +aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async" } +aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } +aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["client"] } aws-sdk-ec2 = { path = "../../build/aws-sdk/sdk/ec2" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util"]} tokio = { version = "1.23.1", features = ["full"]} http = "0.2.0" tokio-stream = "0.1.5" diff --git a/aws/sdk/integration-tests/ec2/tests/paginators.rs b/aws/sdk/integration-tests/ec2/tests/paginators.rs index d070971a4f..a9ab25a4a1 100644 --- a/aws/sdk/integration-tests/ec2/tests/paginators.rs +++ b/aws/sdk/integration-tests/ec2/tests/paginators.rs @@ -4,14 +4,15 @@ */ use aws_sdk_ec2::{config::Credentials, config::Region, types::InstanceType, Client, Config}; -use aws_smithy_client::http_connector::HttpConnector; -use aws_smithy_client::test_connection::TestConnection; +use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; +use aws_smithy_runtime_api::client::http::HttpClient; -fn stub_config(conn: impl Into) -> Config { +fn stub_config(http_client: impl HttpClient + 'static) -> Config { Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build() } @@ -27,17 +28,17 @@ async fn paginators_handle_empty_tokens() { "#; - let conn = TestConnection::<&str>::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .uri("https://ec2.us-east-1.amazonaws.com/") .body(request.into()) .unwrap(), http::Response::builder() .status(200) - .body(response) + .body(SdkBody::from(response)) .unwrap(), )]); - let client = Client::from_conf(stub_config(conn.clone())); + let client = Client::from_conf(stub_config(http_client.clone())); let instance_type = InstanceType::from("g5.48xlarge"); let mut paginator = client .describe_spot_price_history() @@ -49,7 +50,7 @@ async fn paginators_handle_empty_tokens() { .send(); let first_item = paginator.try_next().await.expect("success"); assert_eq!(first_item, None); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } /// See https://github.com/awslabs/aws-sdk-rust/issues/405 @@ -63,17 +64,17 @@ async fn paginators_handle_unset_tokens() { edf3e86c-4baf-47c1-9228-9a5ea09542e8 "#; - let conn = TestConnection::<&str>::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .uri("https://ec2.us-east-1.amazonaws.com/") .body(request.into()) .unwrap(), http::Response::builder() .status(200) - .body(response) + .body(SdkBody::from(response)) .unwrap(), )]); - let client = Client::from_conf(stub_config(conn.clone())); + let client = Client::from_conf(stub_config(http_client.clone())); let instance_type = InstanceType::from("g5.48xlarge"); let mut paginator = client .describe_spot_price_history() @@ -85,5 +86,5 @@ async fn paginators_handle_unset_tokens() { .send(); let first_item = paginator.try_next().await.expect("success"); assert_eq!(first_item, None); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } diff --git a/aws/sdk/integration-tests/glacier/Cargo.toml b/aws/sdk/integration-tests/glacier/Cargo.toml index 4c4ce34887..ca08a6e349 100644 --- a/aws/sdk/integration-tests/glacier/Cargo.toml +++ b/aws/sdk/integration-tests/glacier/Cargo.toml @@ -14,8 +14,8 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http"} aws-sdk-glacier = { path = "../../build/aws-sdk/sdk/glacier" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-protocol-test = { path = "../../build/aws-sdk/sdk/aws-smithy-protocol-test"} +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } bytes = "1.0.0" http = "0.2.0" tokio = { version = "1.23.1", features = ["full", "test-util"]} diff --git a/aws/sdk/integration-tests/glacier/tests/custom-headers.rs b/aws/sdk/integration-tests/glacier/tests/custom-headers.rs index 941ed0a999..c2cd3384ec 100644 --- a/aws/sdk/integration-tests/glacier/tests/custom-headers.rs +++ b/aws/sdk/integration-tests/glacier/tests/custom-headers.rs @@ -5,16 +5,16 @@ use aws_sdk_glacier::config::{Credentials, Region}; use aws_sdk_glacier::primitives::ByteStream; -use aws_smithy_client::test_connection::capture_request; use aws_smithy_protocol_test::{assert_ok, validate_headers}; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn set_correct_headers() { - let (conn, handler) = capture_request(None); + let (http_client, handler) = capture_request(None); let conf = aws_sdk_glacier::Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build(); let client = aws_sdk_glacier::Client::from_conf(conf); @@ -42,11 +42,11 @@ async fn set_correct_headers() { #[tokio::test] async fn autofill_account_id() { - let (conn, handler) = capture_request(None); + let (http_client, handler) = capture_request(None); let conf = aws_sdk_glacier::Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build(); let client = aws_sdk_glacier::Client::from_conf(conf); @@ -65,11 +65,11 @@ async fn autofill_account_id() { #[tokio::test] async fn api_version_set() { - let (conn, handler) = capture_request(None); + let (http_client, handler) = capture_request(None); let conf = aws_sdk_glacier::Config::builder() .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build(); let client = aws_sdk_glacier::Client::from_conf(conf); diff --git a/aws/sdk/integration-tests/iam/Cargo.toml b/aws/sdk/integration-tests/iam/Cargo.toml index 9c7b6b7464..e1d358ea44 100644 --- a/aws/sdk/integration-tests/iam/Cargo.toml +++ b/aws/sdk/integration-tests/iam/Cargo.toml @@ -15,7 +15,7 @@ aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", aws-endpoint = { path = "../../build/aws-sdk/sdk/aws-endpoint"} aws-http = { path = "../../build/aws-sdk/sdk/aws-http"} aws-sdk-iam = { path = "../../build/aws-sdk/sdk/iam" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } bytes = "1.0.0" http = "0.2.0" diff --git a/aws/sdk/integration-tests/iam/tests/resolve-global-endpoint.rs b/aws/sdk/integration-tests/iam/tests/resolve-global-endpoint.rs index 923bf568f6..6ef167ba0a 100644 --- a/aws/sdk/integration-tests/iam/tests/resolve-global-endpoint.rs +++ b/aws/sdk/integration-tests/iam/tests/resolve-global-endpoint.rs @@ -4,18 +4,18 @@ */ use aws_sdk_iam::config::{Credentials, Region}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; // this test is ignored because pseudoregions have been removed. This test should be re-enabled // once FIPS support is added in aws-config #[tokio::test] #[ignore] async fn correct_endpoint_resolver() { - let (conn, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_iam::Config::builder() .region(Region::from_static("iam-fips")) .credentials_provider(Credentials::for_tests()) - .http_connector(conn) + .http_client(http_client) .build(); let client = aws_sdk_iam::Client::from_conf(conf); let _ = client.list_roles().send().await; diff --git a/aws/sdk/integration-tests/kms/Cargo.toml b/aws/sdk/integration-tests/kms/Cargo.toml index 2c76644e94..a26e2dd672 100644 --- a/aws/sdk/integration-tests/kms/Cargo.toml +++ b/aws/sdk/integration-tests/kms/Cargo.toml @@ -15,10 +15,10 @@ aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-runtime = { path = "../../build/aws-sdk/sdk/aws-runtime" } aws-sdk-kms = { path = "../../build/aws-sdk/sdk/kms" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["test-util"] } bytes = "1.0.0" http = "0.2.0" diff --git a/aws/sdk/integration-tests/kms/tests/integration.rs b/aws/sdk/integration-tests/kms/tests/integration.rs index 9125ec12e9..d550337ec5 100644 --- a/aws/sdk/integration-tests/kms/tests/integration.rs +++ b/aws/sdk/integration-tests/kms/tests/integration.rs @@ -5,9 +5,9 @@ use aws_sdk_kms as kms; use aws_sdk_kms::operation::RequestId; -use aws_smithy_client::test_connection::TestConnection; -use aws_smithy_client::SdkError; use aws_smithy_http::body::SdkBody; +use aws_smithy_http::result::SdkError; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use http::header::AUTHORIZATION; use http::Uri; use kms::config::{Config, Credentials, Region}; @@ -20,16 +20,16 @@ use std::time::{Duration, UNIX_EPOCH}; /// Validate that for CN regions we set the URI correctly #[tokio::test] async fn generate_random_cn() { - let conn = TestConnection::new(vec![( + let http_client= StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .uri(Uri::from_static("https://kms.cn-north-1.amazonaws.com.cn/")) .body(SdkBody::from(r#"{"NumberOfBytes":64}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#).unwrap()) + .body(SdkBody::from(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#)).unwrap()) ]); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("cn-north-1")) .credentials_provider(Credentials::for_tests()) .build(); @@ -41,13 +41,13 @@ async fn generate_random_cn() { .await .expect("success"); - assert_eq!(conn.requests().len(), 1); - conn.assert_requests_match(&[]); + assert_eq!(http_client.actual_requests().count(), 1); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn generate_random() { - let conn = TestConnection::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.1") .header("x-amz-target", "TrentService.GenerateRandom") @@ -61,10 +61,10 @@ async fn generate_random() { .body(SdkBody::from(r#"{"NumberOfBytes":64}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#).unwrap()) + .body(SdkBody::from(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA=="}"#)).unwrap()) ]); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests_with_session_token()) .build(); @@ -94,20 +94,20 @@ async fn generate_random() { .sum::(), 8562 ); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } #[tokio::test] async fn generate_random_malformed_response() { - let conn = TestConnection::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder().body(SdkBody::from(r#"{"NumberOfBytes":64}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) // last `}` replaced with a space, invalid JSON - .body(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA==" "#).unwrap()) + .body(SdkBody::from(r#"{"Plaintext":"6CG0fbzzhg5G2VcFCPmJMJ8Njv3voYCgrGlp3+BZe7eDweCXgiyDH9BnkKvLmS7gQhnYDUlyES3fZVGwv5+CxA==" "#)).unwrap()) ]); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests()) .build(); @@ -122,7 +122,7 @@ async fn generate_random_malformed_response() { #[tokio::test] async fn generate_random_keystore_not_found() { - let conn = TestConnection::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.1") .header("x-amz-target", "TrentService.GenerateRandom") @@ -143,10 +143,10 @@ async fn generate_random_keystore_not_found() { .header("date", "Fri, 05 Mar 2021 15:01:40 GMT") .header("content-type", "application/x-amz-json-1.1") .header("content-length", "44") - .body(r#"{"__type":"CustomKeyStoreNotFoundException"}"#).unwrap()) + .body(SdkBody::from(r#"{"__type":"CustomKeyStoreNotFoundException"}"#)).unwrap()) ]); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests_with_session_token()) .build(); @@ -174,5 +174,5 @@ async fn generate_random_keystore_not_found() { inner.request_id(), Some("bfe81a0a-9a08-4e71-9910-cdb5ab6ea3b6") ); - conn.assert_requests_match(&[AUTHORIZATION]); + http_client.assert_requests_match(&[AUTHORIZATION]); } diff --git a/aws/sdk/integration-tests/kms/tests/retryable_errors.rs b/aws/sdk/integration-tests/kms/tests/retryable_errors.rs index 5eee47d9d2..1b932e4be4 100644 --- a/aws/sdk/integration-tests/kms/tests/retryable_errors.rs +++ b/aws/sdk/integration-tests/kms/tests/retryable_errors.rs @@ -6,8 +6,8 @@ use aws_credential_types::Credentials; use aws_runtime::retries::classifier::AwsErrorCodeClassifier; use aws_sdk_kms as kms; -use aws_smithy_client::test_connection::infallible_connection_fn; use aws_smithy_http::result::SdkError; +use aws_smithy_runtime::client::http::test_util::infallible_client_fn; use aws_smithy_runtime_api::client::interceptors::context::{Error, Input, InterceptorContext}; use aws_smithy_runtime_api::client::orchestrator::{HttpResponse, OrchestratorError}; use aws_smithy_runtime_api::client::retries::{ClassifyRetry, RetryReason}; @@ -18,9 +18,9 @@ use kms::operation::create_alias::CreateAliasError; async fn make_err( response: impl Fn() -> http::Response + Send + Sync + 'static, ) -> SdkError { - let conn = infallible_connection_fn(move |_| response()); + let http_client = infallible_client_fn(move |_| response()); let conf = kms::Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(kms::config::Region::from_static("us-east-1")) .build(); diff --git a/aws/sdk/integration-tests/lambda/Cargo.toml b/aws/sdk/integration-tests/lambda/Cargo.toml index 6e51d089f5..5f6cc8acbd 100644 --- a/aws/sdk/integration-tests/lambda/Cargo.toml +++ b/aws/sdk/integration-tests/lambda/Cargo.toml @@ -9,12 +9,12 @@ publish = false [dev-dependencies] async-stream = "0.3.0" -aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } +aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-lambda = { path = "../../build/aws-sdk/sdk/lambda" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-eventstream = { path = "../../build/aws-sdk/sdk/aws-smithy-eventstream" } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } base64 = "0.13.0" bytes = "1.0.0" futures-core = "0.3.14" diff --git a/aws/sdk/integration-tests/lambda/tests/request_id.rs b/aws/sdk/integration-tests/lambda/tests/request_id.rs index b4204b0888..6a6e1384ae 100644 --- a/aws/sdk/integration-tests/lambda/tests/request_id.rs +++ b/aws/sdk/integration-tests/lambda/tests/request_id.rs @@ -7,15 +7,15 @@ use aws_sdk_lambda::config::{Credentials, Region}; use aws_sdk_lambda::operation::list_functions::ListFunctionsError; use aws_sdk_lambda::operation::RequestId; use aws_sdk_lambda::{Client, Config}; -use aws_smithy_client::test_connection::infallible_connection_fn; +use aws_smithy_runtime::client::http::test_util::infallible_client_fn; async fn run_test( response: impl Fn() -> http::Response<&'static str> + Send + Sync + 'static, expect_error: bool, ) { - let conn = infallible_connection_fn(move |_| response()); + let http_client = infallible_client_fn(move |_| response()); let conf = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::from_static("us-east-1")) .build(); diff --git a/aws/sdk/integration-tests/no-default-features/tests/client-construction.rs b/aws/sdk/integration-tests/no-default-features/tests/client-construction.rs index f90ca59fb1..e7d11dbc31 100644 --- a/aws/sdk/integration-tests/no-default-features/tests/client-construction.rs +++ b/aws/sdk/integration-tests/no-default-features/tests/client-construction.rs @@ -12,7 +12,9 @@ use std::time::Duration; // If this test doesn't panic, you may have accidentally unified features, resulting in // the connector being enabled transitively #[tokio::test] -#[should_panic(expected = "Enable the `rustls` crate feature or set a connector to fix this.")] +#[should_panic( + expected = "Enable the `rustls` crate feature or configure a HTTP client to fix this." +)] async fn test_clients_from_sdk_config() { aws_config::load_from_env().await; } @@ -42,10 +44,10 @@ async fn test_clients_from_service_config() { .list_buckets() .send() .await - .expect_err("it should fail to send a request because there is no connector"); + .expect_err("it should fail to send a request because there is no HTTP client"); let msg = format!("{}", DisplayErrorContext(err)); assert!( - msg.contains("No HTTP connector was available to send this request. Enable the `rustls` crate feature or set a connector to fix this."), - "expected '{msg}' to contain 'No HTTP connector was available to send this request. Enable the `rustls` crate feature or set a connector to fix this.'" + msg.contains("No HTTP client was available to send this request. Enable the `rustls` crate feature or configure a HTTP client to fix this."), + "expected '{msg}' to contain 'No HTTP client was available to send this request. Enable the `rustls` crate feature or set a HTTP client to fix this.'" ); } diff --git a/aws/sdk/integration-tests/polly/Cargo.toml b/aws/sdk/integration-tests/polly/Cargo.toml index 5412a3dcaf..444c65beaf 100644 --- a/aws/sdk/integration-tests/polly/Cargo.toml +++ b/aws/sdk/integration-tests/polly/Cargo.toml @@ -14,7 +14,6 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http"} aws-sdk-polly = { path = "../../build/aws-sdk/sdk/polly" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } bytes = "1.0.0" http = "0.2.0" diff --git a/aws/sdk/integration-tests/qldbsession/Cargo.toml b/aws/sdk/integration-tests/qldbsession/Cargo.toml index ef09721a84..59256fb6cd 100644 --- a/aws/sdk/integration-tests/qldbsession/Cargo.toml +++ b/aws/sdk/integration-tests/qldbsession/Cargo.toml @@ -14,8 +14,9 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-qldbsession = { path = "../../build/aws-sdk/sdk/qldbsession" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } +aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async" } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } http = "0.2.0" tokio = { version = "1.23.1", features = ["full"]} diff --git a/aws/sdk/integration-tests/qldbsession/tests/integration.rs b/aws/sdk/integration-tests/qldbsession/tests/integration.rs index 816f3cd8fb..3c7a7424fb 100644 --- a/aws/sdk/integration-tests/qldbsession/tests/integration.rs +++ b/aws/sdk/integration-tests/qldbsession/tests/integration.rs @@ -6,20 +6,20 @@ use aws_sdk_qldbsession::config::{Config, Credentials, Region}; use aws_sdk_qldbsession::types::StartSessionRequest; use aws_sdk_qldbsession::Client; -use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use http::Uri; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn signv4_use_correct_service_name() { - let conn = TestConnection::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("content-type", "application/x-amz-json-1.0") .header("x-amz-target", "QLDBSession.SendCommand") .header("content-length", "49") .header("authorization", "AWS4-HMAC-SHA256 Credential=ANOTREAL/20210305/us-east-1/qldb/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-amz-security-token;x-amz-target;x-amz-user-agent, Signature=350f957e9b736ac3f636d16c59c0a3cee8c2780b0ffadc99bbca841b7f15bee4") - // qldbsession uses the signing name 'qldb' in signature ____________________________________^^^^ + // qldbsession uses the signing name 'qldb' in signature _________________________^^^^ .header("x-amz-date", "20210305T134922Z") .header("x-amz-security-token", "notarealsessiontoken") .header("user-agent", "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0") @@ -27,10 +27,10 @@ async fn signv4_use_correct_service_name() { .body(SdkBody::from(r#"{"StartSession":{"LedgerName":"not-real-ledger"}}"#)).unwrap(), http::Response::builder() .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"{}"#).unwrap()), + .body(SdkBody::from(r#"{}"#)).unwrap()), ]); let conf = Config::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .credentials_provider(Credentials::for_tests_with_session_token()) .build(); @@ -58,5 +58,5 @@ async fn signv4_use_correct_service_name() { .await .expect("request should succeed"); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } diff --git a/aws/sdk/integration-tests/s3/Cargo.toml b/aws/sdk/integration-tests/s3/Cargo.toml index 74453e21e6..a525f69815 100644 --- a/aws/sdk/integration-tests/s3/Cargo.toml +++ b/aws/sdk/integration-tests/s3/Cargo.toml @@ -19,10 +19,9 @@ aws-runtime = { path = "../../build/aws-sdk/sdk/aws-runtime", features = ["test- aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3" } aws-sdk-sts = { path = "../../build/aws-sdk/sdk/sts" } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util", "rt-tokio"] } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "wiremock"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-smithy-protocol-test = { path = "../../build/aws-sdk/sdk/aws-smithy-protocol-test" } -aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"] } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util", "wire-mock"] } aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["test-util"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } diff --git a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs index a83e9f38d9..7aee8af904 100644 --- a/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs +++ b/aws/sdk/integration-tests/s3/tests/alternative-async-runtime.rs @@ -13,8 +13,8 @@ use aws_sdk_s3::types::{ use aws_sdk_s3::{Client, Config}; use aws_smithy_async::assert_elapsed; use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep, Sleep}; -use aws_smithy_client::never::NeverConnector; use aws_smithy_http::result::SdkError; +use aws_smithy_runtime::client::http::test_util::NeverClient; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::timeout::TimeoutConfig; @@ -87,14 +87,14 @@ fn test_async_std_runtime_retry() { } async fn timeout_test(sleep_impl: SharedAsyncSleep) -> Result<(), Box> { - let conn = NeverConnector::new(); + let http_client = NeverClient::new(); let region = Region::from_static("us-east-2"); let timeout_config = TimeoutConfig::builder() .operation_timeout(Duration::from_secs_f32(0.5)) .build(); let config = Config::builder() .region(region) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(Credentials::for_tests()) .timeout_config(timeout_config) .sleep_impl(sleep_impl) @@ -141,10 +141,10 @@ async fn timeout_test(sleep_impl: SharedAsyncSleep) -> Result<(), Box Result<(), Box> { - let conn = NeverConnector::new(); + let http_client = NeverClient::new(); let conf = aws_types::SdkConfig::builder() .region(Region::new("us-east-2")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .retry_config(RetryConfig::standard().with_max_attempts(3)) .timeout_config( @@ -167,7 +167,7 @@ async fn retry_test(sleep_impl: SharedAsyncSleep) -> Result<(), Box TestConnection<&'static str> { - TestConnection::new(vec![ - (http::Request::builder() +) -> StaticReplayClient { + StaticReplayClient::new(vec![ + ReplayEvent::new(http::Request::builder() .header("x-amz-checksum-mode", "ENABLED") .header("user-agent", "aws-sdk-rust/0.123.test os/windows/XPSP3 lang/rust/1.50.0") .header("x-amz-date", "20210618T170728Z") @@ -45,7 +47,7 @@ fn new_checksum_validated_response_test_connection( .header("x-amz-id-2", "kPl+IVVZAwsN8ePUyQJZ40WD9dzaqtr4eNESArqE68GSKtVvuvCTDe+SxhTT+JTUqXB1HL4OxNM=") .header("accept-ranges", "bytes") .status(http::StatusCode::from_u16(200).unwrap()) - .body(r#"Hello world"#).unwrap()), + .body(SdkBody::from(r#"Hello world"#)).unwrap()), ]) } @@ -53,7 +55,7 @@ async fn test_checksum_on_streaming_response( checksum_header_name: &'static str, checksum_header_value: &'static str, ) -> GetObjectOutput { - let conn = new_checksum_validated_response_test_connection( + let http_client = new_checksum_validated_response_test_connection( checksum_header_name, checksum_header_value, ); @@ -61,7 +63,7 @@ async fn test_checksum_on_streaming_response( .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .time_source(UNIX_EPOCH + Duration::from_secs(1624036048)) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); @@ -79,7 +81,7 @@ async fn test_checksum_on_streaming_response( .await .unwrap(); - conn.assert_requests_match(&[ + http_client.assert_requests_match(&[ http::header::HeaderName::from_static("x-amz-checksum-mode"), AUTHORIZATION, ]); @@ -149,11 +151,11 @@ async fn test_checksum_on_streaming_request<'a>( expected_encoded_content_length: &'a str, expected_aws_chunked_encoded_body: &'a str, ) { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); @@ -330,7 +332,7 @@ async fn collect_body_into_string(mut body: aws_smithy_http::body::SdkBody) -> S #[traced_test] async fn test_get_multipart_upload_part_checksum_validation() { let expected_checksum = "cpjwid==-12"; - let (conn, rcvr) = capture_request(Some( + let (http_client, rcvr) = capture_request(Some( http::Response::builder() .header("etag", "\"3e25960a79dbc69b674cd4ec67a72c62\"") .header("x-amz-checksum-crc32", expected_checksum) @@ -340,7 +342,7 @@ async fn test_get_multipart_upload_part_checksum_validation() { let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); @@ -376,7 +378,7 @@ async fn test_get_multipart_upload_part_checksum_validation() { #[traced_test] async fn test_response_checksum_ignores_invalid_base64() { let expected_checksum = "{}{!!#{})!{)@$(}"; - let (conn, rcvr) = capture_request(Some( + let (http_client, rcvr) = capture_request(Some( http::Response::builder() .header("etag", "\"3e25960a79dbc69b674cd4ec67a72c62\"") .header("x-amz-checksum-crc32", expected_checksum) @@ -386,7 +388,7 @@ async fn test_response_checksum_ignores_invalid_base64() { let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/config-override.rs b/aws/sdk/integration-tests/s3/tests/config-override.rs index 28092f37ce..44c7006888 100644 --- a/aws/sdk/integration-tests/s3/tests/config-override.rs +++ b/aws/sdk/integration-tests/s3/tests/config-override.rs @@ -6,15 +6,15 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::{capture_request, CaptureRequestReceiver}; +use aws_smithy_runtime::client::http::test_util::{capture_request, CaptureRequestReceiver}; use aws_types::SdkConfig; fn test_client() -> (CaptureRequestReceiver, Client) { - let (conn, captured_request) = capture_request(None); + let (http_client, captured_request) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-west-2")) - .http_connector(conn) + .http_client(http_client) .build(); let client = Client::new(&sdk_config); (captured_request, client) diff --git a/aws/sdk/integration-tests/s3/tests/customizable-operation.rs b/aws/sdk/integration-tests/s3/tests/customizable-operation.rs index 7621393eca..6bd55816c0 100644 --- a/aws/sdk/integration-tests/s3/tests/customizable-operation.rs +++ b/aws/sdk/integration-tests/s3/tests/customizable-operation.rs @@ -7,19 +7,18 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; - +use aws_smithy_runtime::client::http::test_util::capture_request; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_s3_ops_are_customizable() { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/endpoints.rs b/aws/sdk/integration-tests/s3/tests/endpoints.rs index 7a76b24087..5ac1ac7ad8 100644 --- a/aws/sdk/integration-tests/s3/tests/endpoints.rs +++ b/aws/sdk/integration-tests/s3/tests/endpoints.rs @@ -8,15 +8,15 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::Builder; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::{capture_request, CaptureRequestReceiver}; +use aws_smithy_runtime::client::http::test_util::{capture_request, CaptureRequestReceiver}; use std::time::{Duration, UNIX_EPOCH}; fn test_client(update_builder: fn(Builder) -> Builder) -> (CaptureRequestReceiver, Client) { - let (conn, captured_request) = capture_request(None); + let (http_client, captured_request) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-west-4")) - .http_connector(conn) + .http_client(http_client) .build(); let client = Client::from_conf(update_builder(Builder::from(&sdk_config)).build()); (captured_request, client) diff --git a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs index 07bf543b8c..b120a34188 100644 --- a/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs +++ b/aws/sdk/integration-tests/s3/tests/ignore-invalid-xml-body-root.rs @@ -5,8 +5,8 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::{config::Credentials, config::Region, types::ObjectAttributes, Client}; -use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_types::SdkConfig; use http::header::AUTHORIZATION; use std::time::{Duration, UNIX_EPOCH}; @@ -15,8 +15,8 @@ const RESPONSE_BODY_XML: &[u8] = b"\n< #[tokio::test] async fn ignore_invalid_xml_body_root() { - let conn = TestConnection::new(vec![ - (http::Request::builder() + let http_client = StaticReplayClient::new(vec![ + ReplayEvent::new(http::Request::builder() .header("x-amz-object-attributes", "Checksum") .header("x-amz-user-agent", "aws-sdk-rust/0.123.test api/test-service/0.123 os/windows/XPSP3 lang/rust/1.50.0") .header("x-amz-date", "20210618T170728Z") @@ -37,7 +37,7 @@ async fn ignore_invalid_xml_body_root() { .header("server", "AmazonS3") .header("content-length", "224") .status(200) - .body(RESPONSE_BODY_XML) + .body(SdkBody::from(RESPONSE_BODY_XML)) .unwrap()) ]); @@ -46,7 +46,7 @@ async fn ignore_invalid_xml_body_root() { Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); @@ -64,5 +64,5 @@ async fn ignore_invalid_xml_body_root() { .await .unwrap(); - conn.assert_requests_match(&[AUTHORIZATION]); + http_client.assert_requests_match(&[AUTHORIZATION]); } diff --git a/aws/sdk/integration-tests/s3/tests/interceptors.rs b/aws/sdk/integration-tests/s3/tests/interceptors.rs index 825f895fac..62bc29b039 100644 --- a/aws/sdk/integration-tests/s3/tests/interceptors.rs +++ b/aws/sdk/integration-tests/s3/tests/interceptors.rs @@ -6,8 +6,7 @@ use aws_sdk_s3::config::interceptors::BeforeTransmitInterceptorContextMut; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::erase::DynConnector; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::interceptors::Interceptor; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; @@ -57,13 +56,13 @@ async fn interceptor_priority() { } } - let (conn, rx) = capture_request(None); + let (http_client, rx) = capture_request(None); // The first `TestInterceptor` will put `value1` into config let config = aws_sdk_s3::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) - .http_connector(DynConnector::new(conn)) + .http_client(http_client) .interceptor(TestInterceptor("value1")) .build(); let client = Client::from_conf(config); @@ -89,12 +88,12 @@ async fn interceptor_priority() { #[tokio::test] async fn set_test_user_agent_through_request_mutation() { - let (conn, rx) = capture_request(None); + let (http_client, rx) = capture_request(None); let config = aws_sdk_s3::Config::builder() .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) - .http_connector(DynConnector::new(conn.clone())) + .http_client(http_client.clone()) .build(); let client = Client::from_conf(config); diff --git a/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs b/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs index e67da16c27..209fd6bcde 100644 --- a/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs +++ b/aws/sdk/integration-tests/s3/tests/naughty-string-metadata.rs @@ -5,7 +5,7 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::{config::Credentials, config::Region, primitives::ByteStream, Client}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use aws_types::SdkConfig; use http::HeaderValue; use std::time::{Duration, UNIX_EPOCH}; @@ -48,13 +48,13 @@ const NAUGHTY_STRINGS: &str = include_str!("blns/blns.txt"); #[tokio::test] async fn test_s3_signer_with_naughty_string_metadata() { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let config = aws_sdk_s3::config::Builder::from(&sdk_config) .force_path_style(true) diff --git a/aws/sdk/integration-tests/s3/tests/no_auth.rs b/aws/sdk/integration-tests/s3/tests/no_auth.rs index b558a90494..670d85268c 100644 --- a/aws/sdk/integration-tests/s3/tests/no_auth.rs +++ b/aws/sdk/integration-tests/s3/tests/no_auth.rs @@ -3,17 +3,17 @@ * SPDX-License-Identifier: Apache-2.0 */ -use aws_smithy_client::dvr::ReplayingConnection; use aws_smithy_protocol_test::MediaType; +use aws_smithy_runtime::client::http::test_util::dvr::ReplayingClient; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; #[tokio::test] async fn list_objects() { let _logs = capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/data/no_auth/list-objects.json").unwrap(); + let http_client = ReplayingClient::from_file("tests/data/no_auth/list-objects.json").unwrap(); let config = aws_config::from_env() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .no_credentials() .region("us-east-1") .load() @@ -33,7 +33,8 @@ async fn list_objects() { .await; dbg!(result).expect("success"); - conn.validate_body_and_headers(None, MediaType::Xml) + http_client + .validate_body_and_headers(None, MediaType::Xml) .await .unwrap(); } @@ -42,9 +43,10 @@ async fn list_objects() { async fn list_objects_v2() { let _logs = capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/data/no_auth/list-objects-v2.json").unwrap(); + let http_client = + ReplayingClient::from_file("tests/data/no_auth/list-objects-v2.json").unwrap(); let config = aws_config::from_env() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .no_credentials() .region("us-east-1") .load() @@ -64,7 +66,8 @@ async fn list_objects_v2() { .await; dbg!(result).expect("success"); - conn.validate_body_and_headers(None, MediaType::Xml) + http_client + .validate_body_and_headers(None, MediaType::Xml) .await .unwrap(); } @@ -73,9 +76,9 @@ async fn list_objects_v2() { async fn head_object() { let _logs = capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/data/no_auth/head-object.json").unwrap(); + let http_client = ReplayingClient::from_file("tests/data/no_auth/head-object.json").unwrap(); let config = aws_config::from_env() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .no_credentials() .region("us-east-1") .load() @@ -95,7 +98,8 @@ async fn head_object() { .await; dbg!(result).expect("success"); - conn.validate_body_and_headers(None, MediaType::Xml) + http_client + .validate_body_and_headers(None, MediaType::Xml) .await .unwrap(); } @@ -104,9 +108,9 @@ async fn head_object() { async fn get_object() { let _logs = capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/data/no_auth/get-object.json").unwrap(); + let http_client = ReplayingClient::from_file("tests/data/no_auth/get-object.json").unwrap(); let config = aws_config::from_env() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .no_credentials() .region("us-east-1") .load() @@ -126,7 +130,8 @@ async fn get_object() { .await; dbg!(result).expect("success"); - conn.validate_body_and_headers(None, MediaType::Xml) + http_client + .validate_body_and_headers(None, MediaType::Xml) .await .unwrap(); } diff --git a/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs b/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs index d2b6f4a000..e91af82846 100644 --- a/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs +++ b/aws/sdk/integration-tests/s3/tests/normalize-uri-path.rs @@ -7,18 +7,18 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::{config::Credentials, config::Region, Client}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_operation_should_not_normalize_uri_path() { - let (conn, rx) = capture_request(None); + let (http_client, rx) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs index bc67936924..67557f6e05 100644 --- a/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs +++ b/aws/sdk/integration-tests/s3/tests/query-strings-are-correctly-encoded.rs @@ -7,18 +7,18 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_s3_signer_query_string_with_all_valid_chars() { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/reconnects.rs b/aws/sdk/integration-tests/s3/tests/reconnects.rs index cb29bb2a66..5390cb2850 100644 --- a/aws/sdk/integration-tests/s3/tests/reconnects.rs +++ b/aws/sdk/integration-tests/s3/tests/reconnects.rs @@ -6,14 +6,12 @@ use aws_sdk_s3::config::retry::{ReconnectMode, RetryConfig}; use aws_sdk_s3::config::{Credentials, Region, SharedAsyncSleep}; use aws_smithy_async::rt::sleep::TokioSleep; -use aws_smithy_client::test_connection::wire_mock::{ - check_matches, ReplayedEvent, WireLevelTestConnection, -}; -use aws_smithy_client::{ev, match_events}; +use aws_smithy_runtime::client::http::test_util::wire::{ReplayedEvent, WireMockServer}; +use aws_smithy_runtime::{ev, match_events}; #[tokio::test] async fn test_disable_reconnect_on_503() { - let mock = WireLevelTestConnection::spinup(vec![ + let mock = WireMockServer::start(vec![ ReplayedEvent::status(503), ReplayedEvent::status(503), ReplayedEvent::with_body("here-is-your-object"), @@ -25,7 +23,7 @@ async fn test_disable_reconnect_on_503() { .credentials_provider(Credentials::for_tests()) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) .endpoint_url(mock.endpoint_url()) - .http_connector(mock.http_connector()) + .http_client(mock.http_client()) .retry_config( RetryConfig::standard().with_reconnect_mode(ReconnectMode::ReuseAllConnections), ) @@ -53,7 +51,7 @@ async fn test_disable_reconnect_on_503() { #[tokio::test] async fn test_enabling_reconnect_on_503() { - let mock = WireLevelTestConnection::spinup(vec![ + let mock = WireMockServer::start(vec![ ReplayedEvent::status(503), ReplayedEvent::status(503), ReplayedEvent::with_body("here-is-your-object"), @@ -65,7 +63,7 @@ async fn test_enabling_reconnect_on_503() { .credentials_provider(Credentials::for_tests()) .sleep_impl(SharedAsyncSleep::new(TokioSleep::new())) .endpoint_url(mock.endpoint_url()) - .http_connector(mock.http_connector()) + .http_client(mock.http_client()) .retry_config( RetryConfig::standard().with_reconnect_mode(ReconnectMode::ReconnectOnTransientError), ) diff --git a/aws/sdk/integration-tests/s3/tests/recursion-detection.rs b/aws/sdk/integration-tests/s3/tests/recursion-detection.rs index f0aa974d8e..5bd7b26522 100644 --- a/aws/sdk/integration-tests/s3/tests/recursion-detection.rs +++ b/aws/sdk/integration-tests/s3/tests/recursion-detection.rs @@ -7,18 +7,18 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use http::HeaderValue; #[tokio::test] async fn recursion_detection_applied() { std::env::set_var("AWS_LAMBDA_FUNCTION_NAME", "some-function"); std::env::set_var("_X_AMZN_TRACE_ID", "traceid"); - let (conn, captured_request) = capture_request(None); + let (http_client, captured_request) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); let _ = client.list_objects_v2().bucket("test-bucket").send().await; diff --git a/aws/sdk/integration-tests/s3/tests/request_id.rs b/aws/sdk/integration-tests/s3/tests/request_id.rs index 1df48038f2..d46bfe66da 100644 --- a/aws/sdk/integration-tests/s3/tests/request_id.rs +++ b/aws/sdk/integration-tests/s3/tests/request_id.rs @@ -6,12 +6,12 @@ use aws_sdk_s3::operation::get_object::GetObjectError; use aws_sdk_s3::operation::{RequestId, RequestIdExt}; use aws_sdk_s3::{config::Credentials, config::Region, Client, Config}; -use aws_smithy_client::test_connection::capture_request; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn get_request_id_from_modeled_error() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -28,7 +28,7 @@ async fn get_request_id_from_modeled_error() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -60,7 +60,7 @@ async fn get_request_id_from_modeled_error() { #[tokio::test] async fn get_request_id_from_unmodeled_error() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -77,7 +77,7 @@ async fn get_request_id_from_unmodeled_error() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -106,7 +106,7 @@ async fn get_request_id_from_unmodeled_error() { #[tokio::test] async fn get_request_id_from_successful_nonstreaming_response() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -121,7 +121,7 @@ async fn get_request_id_from_successful_nonstreaming_response() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -141,7 +141,7 @@ async fn get_request_id_from_successful_nonstreaming_response() { #[tokio::test] async fn get_request_id_from_successful_streaming_response() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -150,7 +150,7 @@ async fn get_request_id_from_successful_streaming_response() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -173,7 +173,7 @@ async fn get_request_id_from_successful_streaming_response() { // Verify that the conversion from operation error to the top-level service error maintains the request ID #[tokio::test] async fn conversion_to_service_error_maintains_request_id() { - let (conn, request) = capture_request(Some( + let (http_client, request) = capture_request(Some( http::Response::builder() .header("x-amz-request-id", "correct-request-id") .header("x-amz-id-2", "correct-extended-request-id") @@ -190,7 +190,7 @@ async fn conversion_to_service_error_maintains_request_id() { .unwrap(), )); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/request_information_headers.rs b/aws/sdk/integration-tests/s3/tests/request_information_headers.rs index 8c47e8b367..ab0e8d651d 100644 --- a/aws/sdk/integration-tests/s3/tests/request_information_headers.rs +++ b/aws/sdk/integration-tests/s3/tests/request_information_headers.rs @@ -15,9 +15,8 @@ use aws_sdk_s3::Client; use aws_smithy_async::test_util::InstantSleep; use aws_smithy_async::test_util::ManualTimeSource; use aws_smithy_async::time::SharedTimeSource; -use aws_smithy_client::dvr; -use aws_smithy_client::dvr::MediaType; -use aws_smithy_client::erase::DynConnector; +use aws_smithy_protocol_test::MediaType; +use aws_smithy_runtime::client::http::test_util::dvr::ReplayingClient; use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponents; @@ -72,11 +71,11 @@ async fn three_retries_and_then_success() { let time_source = ManualTimeSource::new(UNIX_EPOCH + Duration::from_secs(1559347200)); let path = "tests/data/request-information-headers/three-retries_and-then-success.json"; - let conn = dvr::ReplayingConnection::from_file(path).unwrap(); + let http_client = ReplayingClient::from_file(path).unwrap(); let config = aws_sdk_s3::Config::builder() .credentials_provider(Credentials::for_tests_with_session_token()) .region(Region::new("us-east-1")) - .http_connector(DynConnector::new(conn.clone())) + .http_client(http_client.clone()) .time_source(SharedTimeSource::new(time_source.clone())) .sleep_impl(SharedAsyncSleep::new(InstantSleep::new(Default::default()))) .retry_config(RetryConfig::standard()) @@ -104,7 +103,10 @@ async fn three_retries_and_then_success() { let resp = resp.expect("valid e2e test"); assert_eq!(resp.name(), Some("test-bucket")); - conn.full_validate(MediaType::Xml).await.expect("failed") + http_client + .full_validate(MediaType::Xml) + .await + .expect("failed") } // // // # Client makes 3 separate SDK operation invocations @@ -168,7 +170,7 @@ async fn three_retries_and_then_success() { // let config = aws_sdk_s3::Config::builder() // .credentials_provider(Credentials::for_tests()) // .region(Region::new("us-east-1")) -// .http_connector(DynConnector::new(conn.clone())) +// .http_client(DynConnector::new(conn.clone())) // .build(); // let client = Client::from_conf(config); // let fixup = FixupPlugin { @@ -259,7 +261,7 @@ async fn three_retries_and_then_success() { // let config = aws_sdk_s3::Config::builder() // .credentials_provider(Credentials::for_tests()) // .region(Region::new("us-east-1")) -// .http_connector(DynConnector::new(conn.clone())) +// .http_client(DynConnector::new(conn.clone())) // .build(); // let client = Client::from_conf(config); // let fixup = FixupPlugin { diff --git a/aws/sdk/integration-tests/s3/tests/required-query-params.rs b/aws/sdk/integration-tests/s3/tests/required-query-params.rs index b5fede83a0..1df7f44e4e 100644 --- a/aws/sdk/integration-tests/s3/tests/required-query-params.rs +++ b/aws/sdk/integration-tests/s3/tests/required-query-params.rs @@ -6,14 +6,14 @@ use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::error::DisplayErrorContext; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; use aws_smithy_http::operation::error::BuildError; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn test_error_when_required_query_param_is_unset() { - let (conn, _request) = capture_request(None); + let (http_client, _request) = capture_request(None); let config = aws_sdk_s3::Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); @@ -36,9 +36,9 @@ async fn test_error_when_required_query_param_is_unset() { #[tokio::test] async fn test_error_when_required_query_param_is_set_but_empty() { - let (conn, _request) = capture_request(None); + let (http_client, _request) = capture_request(None); let config = aws_sdk_s3::Config::builder() - .http_connector(conn) + .http_client(http_client) .credentials_provider(Credentials::for_tests()) .region(Region::new("us-east-1")) .build(); diff --git a/aws/sdk/integration-tests/s3/tests/select-object-content.rs b/aws/sdk/integration-tests/s3/tests/select-object-content.rs index eb4d7a055c..4ba1e49163 100644 --- a/aws/sdk/integration-tests/s3/tests/select-object-content.rs +++ b/aws/sdk/integration-tests/s3/tests/select-object-content.rs @@ -11,19 +11,19 @@ use aws_sdk_s3::types::{ OutputSerialization, SelectObjectContentEventStream, }; use aws_sdk_s3::Client; -use aws_smithy_client::dvr::{Event, ReplayingConnection}; use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; +use aws_smithy_runtime::client::http::test_util::dvr::{Event, ReplayingClient}; use std::error::Error; #[tokio::test] async fn test_success() { let events: Vec = serde_json::from_str(include_str!("select-object-content.json")).unwrap(); - let replayer = ReplayingConnection::new(events); + let replayer = ReplayingClient::new(events); let sdk_config = SdkConfig::builder() .region(Region::from_static("us-east-2")) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) - .http_connector(replayer.clone()) + .http_client(replayer.clone()) .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3/tests/make-connector-override.rs b/aws/sdk/integration-tests/s3/tests/service_timeout_overrides.rs similarity index 58% rename from aws/sdk/integration-tests/s3/tests/make-connector-override.rs rename to aws/sdk/integration-tests/s3/tests/service_timeout_overrides.rs index 90eb79d70c..ba014b0d7f 100644 --- a/aws/sdk/integration-tests/s3/tests/make-connector-override.rs +++ b/aws/sdk/integration-tests/s3/tests/service_timeout_overrides.rs @@ -6,54 +6,13 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_credential_types::Credentials; use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep}; - -use aws_smithy_client::http_connector::{ConnectorSettings, HttpConnector}; -use aws_smithy_client::test_connection; - use aws_smithy_http::result::SdkError; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::region::Region; use aws_types::SdkConfig; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; use std::time::Duration; use tokio::time::Instant; -/// Verify that `make_connector_fn` isn't called per request -#[tokio::test] -async fn make_connector_fn_test() { - let sentinel = Arc::new(AtomicUsize::new(0)); - let connector_sentinel = sentinel.clone(); - let connector_with_counter = HttpConnector::ConnectorFn(Arc::new( - move |_settings: &ConnectorSettings, _sleep: Option| { - connector_sentinel.fetch_add(1, Ordering::Relaxed); - Some(test_connection::infallible_connection_fn(|_req| { - http::Response::builder().status(200).body("ok!").unwrap() - })) - }, - )); - let sdk_config = SdkConfig::builder() - .http_connector(connector_with_counter) - .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) - .region(Region::from_static("us-east-1")) - .build(); - let client = aws_sdk_s3::Client::new(&sdk_config); - assert_eq!(sentinel.load(Ordering::Relaxed), 1); - for _ in 0..10 { - let _ = client - .get_object() - .bucket("foo") - .key("bar") - .send() - .await - .expect("test connector replies with 200"); - } - assert_eq!(sentinel.load(Ordering::Relaxed), 1); - // but creating another client creates another connector - let _client_2 = aws_sdk_s3::Client::new(&sdk_config); - assert_eq!(sentinel.load(Ordering::Relaxed), 2); -} - /// Use a 5 second operation timeout on SdkConfig and a 0ms connect timeout on the service config #[tokio::test] async fn timeouts_can_be_set_by_service() { diff --git a/aws/sdk/integration-tests/s3/tests/signing-it.rs b/aws/sdk/integration-tests/s3/tests/signing-it.rs index 46e1859f0a..d739bec6c1 100644 --- a/aws/sdk/integration-tests/s3/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3/tests/signing-it.rs @@ -7,26 +7,26 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_signer() { - let conn = TestConnection::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("authorization", "AWS4-HMAC-SHA256 Credential=ANOTREAL/20210618/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-user-agent, Signature=ae78f74d26b6b0c3a403d9e8cc7ec3829d6264a2b33db672bf2b151bbb901786") .uri("https://test-bucket.s3.us-east-1.amazonaws.com/?list-type=2&prefix=prefix~") .body(SdkBody::empty()) .unwrap(), - http::Response::builder().status(200).body("").unwrap(), + http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), )]); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .build(); let client = Client::new(&sdk_config); let _ = client @@ -41,5 +41,5 @@ async fn test_signer() { .send() .await; - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } diff --git a/aws/sdk/integration-tests/s3/tests/status-200-errors.rs b/aws/sdk/integration-tests/s3/tests/status-200-errors.rs index 5fee498ec6..ad53f1fedb 100644 --- a/aws/sdk/integration-tests/s3/tests/status-200-errors.rs +++ b/aws/sdk/integration-tests/s3/tests/status-200-errors.rs @@ -6,8 +6,8 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_credential_types::Credentials; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::infallible_connection_fn; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::infallible_client_fn; use aws_smithy_types::error::metadata::ProvideErrorMetadata; use aws_types::region::Region; use aws_types::SdkConfig; @@ -23,11 +23,12 @@ const ERROR_RESPONSE: &str = r#" #[tokio::test] async fn status_200_errors() { - let conn = infallible_connection_fn(|_req| http::Response::new(SdkBody::from(ERROR_RESPONSE))); + let http_client = + infallible_client_fn(|_req| http::Response::new(SdkBody::from(ERROR_RESPONSE))); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-west-4")) - .http_connector(conn) + .http_client(http_client) .build(); let client = Client::new(&sdk_config); let error = client diff --git a/aws/sdk/integration-tests/s3/tests/timeouts.rs b/aws/sdk/integration-tests/s3/tests/timeouts.rs index 59359ad20b..da324e80fa 100644 --- a/aws/sdk/integration-tests/s3/tests/timeouts.rs +++ b/aws/sdk/integration-tests/s3/tests/timeouts.rs @@ -13,7 +13,7 @@ use aws_sdk_s3::types::{ use aws_sdk_s3::Client; use aws_smithy_async::assert_elapsed; use aws_smithy_async::rt::sleep::{default_async_sleep, SharedAsyncSleep, TokioSleep}; -use aws_smithy_client::never::NeverConnector; +use aws_smithy_runtime::client::http::test_util::NeverClient; use aws_smithy_types::error::display::DisplayErrorContext; use aws_smithy_types::timeout::TimeoutConfig; use std::future::Future; @@ -27,7 +27,7 @@ async fn test_timeout_service_ends_request_that_never_completes() { let sdk_config = SdkConfig::builder() .region(Region::from_static("us-east-2")) .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) - .http_connector(NeverConnector::new()) + .http_client(NeverClient::new()) .timeout_config( TimeoutConfig::builder() .operation_timeout(Duration::from_secs_f32(0.5)) diff --git a/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs b/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs index 2dfea8d37b..c9cb041bbd 100644 --- a/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs +++ b/aws/sdk/integration-tests/s3/tests/user-agent-app-name.rs @@ -7,15 +7,15 @@ use aws_config::SdkConfig; use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3::config::{AppName, Credentials, Region}; use aws_sdk_s3::Client; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn user_agent_app_name() { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests())) .region(Region::new("us-east-1")) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .app_name(AppName::new("test-app-name").expect("valid app name")) // set app name in config .build(); let client = Client::new(&sdk_config); diff --git a/aws/sdk/integration-tests/s3control/Cargo.toml b/aws/sdk/integration-tests/s3control/Cargo.toml index beeb8e9177..723f8c964a 100644 --- a/aws/sdk/integration-tests/s3control/Cargo.toml +++ b/aws/sdk/integration-tests/s3control/Cargo.toml @@ -14,7 +14,8 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-s3control = { path = "../../build/aws-sdk/sdk/s3control" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } +aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } bytes = "1.0.0" diff --git a/aws/sdk/integration-tests/s3control/tests/signing-it.rs b/aws/sdk/integration-tests/s3control/tests/signing-it.rs index 4b207f9835..7a1a585d1c 100644 --- a/aws/sdk/integration-tests/s3control/tests/signing-it.rs +++ b/aws/sdk/integration-tests/s3control/tests/signing-it.rs @@ -6,14 +6,14 @@ use aws_credential_types::provider::SharedCredentialsProvider; use aws_sdk_s3control::config::{Credentials, Region}; use aws_sdk_s3control::Client; -use aws_smithy_client::test_connection::TestConnection; use aws_smithy_http::body::SdkBody; +use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; use aws_types::SdkConfig; use std::time::{Duration, UNIX_EPOCH}; #[tokio::test] async fn test_signer() { - let conn = TestConnection::new(vec![( + let http_client = StaticReplayClient::new(vec![ReplayEvent::new( http::Request::builder() .header("authorization", "AWS4-HMAC-SHA256 Credential=ANOTREAL/20211112/us-east-1/s3/aws4_request, \ @@ -22,13 +22,13 @@ async fn test_signer() { .uri("https://test-bucket.s3-control.us-east-1.amazonaws.com/v20180820/accesspoint") .body(SdkBody::empty()) .unwrap(), - http::Response::builder().status(200).body("").unwrap(), + http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), )]); let sdk_config = SdkConfig::builder() .credentials_provider(SharedCredentialsProvider::new( Credentials::for_tests_with_session_token(), )) - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::new("us-east-1")) .build(); let client = Client::new(&sdk_config); @@ -46,5 +46,5 @@ async fn test_signer() { .await .expect_err("empty response"); - conn.assert_requests_match(&[]); + http_client.assert_requests_match(&[]); } diff --git a/aws/sdk/integration-tests/sts/Cargo.toml b/aws/sdk/integration-tests/sts/Cargo.toml index a5e50b83cf..b9e50af6e0 100644 --- a/aws/sdk/integration-tests/sts/Cargo.toml +++ b/aws/sdk/integration-tests/sts/Cargo.toml @@ -13,8 +13,8 @@ publish = false [dev-dependencies] aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-sdk-sts = { path = "../../build/aws-sdk/sdk/sts" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } tokio = { version = "1.23.1", features = ["full", "test-util"] } tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } diff --git a/aws/sdk/integration-tests/sts/tests/signing-it.rs b/aws/sdk/integration-tests/sts/tests/signing-it.rs index 5bf851a5ca..1b0a4e918e 100644 --- a/aws/sdk/integration-tests/sts/tests/signing-it.rs +++ b/aws/sdk/integration-tests/sts/tests/signing-it.rs @@ -4,16 +4,16 @@ */ use aws_sdk_sts::config::{Credentials, Region}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; #[tokio::test] async fn assume_role_signed() { let creds = Credentials::for_tests(); - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_sts::Config::builder() .credentials_provider(creds) .region(Region::new("us-east-1")) - .http_connector(server) + .http_client(http_client) .build(); let client = aws_sdk_sts::Client::from_conf(conf); let _ = client.assume_role().send().await; @@ -26,10 +26,10 @@ async fn assume_role_signed() { #[tokio::test] async fn web_identity_unsigned() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_sts::Config::builder() .region(Region::new("us-east-1")) - .http_connector(server) + .http_client(http_client) .build(); let client = aws_sdk_sts::Client::from_conf(conf); let _ = client.assume_role_with_web_identity().send().await; @@ -42,10 +42,10 @@ async fn web_identity_unsigned() { #[tokio::test] async fn assume_role_saml_unsigned() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_sts::Config::builder() .region(Region::new("us-east-1")) - .http_connector(server) + .http_client(http_client) .build(); let client = aws_sdk_sts::Client::from_conf(conf); let _ = client.assume_role_with_saml().send().await; @@ -58,10 +58,10 @@ async fn assume_role_saml_unsigned() { #[tokio::test] async fn web_identity_no_creds() { - let (server, request) = capture_request(None); + let (http_client, request) = capture_request(None); let conf = aws_sdk_sts::Config::builder() .region(Region::new("us-east-1")) - .http_connector(server) + .http_client(http_client) .build(); let client = aws_sdk_sts::Client::from_conf(conf); let _ = client.assume_role_with_web_identity().send().await; diff --git a/aws/sdk/integration-tests/timestreamquery/Cargo.toml b/aws/sdk/integration-tests/timestreamquery/Cargo.toml index 99955e4764..b8c6a497ac 100644 --- a/aws/sdk/integration-tests/timestreamquery/Cargo.toml +++ b/aws/sdk/integration-tests/timestreamquery/Cargo.toml @@ -14,7 +14,6 @@ publish = false aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-sdk-timestreamquery = { path = "../../build/aws-sdk/sdk/timestreamquery" } aws-smithy-async = { path = "../../build/aws-sdk/sdk/aws-smithy-async", features = ["test-util"] } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util"] } aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["test-util"] } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } tokio = { version = "1.23.1", features = ["full", "test-util"] } diff --git a/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs b/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs index 4c36c1e157..4bff784ade 100644 --- a/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs +++ b/aws/sdk/integration-tests/timestreamquery/tests/endpoint_disco.rs @@ -3,6 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +use aws_smithy_runtime::client::http::test_util::dvr::{MediaType, ReplayingClient}; + #[tokio::test] async fn do_endpoint_discovery() { use aws_credential_types::provider::SharedCredentialsProvider; @@ -11,19 +13,18 @@ async fn do_endpoint_discovery() { use aws_smithy_async::rt::sleep::SharedAsyncSleep; use aws_smithy_async::test_util::controlled_time_and_sleep; use aws_smithy_async::time::{SharedTimeSource, TimeSource}; - use aws_smithy_client::dvr::{MediaType, ReplayingConnection}; use aws_types::region::Region; use aws_types::SdkConfig; use std::time::{Duration, UNIX_EPOCH}; let _logs = aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs(); - let conn = ReplayingConnection::from_file("tests/traffic.json").unwrap(); + let http_client = ReplayingClient::from_file("tests/traffic.json").unwrap(); //let conn = aws_smithy_client::dvr::RecordingConnection::new(conn); let start = UNIX_EPOCH + Duration::from_secs(1234567890); let (ts, sleep, mut gate) = controlled_time_and_sleep(start); let config = SdkConfig::builder() - .http_connector(conn.clone()) + .http_client(http_client.clone()) .region(Region::from_static("us-west-2")) .sleep_impl(SharedAsyncSleep::new(sleep)) .credentials_provider(SharedCredentialsProvider::new( @@ -65,15 +66,16 @@ async fn do_endpoint_discovery() { .unwrap(); // if you want to update this test: // conn.dump_to_file("tests/traffic.json").unwrap(); - conn.validate_body_and_headers( - Some(&[ - "x-amz-security-token", - "x-amz-date", - "content-type", - "x-amz-target", - ]), - MediaType::Json, - ) - .await - .unwrap(); + http_client + .validate_body_and_headers( + Some(&[ + "x-amz-security-token", + "x-amz-date", + "content-type", + "x-amz-target", + ]), + MediaType::Json, + ) + .await + .unwrap(); } diff --git a/aws/sdk/integration-tests/transcribestreaming/Cargo.toml b/aws/sdk/integration-tests/transcribestreaming/Cargo.toml index 181ba493cb..45a26fb836 100644 --- a/aws/sdk/integration-tests/transcribestreaming/Cargo.toml +++ b/aws/sdk/integration-tests/transcribestreaming/Cargo.toml @@ -13,9 +13,9 @@ async-stream = "0.3.0" aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["test-util"] } aws-http = { path = "../../build/aws-sdk/sdk/aws-http" } aws-sdk-transcribestreaming = { path = "../../build/aws-sdk/sdk/transcribestreaming" } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", features = ["test-util", "rustls"] } aws-smithy-eventstream = { path = "../../build/aws-sdk/sdk/aws-smithy-eventstream" } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "test-util"] } bytes = "1.0.0" futures-core = "0.3.14" hound = "3.4.0" diff --git a/aws/sdk/integration-tests/transcribestreaming/tests/test.rs b/aws/sdk/integration-tests/transcribestreaming/tests/test.rs index 62654ebd82..fce762d82b 100644 --- a/aws/sdk/integration-tests/transcribestreaming/tests/test.rs +++ b/aws/sdk/integration-tests/transcribestreaming/tests/test.rs @@ -13,8 +13,8 @@ use aws_sdk_transcribestreaming::types::{ AudioEvent, AudioStream, LanguageCode, MediaEncoding, TranscriptResultStream, }; use aws_sdk_transcribestreaming::{Client, Config}; -use aws_smithy_client::dvr::{Event, ReplayingConnection}; use aws_smithy_eventstream::frame::{DecodedFrame, HeaderValue, Message, MessageFrameDecoder}; +use aws_smithy_runtime::client::http::test_util::dvr::{Event, ReplayingClient}; use bytes::BufMut; use futures_core::Stream; use std::collections::{BTreeMap, BTreeSet}; @@ -98,14 +98,14 @@ async fn start_request( region: &'static str, events_json: &str, input_stream: impl Stream> + Send + Sync + 'static, -) -> (ReplayingConnection, StartStreamTranscriptionOutput) { +) -> (ReplayingClient, StartStreamTranscriptionOutput) { let events: Vec = serde_json::from_str(events_json).unwrap(); - let replayer = ReplayingConnection::new(events); + let replayer = ReplayingClient::new(events); let region = Region::from_static(region); let config = Config::builder() .region(region) - .http_connector(replayer.clone()) + .http_client(replayer.clone()) .credentials_provider(Credentials::for_tests()) .build(); let client = Client::from_conf(config); diff --git a/aws/sdk/integration-tests/webassembly/Cargo.toml b/aws/sdk/integration-tests/webassembly/Cargo.toml index b66406e6f5..159fb4b867 100644 --- a/aws/sdk/integration-tests/webassembly/Cargo.toml +++ b/aws/sdk/integration-tests/webassembly/Cargo.toml @@ -21,8 +21,9 @@ crate-type = ["cdylib"] aws-config = { path = "../../build/aws-sdk/sdk/aws-config", default-features = false, features = ["rt-tokio"]} aws-credential-types = { path = "../../build/aws-sdk/sdk/aws-credential-types", features = ["hardcoded-credentials"] } aws-sdk-s3 = { path = "../../build/aws-sdk/sdk/s3", default-features = false } -aws-smithy-client = { path = "../../build/aws-sdk/sdk/aws-smithy-client", default-features = false } aws-smithy-http = { path = "../../build/aws-sdk/sdk/aws-smithy-http" } +aws-smithy-runtime = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime", features = ["client"] } +aws-smithy-runtime-api = { path = "../../build/aws-sdk/sdk/aws-smithy-runtime-api", features = ["client"] } aws-smithy-types = { path = "../../build/aws-sdk/sdk/aws-smithy-types" } aws-types = { path = "../../build/aws-sdk/sdk/aws-types" } http = "0.2.8" diff --git a/aws/sdk/integration-tests/webassembly/src/adapter/http_client.rs b/aws/sdk/integration-tests/webassembly/src/adapter/http_client.rs deleted file mode 100644 index 5ca84838c9..0000000000 --- a/aws/sdk/integration-tests/webassembly/src/adapter/http_client.rs +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -use aws_smithy_http::body::SdkBody; - -pub(crate) fn make_request(_req: http::Request) -> Result, ()> { - // Consumers here would pass the HTTP request to - // the Wasm host in order to get the response back - let body = " - - - - 2023-01-23T11:59:03.575496Z - doc-example-bucket - - - 2023-01-23T23:32:13.125238Z - doc-example-bucket2 - - - - account-name - a3a42310-42d0-46d1-9745-0cee9f4fb851 - - "; - Ok(http::Response::new(SdkBody::from(body))) -} diff --git a/aws/sdk/integration-tests/webassembly/src/adapter/mod.rs b/aws/sdk/integration-tests/webassembly/src/adapter/mod.rs deleted file mode 100644 index b563eb097a..0000000000 --- a/aws/sdk/integration-tests/webassembly/src/adapter/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -mod http_client; - -use aws_smithy_client::erase::DynConnector; -use aws_smithy_client::http_connector::HttpConnector; -use aws_smithy_http::body::SdkBody; -use aws_smithy_http::result::ConnectorError; -use std::task::{Context, Poll}; -use tower::Service; - -#[derive(Default, Debug, Clone)] -pub(crate) struct Adapter {} - -impl Adapter { - pub fn to_http_connector() -> impl Into { - DynConnector::new(Adapter::default()) - } -} - -impl Service> for Adapter { - type Response = http::Response; - - type Error = ConnectorError; - - #[allow(clippy::type_complexity)] - type Future = std::pin::Pin< - Box> + Send + 'static>, - >; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: http::Request) -> Self::Future { - println!("Adapter: sending request..."); - let res = http_client::make_request(req).unwrap(); - println!("{:?}", res); - Box::pin(async move { Ok(res) }) - } -} diff --git a/aws/sdk/integration-tests/webassembly/src/default_config.rs b/aws/sdk/integration-tests/webassembly/src/default_config.rs index 181d1bb470..c87a364b5e 100644 --- a/aws/sdk/integration-tests/webassembly/src/default_config.rs +++ b/aws/sdk/integration-tests/webassembly/src/default_config.rs @@ -3,14 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ +use crate::http::WasmHttpConnector; use aws_config::retry::RetryConfig; use aws_credential_types::Credentials; use aws_smithy_types::timeout::TimeoutConfig; use aws_types::region::Region; use std::future::Future; -use crate::adapter::Adapter; - pub(crate) fn get_default_config() -> impl Future { aws_config::from_env() .region(Region::from_static("us-west-2")) @@ -21,7 +20,7 @@ pub(crate) fn get_default_config() -> impl Future) -> Result, ()> { + // Consumers here would pass the HTTP request to + // the Wasm host in order to get the response back + let body = " + + + + 2023-01-23T11:59:03.575496Z + doc-example-bucket + + + 2023-01-23T23:32:13.125238Z + doc-example-bucket2 + + + + account-name + a3a42310-42d0-46d1-9745-0cee9f4fb851 + + "; + Ok(http::Response::new(SdkBody::from(body))) +} + +#[derive(Default, Debug, Clone)] +pub(crate) struct WasmHttpConnector; +impl WasmHttpConnector { + pub fn new() -> Self { + Self + } +} + +impl HttpConnector for WasmHttpConnector { + fn call(&self, request: HttpRequest) -> HttpConnectorFuture { + println!("Adapter: sending request..."); + let res = make_request(request).unwrap(); + println!("{:?}", res); + HttpConnectorFuture::new(async move { Ok(res) }) + } +} + +impl HttpClient for WasmHttpConnector { + fn http_connector( + &self, + _settings: &HttpConnectorSettings, + _components: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/aws/sdk/integration-tests/webassembly/src/lib.rs b/aws/sdk/integration-tests/webassembly/src/lib.rs index bc9c1a112b..1c4932afbb 100644 --- a/aws/sdk/integration-tests/webassembly/src/lib.rs +++ b/aws/sdk/integration-tests/webassembly/src/lib.rs @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -mod adapter; mod default_config; +mod http; mod list_buckets; #[tokio::main(flavor = "current_thread")] diff --git a/aws/sdk/sdk-external-types.toml b/aws/sdk/sdk-external-types.toml index b484544c27..4705c4e295 100644 --- a/aws/sdk/sdk-external-types.toml +++ b/aws/sdk/sdk-external-types.toml @@ -1,17 +1,15 @@ # These are the allowed external types in the `aws-sdk-*` generated crates, checked by CI. allowed_external_types = [ "aws_credential_types::*", - "aws_endpoint::*", "aws_http::*", "aws_runtime::*", "aws_smithy_async::*", - "aws_smithy_client::*", "aws_smithy_http::*", - "aws_smithy_http_tower::*", "aws_smithy_runtime::*", "aws_smithy_runtime_api::*", "aws_smithy_types::*", "aws_types::*", + "http::header::map::HeaderMap", "http::header::value::HeaderValue", "http::request::Request", @@ -19,14 +17,6 @@ allowed_external_types = [ "http::uri::Uri", "http::method::Method", - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Switch to AsyncIterator once standardized - "futures_core::stream::Stream", - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Once tooling permits it, only allow the following types in the `event-stream` feature "aws_smithy_eventstream::*", - - # TODO(https://github.com/awslabs/smithy-rs/issues/1193): Decide if we want to continue exposing tower_layer - "tower_layer::Layer", - "tower_layer::identity::Identity", - "tower_layer::stack::Stack", ] 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 cd797f938f..d8a9b6818b 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 643d0af31a..91f08b80be 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,30 @@ 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"), + "HttpClient" to RuntimeType.smithyRuntimeApi(runtimeConfig).resolve("client::http::HttpClient"), + "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() + /// Deprecated. Don't use. + ##[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn http_connector(&self) -> Option<#{SharedHttpClient}> { + self.runtime_components.http_client() + } + + /// 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 +67,23 @@ private class HttpConnectorConfigCustomization( ServiceConfig.BuilderImpl -> writable { rustTemplate( """ - /// Sets the HTTP connector to use when making requests. + /// Deprecated. Don't use. + ##[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn http_connector(self, http_client: impl #{HttpClient} + 'static) -> Self { + self.http_client(http_client) + } + + /// Deprecated. Don't use. + ##[deprecated( + note = "HTTP connector configuration changed. See https://github.com/awslabs/smithy-rs/discussions/3022 for upgrade guidance." + )] + pub fn set_http_connector(&mut self, http_client: Option<#{SharedHttpClient}>) -> &mut Self { + self.set_http_client(http_client) + } + + /// Sets the HTTP client to use when making requests. /// /// ## Examples /// ```no_run @@ -132,10 +92,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 +101,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 #{HttpClient} + 'static) -> 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,41 +126,28 @@ 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(); /// ## } /// ## } /// ``` - """, - *codegenScope, - ) - 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 +155,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 87586fbcb7..6f588f11ac 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 @@ -29,9 +29,11 @@ class ResiliencyConfigCustomization(private val codegenContext: ClientCodegenCon private val moduleUseName = codegenContext.moduleUseName() private val codegenScope = arrayOf( *preludeScope, + "AsyncSleep" to sleepModule.resolve("AsyncSleep"), "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 +152,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 #{AsyncSleep} + 'static) -> Self { + self.set_sleep_impl(Some(#{IntoShared}::into_shared(sleep_impl))); 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 f3402c950d..67b3664688 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,8 +18,10 @@ 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"), + "TimeSource" to RuntimeType.smithyAsync(codegenContext.runtimeConfig).resolve("time::TimeSource"), "UNIX_EPOCH" to RuntimeType.std.resolve("time::UNIX_EPOCH"), "Duration" to RuntimeType.std.resolve("time::Duration"), ) @@ -46,9 +48,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 #{TimeSource} + 'static, ) -> Self { - self.set_time_source(#{Some}(time_source.into())); + self.set_time_source(#{Some}(#{IntoShared}::into_shared(time_source))); 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 78686b2877..3852d09fb2 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 5234f6fe59..dfe7ca5802 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 0d7bb2fd1e..4435f73271 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 @@ -481,6 +481,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()) @@ -500,6 +501,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 44ff13d45e..4a4f810789 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 e53b66fc14..56be3c3778 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,9 +17,12 @@ import software.amazon.smithy.rust.codegen.core.testutil.integrationTest class HttpAuthDecoratorTest { private fun codegenScope(runtimeConfig: RuntimeConfig): Array> = arrayOf( - "TestConnection" to CargoDependency.smithyClient(runtimeConfig) + "ReplayEvent" to CargoDependency.smithyRuntime(runtimeConfig) .toDevDependency().withFeature("test-util").toType() - .resolve("test_connection::TestConnection"), + .resolve("client::http::test_util::ReplayEvent"), + "StaticReplayClient" to CargoDependency.smithyRuntime(runtimeConfig) + .toDevDependency().withFeature("test-util").toType() + .resolve("client::http::test_util::StaticReplayClient"), "SdkBody" to RuntimeType.sdkBody(runtimeConfig), ) @@ -34,25 +37,27 @@ 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 = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::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(), + )], + ); 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 +68,28 @@ 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 = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::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(), + )], + ); 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 +109,27 @@ 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 = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::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(), + )], + ); 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 +149,28 @@ 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 = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::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(), + )], + ); 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 +190,28 @@ 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 = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::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(), + )], + ); 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 +231,28 @@ 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 = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::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(), + )], + ); 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 +270,26 @@ 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 = #{StaticReplayClient}::new( + vec![#{ReplayEvent}::new( + http::Request::builder() + .uri("http://localhost:1234/SomeOperation") + .body(#{SdkBody}::empty()) + .unwrap(), + http::Response::builder().status(200).body(#{SdkBody}::empty()).unwrap(), + )], + ); 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 1bcc30e0c3..9010b94659 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 ec4d152151..bf634170ca 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 b55faa439f..d57272b149 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 9534f7a79e..e7a4ed760f 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 430ff4737e..3d1dcd009d 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 8fb6bb888f..2abb4229c8 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 1839903969..048aa961a3 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 332e331cb2..64c7cae3ba 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 73a5e9ebbe..9dbb43f497 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 f7e463b0a7..639964f762 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/examples/pokemon-service-common/Cargo.toml b/examples/pokemon-service-common/Cargo.toml index f2c86eee0e..6a63045004 100644 --- a/examples/pokemon-service-common/Cargo.toml +++ b/examples/pokemon-service-common/Cargo.toml @@ -16,12 +16,12 @@ tokio = { version = "1", default-features = false, features = ["time"] } tower = "0.4" # Local paths -aws-smithy-client = { path = "../../rust-runtime/aws-smithy-client" } +aws-smithy-runtime = { path = "../../rust-runtime/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x"] } +aws-smithy-runtime-api = { path = "../../rust-runtime/aws-smithy-runtime-api", features = ["client"] } aws-smithy-http = { path = "../../rust-runtime/aws-smithy-http" } aws-smithy-http-server = { path = "../../rust-runtime/aws-smithy-http-server" } pokemon-service-client = { path = "../pokemon-service-client" } pokemon-service-server-sdk = { path = "../pokemon-service-server-sdk" } [dev-dependencies] -aws-smithy-client = { path = "../../rust-runtime/aws-smithy-client", features = ["test-util"] } aws-smithy-runtime = { path = "../../rust-runtime/aws-smithy-runtime", features = ["test-util"] } diff --git a/examples/pokemon-service-common/src/lib.rs b/examples/pokemon-service-common/src/lib.rs index cb5c4bd604..be15b07117 100644 --- a/examples/pokemon-service-common/src/lib.rs +++ b/examples/pokemon-service-common/src/lib.rs @@ -15,15 +15,15 @@ use std::{ }; use async_stream::stream; -use aws_smithy_client::{conns, hyper_ext::Adapter}; use aws_smithy_http::{body::SdkBody, byte_stream::ByteStream}; use aws_smithy_http_server::Extension; +use aws_smithy_runtime::client::http::hyper_014::HyperConnector; +use aws_smithy_runtime_api::client::http::HttpConnector; use http::Uri; use pokemon_service_server_sdk::{ error, input, model, model::CapturingPayload, output, types::Blob, }; use rand::{seq::SliceRandom, Rng}; -use tower::Service; use tracing_subscriber::{prelude::*, EnvFilter}; const PIKACHU_ENGLISH_FLAVOR_TEXT: &str = @@ -326,7 +326,7 @@ pub async fn stream_pokemon_radio( .parse::() .expect("Invalid url in `RADIO_STREAMS`"); - let mut connector = Adapter::builder().build(conns::https()); + let connector = HyperConnector::builder().build_https(); let result = connector .call( http::Request::builder() diff --git a/examples/pokemon-service-common/tests/plugins_execution_order.rs b/examples/pokemon-service-common/tests/plugins_execution_order.rs index fb5f0fb4d7..35d3ba5fe2 100644 --- a/examples/pokemon-service-common/tests/plugins_execution_order.rs +++ b/examples/pokemon-service-common/tests/plugins_execution_order.rs @@ -14,7 +14,7 @@ use aws_smithy_http::body::SdkBody; use aws_smithy_http_server::plugin::{HttpMarker, HttpPlugins, IdentityPlugin, Plugin}; use tower::{Layer, Service}; -use aws_smithy_client::test_connection::capture_request; +use aws_smithy_runtime::client::http::test_util::capture_request; use pokemon_service_client::{Client, Config}; use pokemon_service_common::do_nothing; @@ -46,9 +46,9 @@ async fn plugin_layers_are_executed_in_registration_order() { .build_unchecked(); let request = { - let (conn, rcvr) = capture_request(None); + let (http_client, rcvr) = capture_request(None); let config = Config::builder() - .http_connector(conn) + .http_client(http_client) .endpoint_url("http://localhost:1234") .build(); Client::from_conf(config).do_nothing().send().await.unwrap(); diff --git a/examples/pokemon-service-tls/Cargo.toml b/examples/pokemon-service-tls/Cargo.toml index 9f11a904fc..566afcfc0a 100644 --- a/examples/pokemon-service-tls/Cargo.toml +++ b/examples/pokemon-service-tls/Cargo.toml @@ -31,7 +31,7 @@ hyper-rustls = { version = "0.24", features = ["http2"] } hyper-tls = { version = "0.5" } # Local paths -aws-smithy-client = { path = "../../rust-runtime/aws-smithy-client/", features = ["rustls"] } aws-smithy-http = { path = "../../rust-runtime/aws-smithy-http/" } +aws-smithy-runtime = { path = "../../rust-runtime/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x"] } aws-smithy-types = { path = "../../rust-runtime/aws-smithy-types/" } pokemon-service-client = { path = "../pokemon-service-client/" } diff --git a/examples/pokemon-service-tls/tests/common/mod.rs b/examples/pokemon-service-tls/tests/common/mod.rs index 0d69407cf7..a13c2e60ee 100644 --- a/examples/pokemon-service-tls/tests/common/mod.rs +++ b/examples/pokemon-service-tls/tests/common/mod.rs @@ -6,7 +6,7 @@ use std::{fs::File, io::BufReader, process::Command, time::Duration}; use assert_cmd::prelude::*; -use aws_smithy_client::hyper_ext::Adapter; +use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; use tokio::time::sleep; use pokemon_service_client::{Client, Config}; @@ -44,7 +44,7 @@ pub fn client_http2_only() -> Client { .build(); let config = Config::builder() - .http_connector(Adapter::builder().build(connector)) + .http_client(HyperClientBuilder::new().build(connector)) .endpoint_url(format!("https://{DEFAULT_DOMAIN}:{DEFAULT_PORT}")) .build(); Client::from_conf(config) @@ -74,7 +74,7 @@ fn native_tls_connector() -> NativeTlsConnector { pub fn native_tls_client() -> Client { let config = Config::builder() - .http_connector(Adapter::builder().build(native_tls_connector())) + .http_client(HyperClientBuilder::new().build(native_tls_connector())) .endpoint_url(format!("https://{DEFAULT_DOMAIN}:{DEFAULT_PORT}")) .build(); Client::from_conf(config) diff --git a/examples/python/pokemon-service-test/Cargo.toml b/examples/python/pokemon-service-test/Cargo.toml index b4084185c2..a7960c24e4 100644 --- a/examples/python/pokemon-service-test/Cargo.toml +++ b/examples/python/pokemon-service-test/Cargo.toml @@ -17,7 +17,7 @@ tokio-rustls = "0.24.0" hyper-rustls = { version = "0.24", features = ["http2"] } # Local paths -aws-smithy-client = { path = "../../../rust-runtime/aws-smithy-client/", features = ["rustls"] } +aws-smithy-runtime = { path = "../../../rust-runtime/aws-smithy-runtime/", features = ["client", "connector-hyper-0-14-x"] } aws-smithy-http = { path = "../../../rust-runtime/aws-smithy-http/" } aws-smithy-types = { path = "../../../rust-runtime/aws-smithy-types/" } pokemon-service-client = { path = "../pokemon-service-client/" } diff --git a/examples/python/pokemon-service-test/tests/helpers.rs b/examples/python/pokemon-service-test/tests/helpers.rs index 708e17fe88..53064e3402 100644 --- a/examples/python/pokemon-service-test/tests/helpers.rs +++ b/examples/python/pokemon-service-test/tests/helpers.rs @@ -8,7 +8,7 @@ use std::io::BufReader; use std::process::Command; use std::time::Duration; -use aws_smithy_client::hyper_ext::Adapter; +use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; use command_group::{CommandGroup, GroupChild}; use pokemon_service_client::{Client, Config}; use tokio::time; @@ -102,7 +102,7 @@ pub fn http2_client() -> PokemonClient { let mut roots = tokio_rustls::rustls::RootCertStore::empty(); roots.add_parsable_certificates(&certs); - let connector = hyper_rustls::HttpsConnectorBuilder::new() + let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config( tokio_rustls::rustls::ClientConfig::builder() .with_safe_defaults() @@ -115,7 +115,7 @@ pub fn http2_client() -> PokemonClient { let base_url = PokemonServiceVariant::Http2.base_url(); let config = Config::builder() - .http_connector(Adapter::builder().build(connector)) + .http_client(HyperClientBuilder::new().build(tls_connector)) .endpoint_url(base_url) .build(); Client::from_conf(config) diff --git a/rust-runtime/aws-smithy-async/Cargo.toml b/rust-runtime/aws-smithy-async/Cargo.toml index fd51b4fb1e..12e72c4fea 100644 --- a/rust-runtime/aws-smithy-async/Cargo.toml +++ b/rust-runtime/aws-smithy-async/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/awslabs/smithy-rs" [features] rt-tokio = ["tokio/time"] -test-util = [] +test-util = ["rt-tokio"] [dependencies] pin-project-lite = "0.2" diff --git a/rust-runtime/aws-smithy-async/src/future/mod.rs b/rust-runtime/aws-smithy-async/src/future/mod.rs index 44894e0733..d60fc46c77 100644 --- a/rust-runtime/aws-smithy-async/src/future/mod.rs +++ b/rust-runtime/aws-smithy-async/src/future/mod.rs @@ -5,8 +5,14 @@ //! Useful runtime-agnostic future implementations. +use futures_util::Future; +use std::pin::Pin; + pub mod never; pub mod now_or_later; pub mod pagination_stream; pub mod rendezvous; pub mod timeout; + +/// A boxed future that outputs a `Result`. +pub type BoxFuture = Pin> + Send>>; diff --git a/rust-runtime/aws-smithy-async/src/test_util.rs b/rust-runtime/aws-smithy-async/src/test_util.rs index e323478d84..dd7eacfd89 100644 --- a/rust-runtime/aws-smithy-async/src/test_util.rs +++ b/rust-runtime/aws-smithy-async/src/test_util.rs @@ -22,7 +22,6 @@ pub struct ManualTimeSource { log: Arc>>, } -#[cfg(feature = "test-util")] impl ManualTimeSource { /// Get the number of seconds since the UNIX Epoch as an f64. /// @@ -139,6 +138,13 @@ impl InstantSleep { Self { log } } + /// Create an `InstantSleep` without passing in a shared log. + pub fn unlogged() -> Self { + Self { + log: Default::default(), + } + } + /// Return the sleep durations that were logged by this `InstantSleep`. pub fn logs(&self) -> Vec { self.log.lock().unwrap().iter().cloned().collect() diff --git a/rust-runtime/aws-smithy-runtime-api/src/client.rs b/rust-runtime/aws-smithy-runtime-api/src/client.rs index 9f8a05686e..2afc2546c5 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 9399fa05bf..0000000000 --- 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 0000000000..f7dbadec60 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime-api/src/client/dns.rs @@ -0,0 +1,107 @@ +/* + * 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::impl_shared_conversions; +use aws_smithy_async::future::now_or_later::NowOrLater; +use std::error::Error as StdError; +use std::fmt; +use std::future::Future; +use std::net::IpAddr; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; + +/// Error that occurs when failing to perform a DNS lookup. +#[derive(Debug)] +pub struct ResolveDnsError { + source: BoxError, +} + +impl ResolveDnsError { + /// Creates a new `DnsLookupFailed` error. + pub fn new(source: impl Into) -> Self { + ResolveDnsError { + source: source.into(), + } + } +} + +impl fmt::Display for ResolveDnsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "failed to perform DNS lookup") + } +} + +impl StdError for ResolveDnsError { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(&*self.source as _) + } +} + +type BoxFuture = aws_smithy_async::future::BoxFuture; + +/// New-type for the future returned by the [`DnsResolver`] trait. +pub struct DnsFuture(NowOrLater, ResolveDnsError>, BoxFuture>>); +impl DnsFuture { + /// Create a new `DnsFuture` + pub fn new( + future: impl Future, ResolveDnsError>> + Send + 'static, + ) -> Self { + Self(NowOrLater::new(Box::pin(future))) + } + + /// Create a `DnsFuture` that is immediately ready + pub fn ready(result: Result, ResolveDnsError>) -> Self { + Self(NowOrLater::ready(result)) + } +} +impl Future for DnsFuture { + type Output = Result, ResolveDnsError>; + + 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 0000000000..5347f71247 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime-api/src/client/http.rs @@ -0,0 +1,308 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! HTTP clients and connectors +//! +//! # 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`]. In hyper 0.x, the connector is a [`tower`] `Service` that takes a `Uri` and returns +//! a future with something that implements `AsyncRead + AsyncWrite`. +//! +//! The [`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 available in [`aws-smithy-runtime`] +//! with the `test-util` feature enabled. +//! +//! # Responsibilities of a connector +//! +//! A connector primarily makes HTTP requests, but is also the place where connect and read timeouts are +//! implemented. The `HyperConnector` in [`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. +//! +//! # The [`HttpClient`] trait +//! +//! Connectors allow us to make requests, but we need a layer on top of connectors so that we can handle +//! varying connector settings. For example, say we configure some default HTTP connect/read timeouts on +//! Client, and then configure some override connect/read timeouts for a specific operation. These timeouts +//! ultimately are part of the connector, so the same connector can't be reused for the two different sets +//! of timeouts. Thus, the [`HttpClient`] implementation is responsible for managing multiple connectors +//! with varying config. Some example configs that can impact which connector is used: +//! +//! - HTTP protocol versions +//! - TLS settings +//! - Timeouts +//! +//! Some of these aren't implemented yet, but they will appear in the [`HttpConnectorSettings`] struct +//! once they are. +//! +//! [`hyper`]: https://crates.io/crates/hyper +//! [`tower`]: https://crates.io/crates/tower +//! [`aws-smithy-runtime`]: https://crates.io/crates/aws-smithy-runtime + +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 = aws_smithy_async::future::BoxFuture; + +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/mock 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/orchestrator.rs b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs index a034515562..328117648c 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/client/orchestrator.rs @@ -27,8 +27,6 @@ use aws_smithy_types::config_bag::{Storable, StoreReplace}; use bytes::Bytes; use std::error::Error as StdError; use std::fmt; -use std::future::Future as StdFuture; -use std::pin::Pin; /// Type alias for the HTTP request type that the orchestrator uses. pub type HttpRequest = http::Request; @@ -40,7 +38,7 @@ pub type HttpResponse = http::Response; /// /// See [the Rust blog](https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html) for /// more information on async functions in traits. -pub type BoxFuture = Pin> + Send>>; +pub type BoxFuture = aws_smithy_async::future::BoxFuture; /// Type alias for futures that are returned from several traits since async trait functions are not stable yet (as of 2023-07-21). /// 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 4a92ec6988..0f12d407a5 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 @@ -12,16 +12,19 @@ //! [`RuntimeComponents`](RuntimeComponents). use crate::client::auth::{ - AuthScheme, AuthSchemeId, SharedAuthScheme, SharedAuthSchemeOptionResolver, + AuthScheme, AuthSchemeId, AuthSchemeOptionResolver, SharedAuthScheme, + SharedAuthSchemeOptionResolver, }; -use crate::client::connectors::SharedHttpConnector; -use crate::client::endpoint::SharedEndpointResolver; -use crate::client::identity::{ConfiguredIdentityResolver, SharedIdentityResolver}; -use crate::client::interceptors::SharedInterceptor; -use crate::client::retries::{RetryClassifiers, SharedRetryStrategy}; +use crate::client::endpoint::{EndpointResolver, SharedEndpointResolver}; +use crate::client::http::{HttpClient, SharedHttpClient}; +use crate::client::identity::{ + ConfiguredIdentityResolver, IdentityResolver, SharedIdentityResolver, +}; +use crate::client::interceptors::{Interceptor, SharedInterceptor}; +use crate::client::retries::{RetryClassifiers, RetryStrategy, SharedRetryStrategy}; use crate::shared::IntoShared; -use aws_smithy_async::rt::sleep::SharedAsyncSleep; -use aws_smithy_async::time::SharedTimeSource; +use aws_smithy_async::rt::sleep::{AsyncSleep, SharedAsyncSleep}; +use aws_smithy_async::time::{SharedTimeSource, TimeSource}; use std::fmt; pub(crate) static EMPTY_RUNTIME_COMPONENTS_BUILDER: RuntimeComponentsBuilder = @@ -184,7 +187,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 +222,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. @@ -274,7 +277,7 @@ impl RuntimeComponentsBuilder { /// Sets the auth scheme option resolver. pub fn set_auth_scheme_option_resolver( &mut self, - auth_scheme_option_resolver: Option>, + auth_scheme_option_resolver: Option, ) -> &mut Self { self.auth_scheme_option_resolver = auth_scheme_option_resolver.map(|r| Tracked::new(self.builder_name, r.into_shared())); @@ -284,32 +287,26 @@ impl RuntimeComponentsBuilder { /// Sets the auth scheme option resolver. pub fn with_auth_scheme_option_resolver( mut self, - auth_scheme_option_resolver: Option>, + auth_scheme_option_resolver: Option, ) -> Self { self.set_auth_scheme_option_resolver(auth_scheme_option_resolver); 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( - &mut self, - connector: Option>, - ) -> &mut Self { - self.http_connector = connector.map(|c| Tracked::new(self.builder_name, c.into_shared())); + /// Sets the HTTP client. + pub fn set_http_client(&mut self, connector: Option) -> &mut Self { + self.http_client = connector.map(|c| Tracked::new(self.builder_name, c.into_shared())); self } - /// Sets the HTTP connector. - pub fn with_http_connector( - mut self, - connector: Option>, - ) -> Self { - self.set_http_connector(connector); + /// Sets the HTTP client. + pub fn with_http_client(mut self, connector: Option) -> Self { + self.set_http_client(connector); self } @@ -321,7 +318,7 @@ impl RuntimeComponentsBuilder { /// Sets the endpoint resolver. pub fn set_endpoint_resolver( &mut self, - endpoint_resolver: Option>, + endpoint_resolver: Option, ) -> &mut Self { self.endpoint_resolver = endpoint_resolver.map(|s| Tracked::new(self.builder_name, s.into_shared())); @@ -331,7 +328,7 @@ impl RuntimeComponentsBuilder { /// Sets the endpoint resolver. pub fn with_endpoint_resolver( mut self, - endpoint_resolver: Option>, + endpoint_resolver: Option, ) -> Self { self.set_endpoint_resolver(endpoint_resolver); self @@ -343,17 +340,14 @@ impl RuntimeComponentsBuilder { } /// Adds an auth scheme. - pub fn push_auth_scheme( - &mut self, - auth_scheme: impl IntoShared, - ) -> &mut Self { + pub fn push_auth_scheme(&mut self, auth_scheme: impl AuthScheme + 'static) -> &mut Self { self.auth_schemes .push(Tracked::new(self.builder_name, auth_scheme.into_shared())); self } /// Adds an auth scheme. - pub fn with_auth_scheme(mut self, auth_scheme: impl IntoShared) -> Self { + pub fn with_auth_scheme(mut self, auth_scheme: impl AuthScheme + 'static) -> Self { self.push_auth_scheme(auth_scheme); self } @@ -362,7 +356,7 @@ impl RuntimeComponentsBuilder { pub fn push_identity_resolver( &mut self, scheme_id: AuthSchemeId, - identity_resolver: impl IntoShared, + identity_resolver: impl IdentityResolver + 'static, ) -> &mut Self { self.identity_resolvers.push(Tracked::new( self.builder_name, @@ -375,7 +369,7 @@ impl RuntimeComponentsBuilder { pub fn with_identity_resolver( mut self, scheme_id: AuthSchemeId, - identity_resolver: impl IntoShared, + identity_resolver: impl IdentityResolver + 'static, ) -> Self { self.push_identity_resolver(scheme_id, identity_resolver); self @@ -397,17 +391,14 @@ impl RuntimeComponentsBuilder { } /// Adds an interceptor. - pub fn push_interceptor( - &mut self, - interceptor: impl IntoShared, - ) -> &mut Self { + pub fn push_interceptor(&mut self, interceptor: impl Interceptor + 'static) -> &mut Self { self.interceptors .push(Tracked::new(self.builder_name, interceptor.into_shared())); self } /// Adds an interceptor. - pub fn with_interceptor(mut self, interceptor: impl IntoShared) -> Self { + pub fn with_interceptor(mut self, interceptor: impl Interceptor + 'static) -> Self { self.push_interceptor(interceptor); self } @@ -460,7 +451,7 @@ impl RuntimeComponentsBuilder { /// Sets the retry strategy. pub fn set_retry_strategy( &mut self, - retry_strategy: Option>, + retry_strategy: Option, ) -> &mut Self { self.retry_strategy = retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared())); @@ -470,7 +461,7 @@ impl RuntimeComponentsBuilder { /// Sets the retry strategy. pub fn with_retry_strategy( mut self, - retry_strategy: Option>, + retry_strategy: Option, ) -> Self { self.retry_strategy = retry_strategy.map(|s| Tracked::new(self.builder_name, s.into_shared())); @@ -489,8 +480,8 @@ 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 +497,8 @@ 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 } } @@ -532,15 +523,9 @@ impl RuntimeComponentsBuilder { /// Creates a runtime components builder with all the required components filled in with fake (panicking) implementations. #[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::endpoint::EndpointResolverParams; use crate::client::identity::Identity; - use crate::client::identity::IdentityResolver; - use crate::client::orchestrator::{Future, HttpRequest}; - use crate::client::retries::RetryStrategy; - use aws_smithy_async::rt::sleep::AsyncSleep; - use aws_smithy_async::time::TimeSource; + use crate::client::orchestrator::Future; use aws_smithy_types::config_bag::ConfigBag; use aws_smithy_types::endpoint::Endpoint; @@ -557,10 +542,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 +631,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 34548c0ab7..56596a6087 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 @@ -240,13 +240,21 @@ impl RuntimePlugins { Default::default() } - pub fn with_client_plugin(mut self, plugin: impl IntoShared) -> Self { - insert_plugin!(self.client_plugins, plugin.into_shared()); + /// Adds a client-level runtime plugin. + pub fn with_client_plugin(mut self, plugin: impl RuntimePlugin + 'static) -> Self { + insert_plugin!( + self.client_plugins, + IntoShared::::into_shared(plugin) + ); self } - pub fn with_operation_plugin(mut self, plugin: impl IntoShared) -> Self { - insert_plugin!(self.operation_plugins, plugin.into_shared()); + /// Adds an operation-level runtime plugin. + pub fn with_operation_plugin(mut self, plugin: impl RuntimePlugin + 'static) -> Self { + insert_plugin!( + self.operation_plugins, + IntoShared::::into_shared(plugin) + ); self } @@ -265,13 +273,16 @@ impl RuntimePlugins { } } -#[cfg(test)] +#[cfg(all(test, feature = "test-util"))] 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 +403,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 +420,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 +439,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 c45506c9f3..80b4f68ebe 100644 --- a/rust-runtime/aws-smithy-runtime-api/src/shared.rs +++ b/rust-runtime/aws-smithy-runtime-api/src/shared.rs @@ -19,8 +19,8 @@ #![cfg_attr( feature = "client", doc = " -For example, [`SharedHttpConnector`](crate::client::connectors::SharedHttpConnector), is -a shared type for the [`HttpConnector`](crate::client::connectors::HttpConnector) trait, +For example, [`SharedHttpConnector`](crate::client::http::SharedHttpConnector), is +a shared type for the [`HttpConnector`](crate::client::http::HttpConnector) trait, which allows for sharing a single HTTP connector instance (and its connection pool) among multiple clients. " )] @@ -63,9 +63,10 @@ The `IntoShared` trait is useful for making functions that take any `RuntimePlug For example, this function will convert the given `plugin` argument into a `SharedRuntimePlugin`. ```rust,no_run -# use aws_smithy_runtime_api::client::runtime_plugin::{SharedRuntimePlugin, StaticRuntimePlugin}; -# use aws_smithy_runtime_api::shared::{IntoShared, FromUnshared}; -fn take_shared(plugin: impl IntoShared) { +# use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, SharedRuntimePlugin}; +use aws_smithy_runtime_api::shared::IntoShared; + +fn take_shared(plugin: impl RuntimePlugin + 'static) { let _plugin: SharedRuntimePlugin = plugin.into_shared(); } ``` @@ -74,9 +75,9 @@ This can be called with different types, and even if a `SharedRuntimePlugin` is `SharedRuntimePlugin` inside of another `SharedRuntimePlugin`. ```rust,no_run -# use aws_smithy_runtime_api::client::runtime_plugin::{SharedRuntimePlugin, StaticRuntimePlugin}; +# use aws_smithy_runtime_api::client::runtime_plugin::{RuntimePlugin, SharedRuntimePlugin, StaticRuntimePlugin}; # use aws_smithy_runtime_api::shared::{IntoShared, FromUnshared}; -# fn take_shared(plugin: impl IntoShared) { +# fn take_shared(plugin: impl RuntimePlugin + 'static) { # let _plugin: SharedRuntimePlugin = plugin.into_shared(); # } // Automatically converts it to `SharedRuntimePlugin(StaticRuntimePlugin)` @@ -180,6 +181,14 @@ macro_rules! impl_shared_conversions { }; } +// TODO(https://github.com/awslabs/smithy-rs/issues/3016): 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 743d5d2dae..7b569d35c8 100644 --- a/rust-runtime/aws-smithy-runtime/Cargo.toml +++ b/rust-runtime/aws-smithy-runtime/Cargo.toml @@ -12,13 +12,16 @@ repository = "https://github.com/awslabs/smithy-rs" [features] client = ["aws-smithy-runtime-api/client"] http-auth = ["aws-smithy-runtime-api/http-auth"] +connector-hyper-0-14-x = ["dep:hyper", "hyper?/client", "hyper?/http2", "hyper?/http1", "hyper?/tcp"] +tls-rustls = ["dep:hyper-rustls", "dep:rustls", "connector-hyper-0-14-x"] +rt-tokio = ["tokio/rt"] + +# Features for testing 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"] +wire-mock = ["test-util", "connector-hyper-0-14-x", "hyper?/server"] [dependencies] aws-smithy-async = { path = "../aws-smithy-async" } -aws-smithy-client = { path = "../aws-smithy-client" } aws-smithy-http = { path = "../aws-smithy-http" } aws-smithy-protocol-test = { path = "../aws-smithy-protocol-test", optional = true } aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api" } @@ -44,7 +47,6 @@ approx = "0.5.1" aws-smithy-async = { path = "../aws-smithy-async", features = ["rt-tokio", "test-util"] } aws-smithy-runtime-api = { path = "../aws-smithy-runtime-api", features = ["test-util"] } aws-smithy-types = { path = "../aws-smithy-types", features = ["test-util"] } -hyper-tls = { version = "0.5.0" } tokio = { version = "1.25", features = ["macros", "rt", "test-util"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-test = "0.2.1" diff --git a/rust-runtime/aws-smithy-runtime/external-types.toml b/rust-runtime/aws-smithy-runtime/external-types.toml index f735d80eef..fe24b1a1b0 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 71d1a0ea0f..392dc12023 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 4666c04bc2..0000000000 --- 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.rs b/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util.rs deleted file mode 100644 index 686b710197..0000000000 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util.rs +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -//! Module with client connectors useful for testing. -//! -//! Each test connector is useful for different test use cases: -//! - [`capture_request`](capture_request::capture_request): If you don't care what the -//! response is, but just want to check that the serialized request is what you expect, -//! then use `capture_request`. Or, alternatively, if you don't care what the request -//! is, but want to always respond with a given response, then capture request can also -//! be useful since you can optionally give it a response to return. -//! - [`dvr`]: If you want to record real-world traffic and then replay it later, then DVR's -//! [`RecordingConnector`](dvr::RecordingConnector) and [`ReplayingConnector`](dvr::ReplayingConnector) -//! can accomplish this, and the recorded traffic can be saved to JSON and checked in. Note: if -//! the traffic recording has sensitive information in it, such as signatures or authorization, -//! you will need to manually scrub this out if you intend to store the recording alongside -//! your tests. -//! - [`EventConnector`]: If you want to have a set list of requests and their responses in a test, -//! then the event connector will be useful. On construction, it takes a list of tuples that represent -//! each expected request and the response for that request. At the end of the test, you can ask the -//! connector to verify that the requests matched the expectations. -//! - [`infallible_connection_fn`]: Allows you to create a connector from an infallible function -//! that takes a request and returns a response. -//! - [`NeverConnector`]: Useful for testing timeouts, where you want the connector to never respond. - -mod capture_request; -pub use capture_request::{capture_request, CaptureRequestHandler, CaptureRequestReceiver}; - -#[cfg(feature = "connector-hyper")] -pub mod dvr; - -mod event_connector; -pub use event_connector::{ConnectionEvent, EventConnector}; - -mod infallible; -pub use infallible::infallible_connection_fn; - -mod never; -pub use never::NeverConnector; diff --git a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs b/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs deleted file mode 100644 index 2150235c03..0000000000 --- a/rust-runtime/aws-smithy-runtime/src/client/connectors/test_util/event_connector.rs +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -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::orchestrator::{HttpRequest, HttpResponse}; -use http::header::{HeaderName, CONTENT_TYPE}; -use std::fmt::Debug; -use std::ops::Deref; -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -type ConnectionEvents = Vec; - -/// Test data for the [`EventConnector`]. -/// -/// Each `ConnectionEvent` represents one HTTP request and response -/// through the connector. Optionally, a latency value can be set to simulate -/// network latency (done via async sleep in the `EventConnector`). -#[derive(Debug)] -pub struct ConnectionEvent { - latency: Duration, - req: HttpRequest, - res: HttpResponse, -} - -impl ConnectionEvent { - /// Creates a new `ConnectionEvent`. - pub fn new(req: HttpRequest, res: HttpResponse) -> Self { - Self { - res, - req, - latency: Duration::from_secs(0), - } - } - - /// Add simulated latency to this `ConnectionEvent` - pub fn with_latency(mut self, latency: Duration) -> Self { - self.latency = latency; - self - } - - /// Returns the test request. - pub fn request(&self) -> &HttpRequest { - &self.req - } - - /// Returns the test response. - pub fn response(&self) -> &HttpResponse { - &self.res - } -} - -impl From<(HttpRequest, HttpResponse)> for ConnectionEvent { - fn from((req, res): (HttpRequest, HttpResponse)) -> Self { - Self::new(req, res) - } -} - -#[derive(Debug)] -struct ValidateRequest { - expected: HttpRequest, - actual: HttpRequest, -} - -impl ValidateRequest { - fn assert_matches(&self, index: usize, ignore_headers: &[HeaderName]) { - let (actual, expected) = (&self.actual, &self.expected); - assert_eq!( - actual.uri(), - expected.uri(), - "Request #{index} - URI doesn't match expected value" - ); - for (name, value) in expected.headers() { - if !ignore_headers.contains(name) { - let actual_header = actual - .headers() - .get(name) - .unwrap_or_else(|| panic!("Request #{index} - Header {name:?} is missing")); - assert_eq!( - actual_header.to_str().unwrap(), - value.to_str().unwrap(), - "Request #{index} - Header {name:?} doesn't match expected value", - ); - } - } - let actual_str = std::str::from_utf8(actual.body().bytes().unwrap_or(&[])); - let expected_str = std::str::from_utf8(expected.body().bytes().unwrap_or(&[])); - let media_type = if actual - .headers() - .get(CONTENT_TYPE) - .map(|v| v.to_str().unwrap().contains("json")) - .unwrap_or(false) - { - MediaType::Json - } else { - MediaType::Other("unknown".to_string()) - }; - match (actual_str, expected_str) { - (Ok(actual), Ok(expected)) => assert_ok(validate_body(actual, expected, media_type)), - _ => assert_eq!( - actual.body().bytes(), - expected.body().bytes(), - "Request #{index} - Body contents didn't match expected value" - ), - }; - } -} - -/// Request/response event-driven connector 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 { - data: Arc>, - requests: Arc>>, - sleep_impl: SharedAsyncSleep, -} - -impl EventConnector { - /// Creates a new event connector. - pub fn new(mut data: ConnectionEvents, sleep_impl: impl Into) -> Self { - data.reverse(); - EventConnector { - data: Arc::new(Mutex::new(data)), - requests: Default::default(), - sleep_impl: sleep_impl.into(), - } - } - - fn requests(&self) -> impl Deref> + '_ { - self.requests.lock().unwrap() - } - - /// Asserts the expected requests match the actual requests. - /// - /// The expected requests are given as the connection events when the `EventConnector` - /// is created. The `EventConnector` will record the actual requests and assert that - /// they match the expected requests. - /// - /// A list of headers that should be ignored when comparing requests can be passed - /// for cases where headers are non-deterministic or are irrelevant to the test. - #[track_caller] - pub fn assert_requests_match(&self, ignore_headers: &[HeaderName]) { - for (i, req) in self.requests().iter().enumerate() { - req.assert_matches(i, ignore_headers) - } - let remaining_requests = self.data.lock().unwrap(); - let number_of_remaining_requests = remaining_requests.len(); - let actual_requests = self.requests().len(); - assert!( - remaining_requests.is_empty(), - "Expected {number_of_remaining_requests} additional requests (only {actual_requests} sent)", - ); - } -} - -impl HttpConnector for EventConnector { - 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 { - expected: event.req, - actual: request, - }); - - (Ok(event.res.map(SdkBody::from)), event.latency) - } else { - ( - Err(ConnectorError::other("No more data".into(), None)), - Duration::from_secs(0), - ) - }; - - let sleep = self.sleep_impl.sleep(simulated_latency); - HttpConnectorFuture::new(async move { - sleep.await; - res - }) - } -} 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 dbd1d7c4cf..0000000000 --- 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 0000000000..3a311ccd98 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/dns.rs @@ -0,0 +1,48 @@ +/* + * 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, ResolveDnsError}; + 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(ResolveDnsError::new(IoError::new( + IoErrorKind::Other, + join_failure, + ))), + Ok(Ok(dns_result)) => { + Ok(dns_result.into_iter().map(|addr| addr.ip()).collect()) + } + Ok(Err(dns_failure)) => Err(ResolveDnsError::new(dns_failure)), + } + }) + } + } +} + +#[cfg(all(feature = "rt-tokio", not(target_family = "wasm")))] +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 0000000000..6f5960d073 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/http.rs @@ -0,0 +1,37 @@ +/* + * 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. +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 76% 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 9285394020..6ddfc8b962 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 @@ -4,29 +4,35 @@ */ 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_async::rt::sleep::{default_async_sleep, AsyncSleep, SharedAsyncSleep}; 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 AsyncSleep + 'static) -> 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,151 @@ 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_fn: F, +} + +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 + F: Fn() -> C + Send + Sync, + 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 tcp_connector = (self.tcp_connector_fn)(); + let connector = SharedHttpConnector::new(builder.build(tcp_connector)); + 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 [`SharedHttpClient`] from this builder and a given connector. + /// + #[cfg_attr( + feature = "tls-rustls", + doc = "Use [`build_https`](HyperClientBuilder::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_fn: move || tcp_connector.clone(), + }) + } + + #[cfg(all(test, feature = "test-util"))] + fn build_with_fn(self, tcp_connector_fn: F) -> SharedHttpClient + where + F: Fn() -> C + Send + Sync + 'static, + 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_fn, + }) + } +} + mod timeout_middleware { use aws_smithy_async::future::timeout::{TimedOutError, Timeout}; use aws_smithy_async::rt::sleep::Sleep; @@ -600,7 +765,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 +863,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 +900,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())) @@ -775,17 +935,74 @@ mod timeout_middleware { } } -#[cfg(test)] +#[cfg(all(test, feature = "test-util"))] mod test { use super::*; + use crate::client::http::test_util::NeverTcpConnector; use aws_smithy_http::body::SdkBody; + use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; use http::Uri; use hyper::client::connect::{Connected, Connection}; use std::io::{Error, ErrorKind}; use std::pin::Pin; + use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::Arc; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; + #[tokio::test] + async fn connector_selection() { + // Create a client that increments a count every time it creates a new HyperConnector + let creation_count = Arc::new(AtomicU32::new(0)); + let http_client = HyperClientBuilder::new().build_with_fn({ + let count = creation_count.clone(); + move || { + count.fetch_add(1, Ordering::Relaxed); + NeverTcpConnector::new() + } + }); + + // This configuration should result in 4 separate connectors with different timeout settings + let settings = [ + HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(3)) + .build(), + HttpConnectorSettings::builder() + .read_timeout(Duration::from_secs(3)) + .build(), + HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(3)) + .read_timeout(Duration::from_secs(3)) + .build(), + HttpConnectorSettings::builder() + .connect_timeout(Duration::from_secs(5)) + .read_timeout(Duration::from_secs(3)) + .build(), + ]; + + // Kick off thousands of parallel tasks that will try to create a connector + let components = RuntimeComponentsBuilder::for_tests().build().unwrap(); + let mut handles = Vec::new(); + for setting in &settings { + for _ in 0..1000 { + let client = http_client.clone(); + handles.push(tokio::spawn({ + let setting = setting.clone(); + let components = components.clone(); + async move { + let _ = client.http_connector(&setting, &components); + } + })); + } + } + for handle in handles { + handle.await.unwrap(); + } + + // Verify only 4 connectors were created amidst the chaos + assert_eq!(4, creation_count.load(Ordering::Relaxed)); + } + #[tokio::test] async fn hyper_io_error() { let connector = TestConnection { diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs new file mode 100644 index 0000000000..c05b3dca54 --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util.rs @@ -0,0 +1,58 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Various fake/mock clients for testing. +//! +//! Each test client is useful for different test use cases: +//! - [`capture_request`](capture_request::capture_request): If you don't care what the +//! response is, but just want to check that the serialized request is what you expect, +//! then use `capture_request`. Or, alternatively, if you don't care what the request +//! is, but want to always respond with a given response, then capture request can also +//! be useful since you can optionally give it a response to return. +#![cfg_attr( + feature = "connector-hyper-0-14-x", + doc = "- [`dvr`]: If you want to record real-world traffic and then replay it later, then DVR's" +)] +//! [`RecordingClient`](dvr::RecordingClient) and [`ReplayingClient`](dvr::ReplayingClient) +//! can accomplish this, and the recorded traffic can be saved to JSON and checked in. Note: if +//! the traffic recording has sensitive information in it, such as signatures or authorization, +//! you will need to manually scrub this out if you intend to store the recording alongside +//! your tests. +//! - [`StaticReplayClient`]: If you want to have a set list of requests and their responses in a test, +//! then the static replay client will be useful. On construction, it takes a list of request/response +//! pairs that represent each expected request and the response for that test. At the end of the test, +//! you can ask the client to verify that the requests matched the expectations. +//! - [`infallible_client_fn`]: Allows you to create a client from an infallible function +//! that takes a request and returns a response. +//! - [`NeverClient`]: Useful for testing timeouts, where you want the client to never respond. +//! +#![cfg_attr( + feature = "connector-hyper-0-14-x", + doc = " +There is also the [`NeverTcpConnector`], which makes it easy to test connect/read timeouts. + +Finally, for socket-level mocking, see the [`wire`] module. +" +)] +mod capture_request; +pub use capture_request::{capture_request, CaptureRequestHandler, CaptureRequestReceiver}; + +#[cfg(feature = "connector-hyper-0-14-x")] +pub mod dvr; + +mod replay; +pub use replay::{ReplayEvent, StaticReplayClient}; + +mod infallible; +pub use infallible::infallible_client_fn; + +mod never; +pub use never::NeverClient; + +#[cfg(feature = "connector-hyper-0-14-x")] +pub use never::NeverTcpConnector; + +#[cfg(all(feature = "connector-hyper-0-14-x", feature = "wire-mock"))] +pub mod wire; 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 7721af15de..06915c942e 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 94% 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 90f37b95ba..a4c5138dc5 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,12 +18,12 @@ 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 /// -/// A traffic recording can be replayed with [`RecordingConnector`](RecordingConnector) +/// A traffic recording can be replayed with [`RecordingClient`](RecordingClient) #[derive(Debug, Serialize, Deserialize)] pub struct NetworkTraffic { events: Vec, @@ -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 c175b24d3c..1c682ad7ed 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 HttpConnector + 'static) -> 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 4388e939be..f9386e624d 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/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 b311464774..491f26c0e2 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 0000000000..6afff513d2 --- /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 = connection::NeverTcpConnection; + 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 connection { + 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 connection type that appeases hyper's trait bounds for a TCP connector, but will panic if any of its traits are used. + #[non_exhaustive] + #[derive(Debug, Default)] + pub struct NeverTcpConnection; + + impl Connection for NeverTcpConnection { + fn connected(&self) -> Connected { + unreachable!() + } + } + + impl AsyncRead for NeverTcpConnection { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + _buf: &mut ReadBuf<'_>, + ) -> Poll> { + unreachable!() + } + } + + impl AsyncWrite for NeverTcpConnection { + 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/http/test_util/replay.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs new file mode 100644 index 0000000000..55bd50434c --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/replay.rs @@ -0,0 +1,260 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +use aws_smithy_http::result::ConnectorError; +use aws_smithy_protocol_test::{assert_ok, validate_body, MediaType}; +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::ops::Deref; +use std::sync::{Arc, Mutex, MutexGuard}; + +type ReplayEvents = Vec; + +/// Test data for the [`StaticReplayClient`]. +/// +/// Each `ReplayEvent` represents one HTTP request and response +/// through the connector. +#[derive(Debug)] +pub struct ReplayEvent { + request: HttpRequest, + response: HttpResponse, +} + +impl ReplayEvent { + /// Creates a new `ReplayEvent`. + pub fn new(request: HttpRequest, response: HttpResponse) -> Self { + Self { request, response } + } + + /// Returns the test request. + pub fn request(&self) -> &HttpRequest { + &self.request + } + + /// Returns the test response. + pub fn response(&self) -> &HttpResponse { + &self.response + } +} + +impl From<(HttpRequest, HttpResponse)> for ReplayEvent { + fn from((request, response): (HttpRequest, HttpResponse)) -> Self { + Self::new(request, response) + } +} + +#[derive(Debug)] +struct ValidateRequest { + expected: HttpRequest, + actual: HttpRequest, +} + +impl ValidateRequest { + fn assert_matches(&self, index: usize, ignore_headers: &[HeaderName]) { + let (actual, expected) = (&self.actual, &self.expected); + assert_eq!( + actual.uri(), + expected.uri(), + "Request #{index} - URI doesn't match expected value" + ); + for (name, value) in expected.headers() { + if !ignore_headers.contains(name) { + let actual_header = actual + .headers() + .get(name) + .unwrap_or_else(|| panic!("Request #{index} - Header {name:?} is missing")); + assert_eq!( + actual_header.to_str().unwrap(), + value.to_str().unwrap(), + "Request #{index} - Header {name:?} doesn't match expected value", + ); + } + } + let actual_str = std::str::from_utf8(actual.body().bytes().unwrap_or(&[])); + let expected_str = std::str::from_utf8(expected.body().bytes().unwrap_or(&[])); + let media_type = if actual + .headers() + .get(CONTENT_TYPE) + .map(|v| v.to_str().unwrap().contains("json")) + .unwrap_or(false) + { + MediaType::Json + } else { + MediaType::Other("unknown".to_string()) + }; + match (actual_str, expected_str) { + (Ok(actual), Ok(expected)) => assert_ok(validate_body(actual, expected, media_type)), + _ => assert_eq!( + actual.body().bytes(), + expected.body().bytes(), + "Request #{index} - Body contents didn't match expected value" + ), + }; + } +} + +/// Request/response replaying client for use in tests. +/// +/// This mock client takes a list of request/response pairs named [`ReplayEvent`]. While the client +/// is in use, the responses will be given in the order they appear in the list regardless of what +/// the actual request was. The actual request is recorded, but otherwise not validated against what +/// is in the [`ReplayEvent`]. Later, after the client is finished being used, the +/// [`assert_requests_match`] method can be used to validate the requests. +/// +/// This utility is simpler than [DVR], and thus, is good for tests that don't need +/// to record and replay real traffic. +/// +/// # Example +/// +/// ```no_run +/// use aws_smithy_http::body::SdkBody; +/// use aws_smithy_runtime::client::http::test_util::{ReplayEvent, StaticReplayClient}; +/// +/// let http_client = StaticReplayClient::new(vec![ +/// // Event that covers the first request/response +/// ReplayEvent::new( +/// // If `assert_requests_match` is called later, then this request will be matched +/// // against the actual request that was made. +/// http::Request::builder().uri("http://localhost:1234/foo").body(SdkBody::empty()).unwrap(), +/// // This response will be given to the first request regardless of whether it matches the request above. +/// http::Response::builder().status(200).body(SdkBody::empty()).unwrap(), +/// ), +/// // The next ReplayEvent covers the second request/response pair... +/// ]); +/// +/// # /* +/// let config = my_generated_client::Config::builder() +/// .http_client(http_client.clone()) +/// .build(); +/// let client = my_generated_client::Client::from_conf(config); +/// # */ +/// +/// // Do stuff with client... +/// +/// // When you're done, assert the requests match what you expected +/// http_client.assert_requests_match(&[]); +/// ``` +/// +/// [`assert_requests_match`]: crate::client::http::test_util::StaticReplayClient::assert_requests_match +/// [DVR]: crate::client::http::test_util::dvr +#[derive(Clone, Debug)] +pub struct StaticReplayClient { + data: Arc>, + requests: Arc>>, +} + +impl StaticReplayClient { + /// Creates a new event connector. + pub fn new(mut data: ReplayEvents) -> Self { + data.reverse(); + StaticReplayClient { + data: Arc::new(Mutex::new(data)), + requests: Default::default(), + } + } + + /// 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, + } + } + + fn requests(&self) -> impl Deref> + '_ { + self.requests.lock().unwrap() + } + + /// Asserts the expected requests match the actual requests. + /// + /// The expected requests are given as the connection events when the `EventConnector` + /// is created. The `EventConnector` will record the actual requests and assert that + /// they match the expected requests. + /// + /// A list of headers that should be ignored when comparing requests can be passed + /// for cases where headers are non-deterministic or are irrelevant to the test. + #[track_caller] + pub fn assert_requests_match(&self, ignore_headers: &[HeaderName]) { + for (i, req) in self.requests().iter().enumerate() { + req.assert_matches(i, ignore_headers) + } + let remaining_requests = self.data.lock().unwrap(); + let number_of_remaining_requests = remaining_requests.len(); + let actual_requests = self.requests().len(); + assert!( + remaining_requests.is_empty(), + "Expected {number_of_remaining_requests} additional requests (only {actual_requests} sent)", + ); + } +} + +impl HttpConnector for StaticReplayClient { + fn call(&self, request: HttpRequest) -> HttpConnectorFuture { + let res = if let Some(event) = self.data.lock().unwrap().pop() { + self.requests.lock().unwrap().push(ValidateRequest { + expected: event.request, + actual: request, + }); + + Ok(event.response) + } else { + Err(ConnectorError::other( + "StaticReplayClient: no more test data available to respond with".into(), + None, + )) + }; + + HttpConnectorFuture::new(async move { res }) + } +} + +impl HttpClient for StaticReplayClient { + fn http_connector( + &self, + _: &HttpConnectorSettings, + _: &RuntimeComponents, + ) -> SharedHttpConnector { + self.clone().into_shared() + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs new file mode 100644 index 0000000000..f5dd76e8db --- /dev/null +++ b/rust-runtime/aws-smithy-runtime/src/client/http/test_util/wire.rs @@ -0,0 +1,353 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +//! Utilities for mocking at the socket level +//! +//! Other tools in this module actually operate at the `http::Request` / `http::Response` level. This +//! is useful, but it shortcuts the HTTP implementation (e.g. Hyper). [`WireMockServer`] binds +//! to an actual socket on the host. +//! +//! # Examples +//! ```no_run +//! use aws_smithy_runtime_api::client::http::HttpConnectorSettings; +//! use aws_smithy_runtime::client::http::test_util::wire::{check_matches, ReplayedEvent, WireMockServer}; +//! use aws_smithy_runtime::{match_events, ev}; +//! # async fn example() { +//! +//! // This connection binds to a local address +//! let mock = WireMockServer::start(vec![ +//! ReplayedEvent::status(503), +//! ReplayedEvent::status(200) +//! ]).await; +//! +//! # /* +//! // Create a client using the wire mock +//! let config = my_generated_client::Config::builder() +//! .http_client(mock.http_client()) +//! .build(); +//! let client = Client::from_conf(config); +//! +//! // ... do something with +//! # */ +//! +//! // assert that you got the events you expected +//! match_events!(ev!(dns), ev!(connect), ev!(http(200)))(&mock.events()); +//! # } +//! ``` + +#![allow(missing_docs)] + +use crate::client::http::hyper_014::HyperClientBuilder; +use aws_smithy_async::future::never::Never; +use aws_smithy_async::future::BoxFuture; +use aws_smithy_runtime_api::client::http::SharedHttpClient; +use aws_smithy_runtime_api::shared::IntoShared; +use bytes::Bytes; +use http::{Request, Response}; +use hyper::client::connect::dns::Name; +use hyper::server::conn::AddrStream; +use hyper::service::{make_service_fn, service_fn, Service}; +use hyper::{Body, Server}; +use std::collections::HashSet; +use std::convert::Infallible; +use std::error::Error; +use std::iter::Once; +use std::net::{SocketAddr, TcpListener}; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; +use tokio::spawn; +use tokio::sync::oneshot; + +/// An event recorded by [`WireMockServer`]. +#[non_exhaustive] +#[derive(Debug, Clone)] +pub enum RecordedEvent { + DnsLookup(String), + NewConnection, + Response(ReplayedEvent), +} + +type Matcher = ( + Box Result<(), Box>>, + &'static str, +); + +/// This method should only be used by the macro +#[doc(hidden)] +pub fn check_matches(events: &[RecordedEvent], matchers: &[Matcher]) { + let mut events_iter = events.iter(); + let mut matcher_iter = matchers.iter(); + let mut idx = -1; + loop { + idx += 1; + let bail = |err: Box| panic!("failed on event {}:\n {}", idx, err); + match (events_iter.next(), matcher_iter.next()) { + (Some(event), Some((matcher, _msg))) => matcher(event).unwrap_or_else(bail), + (None, None) => return, + (Some(event), None) => { + bail(format!("got {:?} but no more events were expected", event).into()) + } + (None, Some((_expect, msg))) => { + bail(format!("expected {:?} but no more events were expected", msg).into()) + } + } + } +} + +#[macro_export] +macro_rules! matcher { + ($expect:tt) => { + ( + Box::new( + |event: &$crate::client::http::test_util::wire::RecordedEvent| { + if !matches!(event, $expect) { + return Err(format!( + "expected `{}` but got {:?}", + stringify!($expect), + event + ) + .into()); + } + Ok(()) + }, + ), + stringify!($expect), + ) + }; +} + +/// Helper macro to generate a series of test expectations +#[macro_export] +macro_rules! match_events { + ($( $expect:pat),*) => { + |events| { + $crate::client::http::test_util::wire::check_matches(events, &[$( $crate::matcher!($expect) ),*]); + } + }; + } + +/// Helper to generate match expressions for events +#[macro_export] +macro_rules! ev { + (http($status:expr)) => { + $crate::client::http::test_util::wire::RecordedEvent::Response( + $crate::client::http::test_util::wire::ReplayedEvent::HttpResponse { + status: $status, + .. + }, + ) + }; + (dns) => { + $crate::client::http::test_util::wire::RecordedEvent::DnsLookup(_) + }; + (connect) => { + $crate::client::http::test_util::wire::RecordedEvent::NewConnection + }; + (timeout) => { + $crate::client::http::test_util::wire::RecordedEvent::Response( + $crate::client::http::test_util::wire::ReplayedEvent::Timeout, + ) + }; +} + +pub use {ev, match_events, matcher}; + +#[non_exhaustive] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ReplayedEvent { + Timeout, + HttpResponse { status: u16, body: Bytes }, +} + +impl ReplayedEvent { + pub fn ok() -> Self { + Self::HttpResponse { + status: 200, + body: Bytes::new(), + } + } + + pub fn with_body(body: &str) -> Self { + Self::HttpResponse { + status: 200, + body: Bytes::copy_from_slice(body.as_ref()), + } + } + + pub fn status(status: u16) -> Self { + Self::HttpResponse { + status, + body: Bytes::new(), + } + } +} + +/// Test server that binds to 127.0.0.1:0 +/// +/// See the [module docs](crate::client::http::test_util::wire) for a usage example. +/// +/// Usage: +/// - Call [`WireMockServer::start`] to start the server +/// - Use [`WireMockServer::http_client`] or [`dns_resolver`](WireMockServer::dns_resolver) to configure your client. +/// - Make requests to [`endpoint_url`](WireMockServer::endpoint_url). +/// - Once the test is complete, retrieve a list of events from [`WireMockServer::events`] +#[derive(Debug)] +pub struct WireMockServer { + event_log: Arc>>, + bind_addr: SocketAddr, + // when the sender is dropped, that stops the server + shutdown_hook: oneshot::Sender<()>, +} + +impl WireMockServer { + /// Start a wire mock server with the given events to replay. + pub async fn start(mut response_events: Vec) -> Self { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let (tx, rx) = oneshot::channel(); + let listener_addr = listener.local_addr().unwrap(); + response_events.reverse(); + let response_events = Arc::new(Mutex::new(response_events)); + let handler_events = response_events; + let wire_events = Arc::new(Mutex::new(vec![])); + let wire_log_for_service = wire_events.clone(); + let poisoned_conns: Arc>> = Default::default(); + let make_service = make_service_fn(move |connection: &AddrStream| { + let poisoned_conns = poisoned_conns.clone(); + let events = handler_events.clone(); + let wire_log = wire_log_for_service.clone(); + let remote_addr = connection.remote_addr(); + tracing::info!("established connection: {:?}", connection); + wire_log.lock().unwrap().push(RecordedEvent::NewConnection); + async move { + Ok::<_, Infallible>(service_fn(move |_: Request| { + if poisoned_conns.lock().unwrap().contains(&remote_addr) { + tracing::error!("poisoned connection {:?} was reused!", &remote_addr); + panic!("poisoned connection was reused!"); + } + let next_event = events.clone().lock().unwrap().pop(); + let wire_log = wire_log.clone(); + let poisoned_conns = poisoned_conns.clone(); + async move { + let next_event = next_event + .unwrap_or_else(|| panic!("no more events! Log: {:?}", wire_log)); + wire_log + .lock() + .unwrap() + .push(RecordedEvent::Response(next_event.clone())); + if next_event == ReplayedEvent::Timeout { + tracing::info!("{} is poisoned", remote_addr); + poisoned_conns.lock().unwrap().insert(remote_addr); + } + tracing::debug!("replying with {:?}", next_event); + let event = generate_response_event(next_event).await; + dbg!(event) + } + })) + } + }); + let server = Server::from_tcp(listener) + .unwrap() + .serve(make_service) + .with_graceful_shutdown(async { + rx.await.ok(); + tracing::info!("server shutdown!"); + }); + spawn(server); + Self { + event_log: wire_events, + bind_addr: listener_addr, + shutdown_hook: tx, + } + } + + /// Retrieve the events recorded by this connection + pub fn events(&self) -> Vec { + self.event_log.lock().unwrap().clone() + } + + fn bind_addr(&self) -> SocketAddr { + self.bind_addr + } + + pub fn dns_resolver(&self) -> LoggingDnsResolver { + let event_log = self.event_log.clone(); + let bind_addr = self.bind_addr; + LoggingDnsResolver { + log: event_log, + socket_addr: bind_addr, + } + } + + /// Prebuilt [`HttpClient`](aws_smithy_runtime_api::client::http::HttpClient) with correctly wired DNS resolver. + /// + /// **Note**: This must be used in tandem with [`Self::dns_resolver`] + pub fn http_client(&self) -> SharedHttpClient { + HyperClientBuilder::new() + .build(hyper::client::HttpConnector::new_with_resolver( + self.dns_resolver(), + )) + .into_shared() + } + + /// Endpoint to use when connecting + /// + /// This works in tandem with the [`Self::dns_resolver`] to bind to the correct local IP Address + pub fn endpoint_url(&self) -> String { + format!( + "http://this-url-is-converted-to-localhost.com:{}", + self.bind_addr().port() + ) + } + + /// Shuts down the mock server. + pub fn shutdown(self) { + let _ = self.shutdown_hook.send(()); + } +} + +async fn generate_response_event(event: ReplayedEvent) -> Result, Infallible> { + let resp = match event { + ReplayedEvent::HttpResponse { status, body } => http::Response::builder() + .status(status) + .body(hyper::Body::from(body)) + .unwrap(), + ReplayedEvent::Timeout => { + Never::new().await; + unreachable!() + } + }; + Ok::<_, Infallible>(resp) +} + +/// DNS resolver that keeps a log of all lookups +/// +/// Regardless of what hostname is requested, it will always return the same socket address. +#[derive(Clone, Debug)] +pub struct LoggingDnsResolver { + log: Arc>>, + socket_addr: SocketAddr, +} + +impl Service for LoggingDnsResolver { + type Response = Once; + type Error = Infallible; + type Future = BoxFuture; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Name) -> Self::Future { + let socket_addr = self.socket_addr; + let log = self.log.clone(); + Box::pin(async move { + println!("looking up {:?}, replying with {:?}", req, socket_addr); + log.lock() + .unwrap() + .push(RecordedEvent::DnsLookup(req.to_string())); + Ok(std::iter::once(socket_addr)) + }) + } +} diff --git a/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs b/rust-runtime/aws-smithy-runtime/src/client/orchestrator.rs index 85fdd34564..576eab5cf3 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(|| - OrchestratorError::other("No HTTP connector was available to send this request. \ - Enable the `rustls` crate feature or set a connector to fix this.") + let http_client = halt_on_err!([ctx] => runtime_components.http_client().ok_or_else(|| + OrchestratorError::other("No HTTP client was available to send this request. \ + Enable the `rustls` crate feature or configure a HTTP client 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 3c483ca593..9c4e71237c 100644 --- a/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs +++ b/rust-runtime/aws-smithy-runtime/src/client/orchestrator/operation.rs @@ -4,29 +4,30 @@ */ use crate::client::auth::no_auth::{NoAuthScheme, NO_AUTH_SCHEME_ID}; -use crate::client::connectors::connection_poisoning::ConnectionPoisoningInterceptor; +use crate::client::http::connection_poisoning::ConnectionPoisoningInterceptor; +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}; -use aws_smithy_async::rt::sleep::SharedAsyncSleep; -use aws_smithy_async::time::SharedTimeSource; +use aws_smithy_async::rt::sleep::AsyncSleep; +use aws_smithy_async::time::TimeSource; use aws_smithy_http::result::SdkError; use aws_smithy_runtime_api::box_error::BoxError; use aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptionResolver; 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::HttpClient; 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; +use aws_smithy_runtime_api::client::interceptors::Interceptor; use aws_smithy_runtime_api::client::orchestrator::HttpResponse; use aws_smithy_runtime_api::client::orchestrator::{HttpRequest, OrchestratorError}; use aws_smithy_runtime_api::client::retries::{RetryClassifiers, SharedRetryStrategy}; use aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder; use aws_smithy_runtime_api::client::runtime_plugin::{ - RuntimePlugins, SharedRuntimePlugin, StaticRuntimePlugin, + RuntimePlugin, RuntimePlugins, SharedRuntimePlugin, StaticRuntimePlugin, }; use aws_smithy_runtime_api::client::ser_de::{ RequestSerializer, ResponseDeserializer, SharedRequestSerializer, SharedResponseDeserializer, @@ -34,6 +35,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; @@ -190,8 +192,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 HttpClient + 'static) -> Self { + self.runtime_components.set_http_client(Some(connector)); self } @@ -217,6 +219,7 @@ impl OperationBuilder { } pub fn standard_retry(mut self, retry_config: &RetryConfig) -> Self { + self.config.store_put(retry_config.clone()); self.runtime_components .set_retry_strategy(Some(SharedRetryStrategy::new(StandardRetryStrategy::new( retry_config, @@ -224,6 +227,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(())); @@ -240,17 +248,19 @@ 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 AsyncSleep + 'static) -> 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 TimeSource + 'static) -> Self { + self.runtime_components + .set_time_source(Some(time_source.into_shared())); self } - pub fn interceptor(mut self, interceptor: impl IntoShared) -> Self { + pub fn interceptor(mut self, interceptor: impl Interceptor + 'static) -> Self { self.runtime_components.push_interceptor(interceptor); self } @@ -260,7 +270,7 @@ impl OperationBuilder { self.interceptor(ConnectionPoisoningInterceptor::new()) } - pub fn runtime_plugin(mut self, runtime_plugin: impl IntoShared) -> Self { + pub fn runtime_plugin(mut self, runtime_plugin: impl RuntimePlugin + 'static) -> Self { self.runtime_plugins.push(runtime_plugin.into_shared()); self } @@ -312,11 +322,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); } @@ -329,8 +341,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. Enable the `rustls` crate feature or configure a HTTP client to fix this." ); assert!( components.endpoint_resolver().is_some(), @@ -352,6 +364,10 @@ impl OperationBuilder { config.load::().is_some(), "endpoint resolver params are required" ); + assert!( + config.load::().is_some(), + "timeout config is required" + ); } Operation { @@ -366,7 +382,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, ReplayEvent, StaticReplayClient}; use crate::client::retries::classifier::HttpStatusCodeClassifier; use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep}; use aws_smithy_http::body::SdkBody; @@ -384,10 +400,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())) @@ -414,41 +431,39 @@ mod tests { #[tokio::test] async fn operation_retries() { - let connector = EventConnector::new( - vec![ - ConnectionEvent::new( - http::Request::builder() - .uri("http://localhost:1234/") - .body(SdkBody::from(&b"what are you?"[..])) - .unwrap(), - http::Response::builder() - .status(503) - .body(SdkBody::from(&b""[..])) - .unwrap(), - ), - ConnectionEvent::new( - http::Request::builder() - .uri("http://localhost:1234/") - .body(SdkBody::from(&b"what are you?"[..])) - .unwrap(), - http::Response::builder() - .status(418) - .body(SdkBody::from(&b"I'm a teapot!"[..])) - .unwrap(), - ), - ], - SharedAsyncSleep::new(TokioSleep::new()), - ); + let connector = StaticReplayClient::new(vec![ + ReplayEvent::new( + http::Request::builder() + .uri("http://localhost:1234/") + .body(SdkBody::from(&b"what are you?"[..])) + .unwrap(), + http::Response::builder() + .status(503) + .body(SdkBody::from(&b""[..])) + .unwrap(), + ), + ReplayEvent::new( + http::Request::builder() + .uri("http://localhost:1234/") + .body(SdkBody::from(&b"what are you?"[..])) + .unwrap(), + http::Response::builder() + .status(418) + .body(SdkBody::from(&b"I'm a teapot!"[..])) + .unwrap(), + ), + ]); 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 c149878dda..e2e26418eb 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; diff --git a/tools/ci-resources/tls-stub/Cargo.toml b/tools/ci-resources/tls-stub/Cargo.toml index aea6e0a76d..a70780c482 100644 --- a/tools/ci-resources/tls-stub/Cargo.toml +++ b/tools/ci-resources/tls-stub/Cargo.toml @@ -13,7 +13,7 @@ publish = false aws-config = {path = "../../../aws/sdk/build/aws-sdk/sdk/aws-config", features = ["client-hyper"] } aws-credential-types = { path = "../../../aws/sdk/build/aws-sdk/sdk/aws-credential-types", features = ["hardcoded-credentials"] } aws-sdk-sts = { path = "../../../aws/sdk/build/aws-sdk/sdk/sts" } -aws-smithy-client = { path = "../../../aws/sdk/build/aws-sdk/sdk/aws-smithy-client", features = ["client-hyper", "rustls"] } +aws-smithy-runtime = { path = "../../../aws/sdk/build/aws-sdk/sdk/aws-smithy-runtime", features = ["client", "connector-hyper-0-14-x"] } exitcode = "1" hyper-rustls = { version = "0.24", features = ["rustls-native-certs", "http2"] } rustls = "0.21" diff --git a/tools/ci-resources/tls-stub/src/main.rs b/tools/ci-resources/tls-stub/src/main.rs index 8daebc5a7d..f674d0116b 100644 --- a/tools/ci-resources/tls-stub/src/main.rs +++ b/tools/ci-resources/tls-stub/src/main.rs @@ -11,6 +11,7 @@ use std::time::Duration; use aws_config::timeout::TimeoutConfig; use aws_credential_types::Credentials; use aws_sdk_sts::error::SdkError; +use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; #[cfg(debug_assertions)] use x509_parser::prelude::*; @@ -103,15 +104,15 @@ async fn create_client( .unwrap() .with_root_certificates(roots) .with_no_client_auth(); - let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config(tls_config) .https_only() .enable_http1() .enable_http2() .build(); - let smithy_connector = aws_smithy_client::hyper_ext::Adapter::builder().build(https_connector); + let http_client = HyperClientBuilder::new().build(tls_connector); let sdk_config = aws_config::from_env() - .http_connector(smithy_connector) + .http_client(http_client) .credentials_provider(credentials) .region("us-nether-1") .endpoint_url(format!("https://{host}:{port}"))