Unverified Commit 9587dbc0 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Lazy initialize the default HTTP client (#3262)



This change initializes the TLS trust certs at base client validation
time rather than upon creation of the default HTTP client runtime plugin
so that if the default is overridden, that initialization doesn't need
to take place. This is especially helpful on MacOS where that
initialization takes approximately 100 milliseconds.

----

_By submitting this pull request, I confirm that you can use, modify,
copy, and redistribute this contribution, under the terms of your
choice._

---------

Co-authored-by: default avatarRussell Cohen <rcoh@amazon.com>
parent 5b93fd2f
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -11,6 +11,18 @@
# meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client | server | all"}
# author = "rcoh"

[[aws-sdk-rust]]
message = "Loading native TLS trusted certs for the default HTTP client now only occurs if the default HTTP client is not overridden in config."
references = ["smithy-rs#3262"]
meta = { "breaking" = false, "tada" = false, "bug" = true }
author = "jdisanti"

[[smithy-rs]]
message = "Loading native TLS trusted certs for the default HTTP client now only occurs if the default HTTP client is not overridden in config."
references = ["smithy-rs#3262"]
meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "client" }
author = "jdisanti"

[[aws-sdk-rust]]
message = """Client creation now takes microseconds instead of milliseconds.
Previously, it would take 2-3 milliseconds for each client instantiation due to time spent compiling regexes.
+8 −4
Original line number Diff line number Diff line
@@ -6,6 +6,8 @@
use aws_credential_types::provider::SharedCredentialsProvider;
use aws_credential_types::Credentials;
use aws_smithy_async::rt::sleep::{SharedAsyncSleep, TokioSleep};
use aws_smithy_runtime::client::http::test_util::NeverClient;
use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
use aws_smithy_runtime_api::client::result::SdkError;
use aws_smithy_types::timeout::TimeoutConfig;
use aws_types::region::Region;
@@ -13,9 +15,10 @@ use aws_types::SdkConfig;
use std::time::Duration;
use tokio::time::Instant;

/// Use a 5 second operation timeout on SdkConfig and a 0ms connect timeout on the service config
/// Use a 5 second operation timeout on SdkConfig and a 0ms operation timeout on the service config
#[tokio::test]
async fn timeouts_can_be_set_by_service() {
    let (_guard, _) = capture_test_logs();
    let sdk_config = SdkConfig::builder()
        .credentials_provider(SharedCredentialsProvider::new(Credentials::for_tests()))
        .region(Region::from_static("us-east-1"))
@@ -25,6 +28,7 @@ async fn timeouts_can_be_set_by_service() {
                .operation_timeout(Duration::from_secs(5))
                .build(),
        )
        .http_client(NeverClient::new())
        // ip that
        .endpoint_url(
            // Emulate a connect timeout error by hitting an unroutable IP
@@ -34,7 +38,7 @@ async fn timeouts_can_be_set_by_service() {
    let config = aws_sdk_s3::config::Builder::from(&sdk_config)
        .timeout_config(
            TimeoutConfig::builder()
                .connect_timeout(Duration::from_secs(0))
                .operation_timeout(Duration::from_secs(0))
                .build(),
        )
        .build();
@@ -48,8 +52,8 @@ async fn timeouts_can_be_set_by_service() {
        .await
        .expect_err("unroutable IP should timeout");
    match err {
        SdkError::DispatchFailure(err) => assert!(err.is_timeout()),
        // if the connect timeout is not respected, this times out after 1 second because of the operation timeout with `SdkError::Timeout`
        SdkError::TimeoutError(_err) => { /* ok */ }
        // if the connect timeout is not respected, this times out after 5 seconds because of the operation timeout with `SdkError::Timeout`
        _other => panic!("unexpected error: {:?}", _other),
    }
    // there should be a 0ms timeout, we gotta set some stuff up. Just want to make sure
+9 −0
Original line number Diff line number Diff line
Validate the base client configuration.

This gets called upon client construction. The full config may not be available at
this time (hence why it has [`RuntimeComponentsBuilder`] as an argument rather
than [`RuntimeComponents`]). Any error returned here will become a panic
in the client constructor.

[`RuntimeComponentsBuilder`]: crate::client::runtime_components::RuntimeComponentsBuilder
[`RuntimeComponents`]: crate::client::runtime_components::RuntimeComponents
+7 −0
Original line number Diff line number Diff line
Validate the final client configuration.

This gets called immediately after the [`Intercept::read_before_execution`] trait hook
when the final configuration has been resolved. Any error returned here will
cause the operation to return that error.

[`Intercept::read_before_execution`]: crate::client::interceptors::Intercept::read_before_execution
+41 −2
Original line number Diff line number Diff line
@@ -50,11 +50,13 @@
//! [`tower`]: https://crates.io/crates/tower
//! [`aws-smithy-runtime`]: https://crates.io/crates/aws-smithy-runtime

use crate::box_error::BoxError;
use crate::client::orchestrator::{HttpRequest, HttpResponse};
use crate::client::result::ConnectorError;
use crate::client::runtime_components::sealed::ValidateConfig;
use crate::client::runtime_components::RuntimeComponents;
use crate::client::runtime_components::{RuntimeComponents, RuntimeComponentsBuilder};
use crate::impl_shared_conversions;
use aws_smithy_types::config_bag::ConfigBag;
use std::fmt;
use std::sync::Arc;
use std::time::Duration;
@@ -143,6 +145,26 @@ pub trait HttpClient: Send + Sync + fmt::Debug {
        settings: &HttpConnectorSettings,
        components: &RuntimeComponents,
    ) -> SharedHttpConnector;

    #[doc = include_str!("../../rustdoc/validate_base_client_config.md")]
    fn validate_base_client_config(
        &self,
        runtime_components: &RuntimeComponentsBuilder,
        cfg: &ConfigBag,
    ) -> Result<(), BoxError> {
        let _ = (runtime_components, cfg);
        Ok(())
    }

    #[doc = include_str!("../../rustdoc/validate_final_config.md")]
    fn validate_final_config(
        &self,
        runtime_components: &RuntimeComponents,
        cfg: &ConfigBag,
    ) -> Result<(), BoxError> {
        let _ = (runtime_components, cfg);
        Ok(())
    }
}

/// Shared HTTP client for use across multiple clients and requests.
@@ -170,7 +192,24 @@ impl HttpClient for SharedHttpClient {
    }
}

impl ValidateConfig for SharedHttpClient {}
impl ValidateConfig for SharedHttpClient {
    fn validate_base_client_config(
        &self,
        runtime_components: &super::runtime_components::RuntimeComponentsBuilder,
        cfg: &aws_smithy_types::config_bag::ConfigBag,
    ) -> Result<(), crate::box_error::BoxError> {
        self.selector
            .validate_base_client_config(runtime_components, cfg)
    }

    fn validate_final_config(
        &self,
        runtime_components: &RuntimeComponents,
        cfg: &aws_smithy_types::config_bag::ConfigBag,
    ) -> Result<(), crate::box_error::BoxError> {
        self.selector.validate_final_config(runtime_components, cfg)
    }
}

impl_shared_conversions!(convert SharedHttpClient from HttpClient using SharedHttpClient::new);

Loading