-
Notifications
You must be signed in to change notification settings - Fork 195
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Client examples that use the generic client have been added (#2799)
## Motivation and Context Example code that demonstrates the usage of pokemon-service-client. ## Description Examples have been added that show how to add middleware, configure retries, timeouts, and handle errors when calling operations on the pokemon-service. _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: Fahad Zubair <[email protected]>
- Loading branch information
Showing
17 changed files
with
1,427 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
[package] | ||
name = "pokemon-service-client-usage" | ||
version = "0.1.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
[features] | ||
|
||
|
||
[dependencies] | ||
# The generated client utilizes types defined in other crates, such as `aws_smithy_types` | ||
# and `aws_smithy_http`. However, most of these types are re-exported by the generated client, | ||
# eliminating the need to directly depend on the crates that provide them. In rare instances, | ||
# you may still need to include one of these crates as a dependency. Examples that require this | ||
# are specifically noted in comments above the corresponding dependency in this file. | ||
pokemon-service-client = { path = "../pokemon-service-client/" } | ||
|
||
# Required for getting the operation name from the `Metadata`. | ||
aws-smithy-http = { path = "../../rust-runtime/aws-smithy-http/" } | ||
|
||
# Required for `Storable` and `StoreReplace` in `response-header-interceptor` example. | ||
aws-smithy-types = { path = "../../rust-runtime/aws-smithy-types/" } | ||
|
||
# Required for `HyperClientBuilder` in `client-connector` example. | ||
aws-smithy-runtime = { path = "../../rust-runtime/aws-smithy-runtime/", features=["test-util"] } | ||
|
||
|
||
|
||
hyper = { version = "0.14.25", features = ["client", "full"] } | ||
tokio = {version = "1.26.0", features=["full"]} | ||
tracing = "0.1.37" | ||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } | ||
rustls = "0.21.7" | ||
hyper-rustls = "0.24.1" | ||
http = "0.2.9" | ||
uuid = {version="1.4.1", features = ["v4"]} | ||
thiserror = "1.0.49" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# smithy-rs Client Examples | ||
|
||
This package contains some examples on how to use the Smithy Client to communicate | ||
with a Smithy-based service. | ||
|
||
## Pre-requisites | ||
|
||
1. Build the `pokemon-service-client` and `pokemon-service` by invoking `make` in the | ||
[examples](https://github.com/awslabs/smithy-rs/tree/main/examples) folder. | ||
|
||
```console | ||
make | ||
``` | ||
|
||
2. Run the Pokemon service locally by issuing the following command from the | ||
[examples](https://github.com/awslabs/smithy-rs/tree/main/examples) folder. This | ||
will launch the Smithy-Rs based service on TCP port 13734. | ||
|
||
```console | ||
cargo run --bin pokemon-service | ||
``` | ||
|
||
## Running the examples | ||
|
||
You can view a list of examples by running `cargo run --example` from the | ||
[pokemon-service-client-usage](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage) | ||
folder. To run an example, pass its name to the `cargo run --example` command, e.g.: | ||
|
||
```console | ||
cargo run --example simple-client | ||
``` | ||
|
||
## List of examples | ||
|
||
| Rust Example | Description | | ||
|--------------------------------|-------------------------------------------------------------------------| | ||
| simple-client | Creates a Smithy Client and calls an operation on it. | | ||
| endpoint-resolver | How to set a custom endpoint resolver. | | ||
| handling-errors | How to send an input parameter to an operation, and to handle errors. | | ||
| custom-header | How to add headers to a request. | | ||
| custom-header-using-interceptor| How to access operation name being called in an interceptor. | | ||
| response-header-interceptor | How to get operation name and access response before it is deserialized.| | ||
| use-config-bag | How to use the property bag to pass data across interceptors. | | ||
| retries-customize | Customize retry settings. | | ||
| retries-disable | How to disable retries. | | ||
| timeout-config | How to configure timeouts. | | ||
| mock-request | Use a custom HttpConnector / Client to generate mock responses. | | ||
| trace-serialize | Trace request and response as they are serialized / deserialized. | | ||
| client-connector | Shows how to change TLS related configuration. | |
74 changes: 74 additions & 0 deletions
74
examples/pokemon-service-client-usage/examples/client-connector.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
/// This example demonstrates how to set connector settings. For example, how to set | ||
/// trusted root certificates to use for HTTPs communication. | ||
/// | ||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734. | ||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md) | ||
/// file for instructions on how to launch the service locally. | ||
/// | ||
/// The example can be run using `cargo run --example client-connector`. | ||
/// | ||
use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder; | ||
use hyper_rustls::ConfigBuilderExt; | ||
use pokemon_service_client::Client as PokemonClient; | ||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL}; | ||
|
||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon | ||
/// service on TCP port 13734. | ||
/// | ||
/// # Examples | ||
/// | ||
/// Basic usage: | ||
/// ``` | ||
/// let client = create_client(); | ||
/// ``` | ||
fn create_client() -> PokemonClient { | ||
let tls_config = rustls::ClientConfig::builder() | ||
.with_safe_defaults() | ||
// `with_native_roots()`: Load platform trusted root certificates. | ||
// `with_webpki_roots()`: Load Mozilla’s set of trusted roots. | ||
.with_native_roots() | ||
// To use client side certificates, you can use | ||
// `.with_client_auth_cert(client_cert, client_key)` instead of `.with_no_client_auth()` | ||
.with_no_client_auth(); | ||
|
||
let tls_connector = hyper_rustls::HttpsConnectorBuilder::new() | ||
.with_tls_config(tls_config) | ||
// This can be changed to `.https_only()` to ensure that the client always uses HTTPs | ||
.https_or_http() | ||
.enable_http1() | ||
.enable_http2() | ||
.build(); | ||
|
||
// Create a hyper-based HTTP client that uses this TLS connector. | ||
let http_client = HyperClientBuilder::new().build(tls_connector); | ||
|
||
// Pass the smithy connector to the Client::ConfigBuilder | ||
let config = pokemon_service_client::Config::builder() | ||
.endpoint_url(POKEMON_SERVICE_URL) | ||
.http_client(http_client) | ||
.build(); | ||
|
||
// Instantiate a client by applying the configuration. | ||
pokemon_service_client::Client::from_conf(config) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
setup_tracing_subscriber(); | ||
|
||
// Create a configured `smithy-rs` client. | ||
let client = create_client(); | ||
|
||
// Call an operation `get_server_statistics` on the Pokémon service. | ||
let response = client | ||
.get_server_statistics() | ||
.send() | ||
.await | ||
.expect("operation failed"); | ||
|
||
tracing::info!(?response, "Response from service") | ||
} |
158 changes: 158 additions & 0 deletions
158
examples/pokemon-service-client-usage/examples/custom-header-using-interceptor.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
/// In this example, a custom header `x-amzn-client-ttl-seconds` is set for all outgoing requests. | ||
/// It serves as a demonstration of how an operation name can be retrieved and utilized within | ||
/// the interceptor. | ||
/// | ||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734. | ||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md) | ||
/// file for instructions on how to launch the service locally. | ||
/// | ||
/// The example can be run using `cargo run --example custom-header-using-interceptor`. | ||
/// | ||
use std::{collections::HashMap, time::Duration}; | ||
|
||
use pokemon_service_client::config::{ConfigBag, Intercept}; | ||
use pokemon_service_client::Client as PokemonClient; | ||
use pokemon_service_client::{ | ||
config::{interceptors::BeforeTransmitInterceptorContextMut, RuntimeComponents}, | ||
error::BoxError, | ||
}; | ||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL}; | ||
|
||
// The `TtlHeaderInterceptor` keeps a map of operation specific value to send | ||
// in the header for each Request. | ||
#[derive(Debug)] | ||
pub struct TtlHeaderInterceptor { | ||
/// Default time-to-live for an operation. | ||
default_ttl: hyper::http::HeaderValue, | ||
/// Operation specific time-to-live. | ||
operation_ttl: HashMap<&'static str, hyper::http::HeaderValue>, | ||
} | ||
|
||
// Helper function to format duration as fractional seconds. | ||
fn format_ttl_value(ttl: Duration) -> String { | ||
format!("{:.2}", ttl.as_secs_f64()) | ||
} | ||
|
||
impl TtlHeaderInterceptor { | ||
fn new(default_ttl: Duration) -> Self { | ||
let duration_str = format_ttl_value(default_ttl); | ||
let default_ttl_value = hyper::http::HeaderValue::from_str(duration_str.as_str()) | ||
.expect("could not create a header value for the default ttl"); | ||
|
||
Self { | ||
default_ttl: default_ttl_value, | ||
operation_ttl: Default::default(), | ||
} | ||
} | ||
|
||
/// Adds an operation name specific timeout value that needs to be set in the header. | ||
fn add_operation_ttl(&mut self, operation_name: &'static str, ttl: Duration) { | ||
let duration_str = format_ttl_value(ttl); | ||
|
||
self.operation_ttl.insert( | ||
operation_name, | ||
hyper::http::HeaderValue::from_str(duration_str.as_str()) | ||
.expect("cannot create header value for the given ttl duration"), | ||
); | ||
} | ||
} | ||
|
||
/// Appends the header `x-amzn-client-ttl-seconds` using either the default time-to-live value | ||
/// or an operation-specific value if it was set earlier using `add_operation_ttl`. | ||
//impl aws_smithy_runtime_api::client::interceptors::Interceptor for TtlHeaderInterceptor { | ||
impl Intercept for TtlHeaderInterceptor { | ||
fn name(&self) -> &'static str { | ||
"TtlHeaderInterceptor" | ||
} | ||
|
||
/// Before the request is signed, add the header to the outgoing request. | ||
fn modify_before_signing( | ||
&self, | ||
context: &mut BeforeTransmitInterceptorContextMut<'_>, | ||
_runtime_components: &RuntimeComponents, | ||
cfg: &mut ConfigBag, | ||
) -> Result<(), BoxError> { | ||
// Metadata in the ConfigBag has the operation name. | ||
let metadata = cfg | ||
.load::<aws_smithy_http::operation::Metadata>() | ||
.expect("metadata should exist"); | ||
let operation_name = metadata.name(); | ||
|
||
// Get operation specific or default HeaderValue to set for the header key. | ||
let ttl = self | ||
.operation_ttl | ||
.get(operation_name) | ||
.unwrap_or(&self.default_ttl); | ||
|
||
context | ||
.request_mut() | ||
.headers_mut() | ||
.insert("x-amzn-client-ttl-seconds", ttl.clone()); | ||
|
||
tracing::info!("{operation_name} header set to {ttl:?}"); | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon service on TCP port 13734. | ||
/// | ||
/// # Examples | ||
/// | ||
/// Basic usage: | ||
/// | ||
/// ``` | ||
/// let client = create_client(); | ||
/// ``` | ||
fn create_client() -> PokemonClient { | ||
// By default set the value of all operations to 6 seconds. | ||
const DEFAULT_TTL: Duration = Duration::from_secs(6); | ||
|
||
// Set up the interceptor to add an operation specific value of 3.5 seconds to be added | ||
// for GetStorage operation. | ||
let mut ttl_headers_interceptor = TtlHeaderInterceptor::new(DEFAULT_TTL); | ||
ttl_headers_interceptor.add_operation_ttl("GetStorage", Duration::from_millis(3500)); | ||
|
||
// The generated client has a type `Config::Builder` that can be used to build a `Config`, which | ||
// allows configuring endpoint-resolver, timeouts, retries etc. | ||
let config = pokemon_service_client::Config::builder() | ||
.endpoint_url(POKEMON_SERVICE_URL) | ||
.interceptor(ttl_headers_interceptor) | ||
.build(); | ||
|
||
pokemon_service_client::Client::from_conf(config) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
setup_tracing_subscriber(); | ||
|
||
// Create a configured `smithy-rs` client. | ||
let client = create_client(); | ||
|
||
// Call an operation `get_server_statistics` on the Pokémon service. | ||
let response = client | ||
.get_server_statistics() | ||
.send() | ||
.await | ||
.expect("operation failed"); | ||
|
||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response for get_server_statistics()"); | ||
|
||
// Call the operation `get_storage` on the Pokémon service. The `TtlHeaderInterceptor` | ||
// interceptor will add a specific header name / value pair for this operation. | ||
let response = client | ||
.get_storage() | ||
.user("ash") | ||
.passcode("pikachu123") | ||
.send() | ||
.await | ||
.expect("operation failed"); | ||
|
||
// Print the response received from the service. | ||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received"); | ||
} |
62 changes: 62 additions & 0 deletions
62
examples/pokemon-service-client-usage/examples/custom-header.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
/// This example demonstrates how to create a `smithy-rs` client, and call an operation with custom | ||
/// headers in the request. | ||
/// | ||
/// The example assumes that the Pokémon service is running on the localhost on TCP port 13734. | ||
/// Refer to the [README.md](https://github.com/awslabs/smithy-rs/tree/main/examples/pokemon-service-client-usage/README.md) | ||
/// file for instructions on how to launch the service locally. | ||
/// | ||
/// The example can be run using `cargo run --example custom-header` | ||
/// | ||
use pokemon_service_client::Client as PokemonClient; | ||
use pokemon_service_client_usage::{setup_tracing_subscriber, POKEMON_SERVICE_URL}; | ||
|
||
/// Creates a new `smithy-rs` client that is configured to communicate with a locally running Pokémon | ||
/// service on TCP port 13734. | ||
/// | ||
/// # Examples | ||
/// | ||
/// Basic usage: | ||
/// | ||
/// ``` | ||
/// let client = create_client(); | ||
/// ``` | ||
fn create_client() -> PokemonClient { | ||
// The generated client has a type `Config::Builder` that can be used to build a `Config`, which | ||
// allows configuring endpoint-resolver, timeouts, retries etc. | ||
let config = pokemon_service_client::Config::builder() | ||
.endpoint_url(POKEMON_SERVICE_URL) | ||
.build(); | ||
|
||
// Apply the configuration on the client, and return that. | ||
pokemon_service_client::Client::from_conf(config) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
setup_tracing_subscriber(); | ||
|
||
// Create a configured `smithy-rs` client. | ||
let client = create_client(); | ||
|
||
// Call an operation `get_server_statistics` on the Pokémon service. | ||
let response = client | ||
.get_server_statistics() | ||
.customize() | ||
.mutate_request(|req| { | ||
// For demonstration purposes, add a header `x-ttl-seconds` to the outgoing request. | ||
let headers = req.headers_mut(); | ||
headers.insert( | ||
hyper::header::HeaderName::from_static("x-ttl-seconds"), | ||
hyper::header::HeaderValue::from(30), | ||
); | ||
}) | ||
.send() | ||
.await | ||
.expect("operation failed"); | ||
|
||
tracing::info!(%POKEMON_SERVICE_URL, ?response, "Response received"); | ||
} |
Oops, something went wrong.