Loading aws/rust-runtime/aws-endpoint/src/lib.rs +1 −1 Original line number Diff line number Diff line Loading @@ -161,7 +161,7 @@ pub fn set_endpoint_resolver(config: &mut PropertyBag, provider: AwsEndpointReso /// 3. Apply the endpoint to the URI in the request /// 4. Set the `SigningRegion` and `SigningService` in the property bag to drive downstream /// signing middleware. #[derive(Clone, Debug)] #[derive(Clone)] pub struct AwsEndpointStage; #[derive(Debug)] Loading aws/rust-runtime/aws-http/src/user_agent.rs +1 −1 Original line number Diff line number Diff line Loading @@ -216,7 +216,7 @@ impl Display for ExecEnvMetadata { } #[non_exhaustive] #[derive(Default, Clone, Debug)] #[derive(Default, Clone)] pub struct UserAgentStage; impl UserAgentStage { Loading aws/rust-runtime/aws-hyper/Cargo.toml +2 −3 Original line number Diff line number Diff line Loading @@ -10,8 +10,8 @@ license = "Apache-2.0" [features] test-util = ["protocol-test-helpers"] default = ["test-util"] native-tls = ["hyper-tls", "smithy-client/native-tls"] rustls = ["hyper-rustls", "smithy-client/rustls"] native-tls = ["hyper-tls"] rustls = ["hyper-rustls"] [dependencies] hyper = { version = "0.14.2", features = ["client", "http1", "http2", "tcp", "runtime"] } Loading @@ -28,7 +28,6 @@ http-body = "0.4.0" smithy-http = { path = "../../../rust-runtime/smithy-http" } smithy-types = { path = "../../../rust-runtime/smithy-types" } smithy-http-tower = { path = "../../../rust-runtime/smithy-http-tower" } smithy-client = { path = "../../../rust-runtime/smithy-client" } fastrand = "1.4.0" tokio = { version = "1", features = ["time"] } Loading aws/rust-runtime/aws-hyper/src/conn.rs 0 → 100644 +203 −0 Original line number Diff line number Diff line /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ use crate::BoxError; use http::Request; use hyper::client::ResponseFuture; use hyper::Response; use smithy_http::body::SdkBody; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use tower::Service; #[derive(Clone)] pub struct Standard(Connector); impl Standard { /// An https connection /// /// If the `rustls` feature is enabled, this will use `rustls`. /// If the ONLY the `native-tls` feature is enabled, this will use `native-tls`. /// If both features are enabled, this will use `rustls` #[cfg(any(feature = "native-tls", feature = "rustls"))] pub fn https() -> Self { #[cfg(feature = "rustls")] { Self::rustls() } // If we are compiling this function & rustls is not enabled, then native-tls MUST be enabled #[cfg(not(feature = "rustls"))] { Self::native_tls() } } #[cfg(feature = "rustls")] pub fn rustls() -> Self { let https = hyper_rustls::HttpsConnector::with_native_roots(); let client = hyper::Client::builder().build::<_, SdkBody>(https); Self(Connector::RustlsHttps(client)) } #[cfg(feature = "native-tls")] pub fn native_tls() -> Self { let https = hyper_tls::HttpsConnector::new(); let client = hyper::Client::builder().build::<_, SdkBody>(https); Self(Connector::NativeHttps(client)) } /// A connection based on the provided `impl HttpService` /// /// Generally, [`Standard::https()`](Standard::https) should be used. This constructor is intended to support /// using things like [`TestConnection`](crate::test_connection::TestConnection) or alternative /// http implementations. pub fn new(connector: impl HttpService + 'static) -> Self { Self(Connector::Dyn(Box::new(connector))) } } /// An Http connection type for most use cases /// /// This supports three options: /// 1. HTTPS /// 2. A `TestConnection` /// 3. Any implementation of the `HttpService` trait /// /// This is designed to be used with [`aws_hyper::Client`](crate::Client) as a connector. #[derive(Clone)] enum Connector { /// An Https Connection /// /// This is the correct connection for use cases talking to real AWS services. #[cfg(feature = "native-tls")] NativeHttps(hyper::Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>, SdkBody>), /// An Https Connection /// /// This is the correct connection for use cases talking to real AWS services. #[cfg(feature = "rustls")] RustlsHttps(hyper::Client<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>, SdkBody>), /// A generic escape hatch /// /// This enables using any implementation of the HttpService trait. This allows using a totally /// separate HTTP stack or your own custom `TestConnection`. Dyn(Box<dyn HttpService>), } impl Clone for Box<dyn HttpService> { fn clone(&self) -> Self { self.clone_box() } } pub trait HttpService: Send + Sync { /// Return whether this service is ready to accept a request /// /// See [`Service::poll_ready`](tower::Service::poll_ready) fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), BoxError>>; /// Call this service and return a response /// /// See [`Service::call`](tower::Service::call) fn call( &mut self, req: http::Request<SdkBody>, ) -> Pin<Box<dyn Future<Output = Result<http::Response<SdkBody>, BoxError>> + Send>>; /// Return a Boxed-clone of this service /// /// `aws_hyper::Client` will clone the inner service for each request so this should be a cheap /// clone operation. fn clone_box(&self) -> Box<dyn HttpService>; } /// Reverse implementation: If you have a correctly shaped tower service, it _is_ an `HttpService` /// /// This is to facilitate ease of use for people using `Standard::Dyn` impl<S> HttpService for S where S: Service<http::Request<SdkBody>, Response = http::Response<SdkBody>> + Send + Sync + Clone + 'static, S::Error: Into<BoxError> + Send + Sync + 'static, S::Future: Send + 'static, { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), BoxError>> { Service::poll_ready(self, cx).map_err(|err| err.into()) } fn call( &mut self, req: Request<SdkBody>, ) -> Pin<Box<dyn Future<Output = Result<Response<SdkBody>, BoxError>> + Send>> { let fut = Service::call(self, req); let fut = async move { fut.await .map(|res| res.map(SdkBody::from)) .map_err(|err| err.into()) }; Box::pin(fut) } fn clone_box(&self) -> Box<dyn HttpService> { Box::new(self.clone()) } } impl tower::Service<http::Request<SdkBody>> for Standard { type Response = http::Response<SdkBody>; type Error = BoxError; type Future = StandardFuture; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { match &mut self.0 { #[cfg(feature = "native-tls")] Connector::NativeHttps(https) => { Service::poll_ready(https, cx).map_err(|err| err.into()) } #[cfg(feature = "rustls")] Connector::RustlsHttps(https) => { Service::poll_ready(https, cx).map_err(|err| err.into()) } Connector::Dyn(conn) => conn.poll_ready(cx), } } fn call(&mut self, req: http::Request<SdkBody>) -> Self::Future { match &mut self.0 { #[cfg(feature = "native-tls")] Connector::NativeHttps(https) => StandardFuture::Https(Service::call(https, req)), #[cfg(feature = "rustls")] Connector::RustlsHttps(https) => StandardFuture::Https(Service::call(https, req)), Connector::Dyn(conn) => StandardFuture::Dyn(conn.call(req)), } } } /// Future returned by `Standard` when used as a tower::Service #[pin_project::pin_project(project = FutProj)] pub enum StandardFuture { Https(#[pin] ResponseFuture), Dyn(#[pin] Pin<Box<dyn Future<Output = Result<http::Response<SdkBody>, BoxError>> + Send>>), } impl Future for StandardFuture { type Output = Result<http::Response<SdkBody>, BoxError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { match self.project() { FutProj::Https(fut) => fut .poll(cx) .map(|resp| resp.map(|res| res.map(SdkBody::from))) .map_err(|err| err.into()), FutProj::Dyn(dyn_fut) => dyn_fut.poll(cx), } } } aws/rust-runtime/aws-hyper/src/lib.rs +113 −64 Original line number Diff line number Diff line Loading @@ -3,54 +3,41 @@ * SPDX-License-Identifier: Apache-2.0. */ #[doc(inline)] pub use smithy_client::test_connection; pub mod conn; mod retry; #[cfg(feature = "test-util")] pub mod test_connection; pub use smithy_client::retry::Config as RetryConfig; pub use retry::RetryConfig; use crate::conn::Standard; use crate::retry::RetryHandlerFactory; use aws_endpoint::AwsEndpointStage; use aws_http::user_agent::UserAgentStage; use aws_sig_auth::middleware::SigV4SigningStage; use aws_sig_auth::signer::SigV4Signer; use smithy_http::body::SdkBody; use smithy_http::operation::Operation; use smithy_http::response::ParseHttpResponse; pub use smithy_http::result::{SdkError, SdkSuccess}; use smithy_http::retry::ClassifyResponse; use smithy_http_tower::dispatch::DispatchLayer; use smithy_http_tower::map_request::MapRequestLayer; use std::fmt::Debug; use tower::layer::util::Stack; use tower::ServiceBuilder; use smithy_http_tower::parse_response::ParseResponseLayer; use smithy_types::retry::ProvideErrorKind; use std::error::Error; use std::fmt; use std::fmt::{Debug, Formatter}; use tower::{Service, ServiceBuilder, ServiceExt}; type AwsMiddlewareStack = Stack< MapRequestLayer<SigV4SigningStage>, Stack<MapRequestLayer<UserAgentStage>, MapRequestLayer<AwsEndpointStage>>, >; #[derive(Debug, Default)] #[non_exhaustive] pub struct AwsMiddleware; impl<S> tower::Layer<S> for AwsMiddleware { type Service = <AwsMiddlewareStack as tower::Layer<S>>::Service; fn layer(&self, inner: S) -> Self::Service { let signer = MapRequestLayer::for_mapper(SigV4SigningStage::new(SigV4Signer::new())); let endpoint_resolver = MapRequestLayer::for_mapper(AwsEndpointStage); let user_agent = MapRequestLayer::for_mapper(UserAgentStage::new()); // These layers can be considered as occuring in order, that is: // 1. Resolve an endpoint // 2. Add a user agent // 3. Sign // (4. Dispatch over the wire) ServiceBuilder::new() .layer(endpoint_resolver) .layer(user_agent) .layer(signer) .service(inner) } } type BoxError = Box<dyn Error + Send + Sync>; pub type StandardClient = Client<conn::Standard>; /// AWS Service Client /// /// Hyper-based AWS Service Client. Most customers will want to construct a client with /// [`Client::https`](smithy_client::Client::https). For testing & other more advanced use cases, a /// custom connector may be used via [`Client::new(connector)`](smithy_client::Client::new). /// [`Client::https()`](Client::https). For testing & other more advanced use cases, a custom /// connector may be used via [`Client::new(connector)`](Client::new). /// /// The internal connector must implement the following trait bound to be used to dispatch requests: /// ```rust,ignore Loading @@ -61,54 +48,116 @@ impl<S> tower::Layer<S> for AwsMiddleware { /// S::Error: Into<BoxError> + Send + Sync + 'static, /// S::Future: Send + 'static, /// ``` #[doc(inline)] pub type Client<C> = smithy_client::Client<C, AwsMiddleware>; pub struct Client<S> { inner: S, retry_handler: RetryHandlerFactory, } #[doc(inline)] pub use smithy_client::erase::DynConnector; pub type StandardClient = Client<DynConnector>; impl<S> Debug for Client<S> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut formatter = f.debug_struct("Client"); formatter.field("retry_handler", &self.retry_handler); formatter.finish() } } #[doc(inline)] pub use smithy_client::bounds::SmithyConnector; impl<S> Client<S> { /// Construct a new `Client` with a custom connector pub fn new(connector: S) -> Self { Client { inner: connector, retry_handler: RetryHandlerFactory::new(RetryConfig::default()), } } #[doc(inline)] pub type Builder<C> = smithy_client::Builder<C, AwsMiddleware>; pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self { self.retry_handler.with_config(retry_config); self } } impl Client<Standard> { /// Construct an `https` based client /// /// If the `rustls` feature is enabled, this will use `rustls`. /// If the ONLY the `native-tls` feature is enabled, this will use `native-tls`. /// If both features are enabled, this will use `rustls` #[cfg(any(feature = "native-tls", feature = "rustls"))] pub fn https() -> StandardClient { #[cfg(feature = "rustls")] let with_https = |b: Builder<_>| b.rustls(); // If we are compiling this function & rustls is not enabled, then native-tls MUST be enabled #[cfg(not(feature = "rustls"))] let with_https = |b: Builder<_>| b.native_tls(); Client { inner: Standard::https(), retry_handler: RetryHandlerFactory::new(RetryConfig::default()), } } } with_https(smithy_client::Builder::new()) .build() .into_dyn_connector() impl<S> Client<S> where S: Service<http::Request<SdkBody>, Response = http::Response<SdkBody>> + Send + Clone + 'static, S::Error: Into<BoxError> + Send + Sync + 'static, S::Future: Send + 'static, { /// Dispatch this request to the network /// /// For ergonomics, this does not include the raw response for successful responses. To /// access the raw response use `call_raw`. pub async fn call<O, T, E, Retry>(&self, input: Operation<O, Retry>) -> Result<T, SdkError<E>> where O: ParseHttpResponse<SdkBody, Output = Result<T, E>> + Send + Sync + Clone + 'static, E: Error + ProvideErrorKind, Retry: ClassifyResponse<SdkSuccess<T>, SdkError<E>>, { self.call_raw(input).await.map(|res| res.parsed) } mod static_tests { /// Dispatch this request to the network /// /// The returned result contains the raw HTTP response which can be useful for debugging or implementing /// unsupported features. pub async fn call_raw<O, R, E, Retry>( &self, input: Operation<O, Retry>, ) -> Result<SdkSuccess<R>, SdkError<E>> where O: ParseHttpResponse<SdkBody, Output = Result<R, E>> + Send + Sync + Clone + 'static, E: Error + ProvideErrorKind, Retry: ClassifyResponse<SdkSuccess<R>, SdkError<E>>, { let signer = MapRequestLayer::for_mapper(SigV4SigningStage::new(SigV4Signer::new())); let endpoint_resolver = MapRequestLayer::for_mapper(AwsEndpointStage); let user_agent = MapRequestLayer::for_mapper(UserAgentStage::new()); let inner = self.inner.clone(); let mut svc = ServiceBuilder::new() // Create a new request-scoped policy .retry(self.retry_handler.new_handler()) .layer(ParseResponseLayer::<O, Retry>::new()) // These layers can be considered as occuring in order, that is: // 1. Resolve an endpoint // 2. Add a user agent // 3. Sign // 4. Dispatch over the wire .layer(endpoint_resolver) .layer(user_agent) .layer(signer) .layer(DispatchLayer::new()) .service(inner); svc.ready().await?.call(input).await } } #[cfg(test)] mod tests { #[cfg(any(feature = "rustls", feature = "native-tls"))] #[allow(dead_code)] #[test] fn construct_default_client() { let c = crate::Client::https(); fn is_send_sync<T: Send + Sync>(_c: T) {} is_send_sync(c); } } #[cfg(test)] mod tests { #[cfg(any(feature = "rustls", feature = "native-tls"))] #[test] fn client_debug_includes_retry_info() { let client = crate::Client::https(); let s = format!("{:?}", client); assert!(s.contains("RetryConfig")); assert!(s.contains("quota_available")); } } Loading
aws/rust-runtime/aws-endpoint/src/lib.rs +1 −1 Original line number Diff line number Diff line Loading @@ -161,7 +161,7 @@ pub fn set_endpoint_resolver(config: &mut PropertyBag, provider: AwsEndpointReso /// 3. Apply the endpoint to the URI in the request /// 4. Set the `SigningRegion` and `SigningService` in the property bag to drive downstream /// signing middleware. #[derive(Clone, Debug)] #[derive(Clone)] pub struct AwsEndpointStage; #[derive(Debug)] Loading
aws/rust-runtime/aws-http/src/user_agent.rs +1 −1 Original line number Diff line number Diff line Loading @@ -216,7 +216,7 @@ impl Display for ExecEnvMetadata { } #[non_exhaustive] #[derive(Default, Clone, Debug)] #[derive(Default, Clone)] pub struct UserAgentStage; impl UserAgentStage { Loading
aws/rust-runtime/aws-hyper/Cargo.toml +2 −3 Original line number Diff line number Diff line Loading @@ -10,8 +10,8 @@ license = "Apache-2.0" [features] test-util = ["protocol-test-helpers"] default = ["test-util"] native-tls = ["hyper-tls", "smithy-client/native-tls"] rustls = ["hyper-rustls", "smithy-client/rustls"] native-tls = ["hyper-tls"] rustls = ["hyper-rustls"] [dependencies] hyper = { version = "0.14.2", features = ["client", "http1", "http2", "tcp", "runtime"] } Loading @@ -28,7 +28,6 @@ http-body = "0.4.0" smithy-http = { path = "../../../rust-runtime/smithy-http" } smithy-types = { path = "../../../rust-runtime/smithy-types" } smithy-http-tower = { path = "../../../rust-runtime/smithy-http-tower" } smithy-client = { path = "../../../rust-runtime/smithy-client" } fastrand = "1.4.0" tokio = { version = "1", features = ["time"] } Loading
aws/rust-runtime/aws-hyper/src/conn.rs 0 → 100644 +203 −0 Original line number Diff line number Diff line /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ use crate::BoxError; use http::Request; use hyper::client::ResponseFuture; use hyper::Response; use smithy_http::body::SdkBody; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use tower::Service; #[derive(Clone)] pub struct Standard(Connector); impl Standard { /// An https connection /// /// If the `rustls` feature is enabled, this will use `rustls`. /// If the ONLY the `native-tls` feature is enabled, this will use `native-tls`. /// If both features are enabled, this will use `rustls` #[cfg(any(feature = "native-tls", feature = "rustls"))] pub fn https() -> Self { #[cfg(feature = "rustls")] { Self::rustls() } // If we are compiling this function & rustls is not enabled, then native-tls MUST be enabled #[cfg(not(feature = "rustls"))] { Self::native_tls() } } #[cfg(feature = "rustls")] pub fn rustls() -> Self { let https = hyper_rustls::HttpsConnector::with_native_roots(); let client = hyper::Client::builder().build::<_, SdkBody>(https); Self(Connector::RustlsHttps(client)) } #[cfg(feature = "native-tls")] pub fn native_tls() -> Self { let https = hyper_tls::HttpsConnector::new(); let client = hyper::Client::builder().build::<_, SdkBody>(https); Self(Connector::NativeHttps(client)) } /// A connection based on the provided `impl HttpService` /// /// Generally, [`Standard::https()`](Standard::https) should be used. This constructor is intended to support /// using things like [`TestConnection`](crate::test_connection::TestConnection) or alternative /// http implementations. pub fn new(connector: impl HttpService + 'static) -> Self { Self(Connector::Dyn(Box::new(connector))) } } /// An Http connection type for most use cases /// /// This supports three options: /// 1. HTTPS /// 2. A `TestConnection` /// 3. Any implementation of the `HttpService` trait /// /// This is designed to be used with [`aws_hyper::Client`](crate::Client) as a connector. #[derive(Clone)] enum Connector { /// An Https Connection /// /// This is the correct connection for use cases talking to real AWS services. #[cfg(feature = "native-tls")] NativeHttps(hyper::Client<hyper_tls::HttpsConnector<hyper::client::HttpConnector>, SdkBody>), /// An Https Connection /// /// This is the correct connection for use cases talking to real AWS services. #[cfg(feature = "rustls")] RustlsHttps(hyper::Client<hyper_rustls::HttpsConnector<hyper::client::HttpConnector>, SdkBody>), /// A generic escape hatch /// /// This enables using any implementation of the HttpService trait. This allows using a totally /// separate HTTP stack or your own custom `TestConnection`. Dyn(Box<dyn HttpService>), } impl Clone for Box<dyn HttpService> { fn clone(&self) -> Self { self.clone_box() } } pub trait HttpService: Send + Sync { /// Return whether this service is ready to accept a request /// /// See [`Service::poll_ready`](tower::Service::poll_ready) fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), BoxError>>; /// Call this service and return a response /// /// See [`Service::call`](tower::Service::call) fn call( &mut self, req: http::Request<SdkBody>, ) -> Pin<Box<dyn Future<Output = Result<http::Response<SdkBody>, BoxError>> + Send>>; /// Return a Boxed-clone of this service /// /// `aws_hyper::Client` will clone the inner service for each request so this should be a cheap /// clone operation. fn clone_box(&self) -> Box<dyn HttpService>; } /// Reverse implementation: If you have a correctly shaped tower service, it _is_ an `HttpService` /// /// This is to facilitate ease of use for people using `Standard::Dyn` impl<S> HttpService for S where S: Service<http::Request<SdkBody>, Response = http::Response<SdkBody>> + Send + Sync + Clone + 'static, S::Error: Into<BoxError> + Send + Sync + 'static, S::Future: Send + 'static, { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), BoxError>> { Service::poll_ready(self, cx).map_err(|err| err.into()) } fn call( &mut self, req: Request<SdkBody>, ) -> Pin<Box<dyn Future<Output = Result<Response<SdkBody>, BoxError>> + Send>> { let fut = Service::call(self, req); let fut = async move { fut.await .map(|res| res.map(SdkBody::from)) .map_err(|err| err.into()) }; Box::pin(fut) } fn clone_box(&self) -> Box<dyn HttpService> { Box::new(self.clone()) } } impl tower::Service<http::Request<SdkBody>> for Standard { type Response = http::Response<SdkBody>; type Error = BoxError; type Future = StandardFuture; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { match &mut self.0 { #[cfg(feature = "native-tls")] Connector::NativeHttps(https) => { Service::poll_ready(https, cx).map_err(|err| err.into()) } #[cfg(feature = "rustls")] Connector::RustlsHttps(https) => { Service::poll_ready(https, cx).map_err(|err| err.into()) } Connector::Dyn(conn) => conn.poll_ready(cx), } } fn call(&mut self, req: http::Request<SdkBody>) -> Self::Future { match &mut self.0 { #[cfg(feature = "native-tls")] Connector::NativeHttps(https) => StandardFuture::Https(Service::call(https, req)), #[cfg(feature = "rustls")] Connector::RustlsHttps(https) => StandardFuture::Https(Service::call(https, req)), Connector::Dyn(conn) => StandardFuture::Dyn(conn.call(req)), } } } /// Future returned by `Standard` when used as a tower::Service #[pin_project::pin_project(project = FutProj)] pub enum StandardFuture { Https(#[pin] ResponseFuture), Dyn(#[pin] Pin<Box<dyn Future<Output = Result<http::Response<SdkBody>, BoxError>> + Send>>), } impl Future for StandardFuture { type Output = Result<http::Response<SdkBody>, BoxError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { match self.project() { FutProj::Https(fut) => fut .poll(cx) .map(|resp| resp.map(|res| res.map(SdkBody::from))) .map_err(|err| err.into()), FutProj::Dyn(dyn_fut) => dyn_fut.poll(cx), } } }
aws/rust-runtime/aws-hyper/src/lib.rs +113 −64 Original line number Diff line number Diff line Loading @@ -3,54 +3,41 @@ * SPDX-License-Identifier: Apache-2.0. */ #[doc(inline)] pub use smithy_client::test_connection; pub mod conn; mod retry; #[cfg(feature = "test-util")] pub mod test_connection; pub use smithy_client::retry::Config as RetryConfig; pub use retry::RetryConfig; use crate::conn::Standard; use crate::retry::RetryHandlerFactory; use aws_endpoint::AwsEndpointStage; use aws_http::user_agent::UserAgentStage; use aws_sig_auth::middleware::SigV4SigningStage; use aws_sig_auth::signer::SigV4Signer; use smithy_http::body::SdkBody; use smithy_http::operation::Operation; use smithy_http::response::ParseHttpResponse; pub use smithy_http::result::{SdkError, SdkSuccess}; use smithy_http::retry::ClassifyResponse; use smithy_http_tower::dispatch::DispatchLayer; use smithy_http_tower::map_request::MapRequestLayer; use std::fmt::Debug; use tower::layer::util::Stack; use tower::ServiceBuilder; use smithy_http_tower::parse_response::ParseResponseLayer; use smithy_types::retry::ProvideErrorKind; use std::error::Error; use std::fmt; use std::fmt::{Debug, Formatter}; use tower::{Service, ServiceBuilder, ServiceExt}; type AwsMiddlewareStack = Stack< MapRequestLayer<SigV4SigningStage>, Stack<MapRequestLayer<UserAgentStage>, MapRequestLayer<AwsEndpointStage>>, >; #[derive(Debug, Default)] #[non_exhaustive] pub struct AwsMiddleware; impl<S> tower::Layer<S> for AwsMiddleware { type Service = <AwsMiddlewareStack as tower::Layer<S>>::Service; fn layer(&self, inner: S) -> Self::Service { let signer = MapRequestLayer::for_mapper(SigV4SigningStage::new(SigV4Signer::new())); let endpoint_resolver = MapRequestLayer::for_mapper(AwsEndpointStage); let user_agent = MapRequestLayer::for_mapper(UserAgentStage::new()); // These layers can be considered as occuring in order, that is: // 1. Resolve an endpoint // 2. Add a user agent // 3. Sign // (4. Dispatch over the wire) ServiceBuilder::new() .layer(endpoint_resolver) .layer(user_agent) .layer(signer) .service(inner) } } type BoxError = Box<dyn Error + Send + Sync>; pub type StandardClient = Client<conn::Standard>; /// AWS Service Client /// /// Hyper-based AWS Service Client. Most customers will want to construct a client with /// [`Client::https`](smithy_client::Client::https). For testing & other more advanced use cases, a /// custom connector may be used via [`Client::new(connector)`](smithy_client::Client::new). /// [`Client::https()`](Client::https). For testing & other more advanced use cases, a custom /// connector may be used via [`Client::new(connector)`](Client::new). /// /// The internal connector must implement the following trait bound to be used to dispatch requests: /// ```rust,ignore Loading @@ -61,54 +48,116 @@ impl<S> tower::Layer<S> for AwsMiddleware { /// S::Error: Into<BoxError> + Send + Sync + 'static, /// S::Future: Send + 'static, /// ``` #[doc(inline)] pub type Client<C> = smithy_client::Client<C, AwsMiddleware>; pub struct Client<S> { inner: S, retry_handler: RetryHandlerFactory, } #[doc(inline)] pub use smithy_client::erase::DynConnector; pub type StandardClient = Client<DynConnector>; impl<S> Debug for Client<S> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut formatter = f.debug_struct("Client"); formatter.field("retry_handler", &self.retry_handler); formatter.finish() } } #[doc(inline)] pub use smithy_client::bounds::SmithyConnector; impl<S> Client<S> { /// Construct a new `Client` with a custom connector pub fn new(connector: S) -> Self { Client { inner: connector, retry_handler: RetryHandlerFactory::new(RetryConfig::default()), } } #[doc(inline)] pub type Builder<C> = smithy_client::Builder<C, AwsMiddleware>; pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self { self.retry_handler.with_config(retry_config); self } } impl Client<Standard> { /// Construct an `https` based client /// /// If the `rustls` feature is enabled, this will use `rustls`. /// If the ONLY the `native-tls` feature is enabled, this will use `native-tls`. /// If both features are enabled, this will use `rustls` #[cfg(any(feature = "native-tls", feature = "rustls"))] pub fn https() -> StandardClient { #[cfg(feature = "rustls")] let with_https = |b: Builder<_>| b.rustls(); // If we are compiling this function & rustls is not enabled, then native-tls MUST be enabled #[cfg(not(feature = "rustls"))] let with_https = |b: Builder<_>| b.native_tls(); Client { inner: Standard::https(), retry_handler: RetryHandlerFactory::new(RetryConfig::default()), } } } with_https(smithy_client::Builder::new()) .build() .into_dyn_connector() impl<S> Client<S> where S: Service<http::Request<SdkBody>, Response = http::Response<SdkBody>> + Send + Clone + 'static, S::Error: Into<BoxError> + Send + Sync + 'static, S::Future: Send + 'static, { /// Dispatch this request to the network /// /// For ergonomics, this does not include the raw response for successful responses. To /// access the raw response use `call_raw`. pub async fn call<O, T, E, Retry>(&self, input: Operation<O, Retry>) -> Result<T, SdkError<E>> where O: ParseHttpResponse<SdkBody, Output = Result<T, E>> + Send + Sync + Clone + 'static, E: Error + ProvideErrorKind, Retry: ClassifyResponse<SdkSuccess<T>, SdkError<E>>, { self.call_raw(input).await.map(|res| res.parsed) } mod static_tests { /// Dispatch this request to the network /// /// The returned result contains the raw HTTP response which can be useful for debugging or implementing /// unsupported features. pub async fn call_raw<O, R, E, Retry>( &self, input: Operation<O, Retry>, ) -> Result<SdkSuccess<R>, SdkError<E>> where O: ParseHttpResponse<SdkBody, Output = Result<R, E>> + Send + Sync + Clone + 'static, E: Error + ProvideErrorKind, Retry: ClassifyResponse<SdkSuccess<R>, SdkError<E>>, { let signer = MapRequestLayer::for_mapper(SigV4SigningStage::new(SigV4Signer::new())); let endpoint_resolver = MapRequestLayer::for_mapper(AwsEndpointStage); let user_agent = MapRequestLayer::for_mapper(UserAgentStage::new()); let inner = self.inner.clone(); let mut svc = ServiceBuilder::new() // Create a new request-scoped policy .retry(self.retry_handler.new_handler()) .layer(ParseResponseLayer::<O, Retry>::new()) // These layers can be considered as occuring in order, that is: // 1. Resolve an endpoint // 2. Add a user agent // 3. Sign // 4. Dispatch over the wire .layer(endpoint_resolver) .layer(user_agent) .layer(signer) .layer(DispatchLayer::new()) .service(inner); svc.ready().await?.call(input).await } } #[cfg(test)] mod tests { #[cfg(any(feature = "rustls", feature = "native-tls"))] #[allow(dead_code)] #[test] fn construct_default_client() { let c = crate::Client::https(); fn is_send_sync<T: Send + Sync>(_c: T) {} is_send_sync(c); } } #[cfg(test)] mod tests { #[cfg(any(feature = "rustls", feature = "native-tls"))] #[test] fn client_debug_includes_retry_info() { let client = crate::Client::https(); let s = format!("{:?}", client); assert!(s.contains("RetryConfig")); assert!(s.contains("quota_available")); } }