Move examples to root, refactor to workspace, and refactor integration tests (#2481)

* Move examples

* Update documentation

* Add to CI

* Fix CI

* Cleanup

* Fix clippy lints

* Fix documentation

* Bump example dependencies

* Cleanup

* Update documentation

---------

Co-authored-by: Matteo Bigoi <1781140+crisidev@users.noreply.github.com>
This commit is contained in:
Harry Barber 2023-03-22 14:00:21 +00:00 committed by GitHub
parent f3e44742e3
commit a737694f73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 674 additions and 555 deletions

View File

@ -38,6 +38,7 @@ Project Layout
* [`design`](design): Design documentation. See the [design/README.md](design/README.md) for details about building / viewing.
* `codegen-server`: Whitelabel Smithy server code generation
* `codegen-server-test`: Smithy protocol test generation & integration tests for Smithy server whitelabel code
* `examples`: A collection of server implementation examples
Testing
-------

View File

@ -648,7 +648,7 @@ stateDiagram-v2
Op1 --> Op2 : Plugin#colon;#colon;map
```
An example `Plugin` implementation can be found in [aws-smithy-http-server/examples/pokemon-service/src/plugin.rs](https://github.com/awslabs/smithy-rs/blob/main/rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/plugin.rs).
An example `Plugin` implementation can be found in [/examples/pokemon-service/src/plugin.rs](https://github.com/awslabs/smithy-rs/blob/main/examples/pokemon-service/src/plugin.rs).
The service builder API requires plugins to be specified upfront - they must be passed as an argument to `builder_with_plugins` and cannot be modified afterwards.
This constraint is in place to ensure that all handlers are upgraded using the same set of plugins.

View File

@ -69,7 +69,7 @@ let app = PokemonService::builder_with_plugins(plugins)
### Example
The Pokémon service example, located at `rust-runtime/aws-smithy-http-server/examples/pokemon-service`, sets up a `tracing` `Subscriber` as follows:
The Pokémon service example, located at `/examples/pokemon-service`, sets up a `tracing` `Subscriber` as follows:
```rust
/// Setup `tracing::subscriber` to read the log level from RUST_LOG environment variable.

View File

@ -1,2 +1,3 @@
pokemon-service-client/
pokemon-service-server-sdk/
Cargo.lock

View File

@ -5,11 +5,12 @@ using [wrk](https://github.com/wg/wrk).
<!-- vim-markdown-toc Marked -->
* [2022-03-04](#2022-03-04)
* [c6i.8xlarge](#c6i.8xlarge)
* [Full result](#full-result)
* [c6g.8xlarge](#c6g.8xlarge)
* [Full result](#full-result)
- [Smithy Rust Server SDK benchmarks](#smithy-rust-server-sdk-benchmarks)
- [2022-03-04](#2022-03-04)
- [c6i.8xlarge](#c6i8xlarge)
- [Full result](#full-result)
- [c6g.8xlarge](#c6g8xlarge)
- [Full result](#full-result-1)
<!-- vim-markdown-toc -->
@ -20,19 +21,19 @@ returning an empty output and can be used to stress test the framework overhead.
### c6i.8xlarge
* 32 cores Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz
* 64 Gb memory
* Benchmark:
- Duration: 10 minutes
- Connections: 1024
- Threads: 16
* Result:
- Request/sec: 1_608_742
* RSS[^1] memory: 72200 bytes
- 32 cores Intel(R) Xeon(R) Platinum 8375C CPU @ 2.90GHz
- 64 Gb memory
- Benchmark:
- Duration: 10 minutes
- Connections: 1024
- Threads: 16
- Result:
- Request/sec: 1_608_742
- RSS[^1] memory: 72200 bytes
#### Full result
```
```text
wrk -t16 -c1024 -d10m --latency http://localhost:13734/empty-operation
Running 10m test @ http://localhost:13734/empty-operation
16 threads and 1024 connections
@ -52,20 +53,20 @@ Transfer/sec: 167.23MB
### c6g.8xlarge
* 32 cores Amazon Graviton 2 @ 2.50GHz
* 64 Gb memory
* Benchmark:
- Duration: 10 minutes
- Connections: 1024
- Threads: 16
* Result:
- Request/sec: 1_379_942
- RSS[^1] memory: 70264 bytes
- 32 cores Amazon Graviton 2 @ 2.50GHz
- 64 Gb memory
- Benchmark:
- Duration: 10 minutes
- Connections: 1024
- Threads: 16
- Result:
- Request/sec: 1_379_942
- RSS[^1] memory: 70264 bytes
#### Full result
```
```text
wrk -t16 -c1024 -d10m --latency http://localhost:13734/empty-operation
Running 10m test @ http://localhost:13734/empty-operation
16 threads and 1024 connections

View File

@ -1,9 +1,12 @@
# Without this configuration, the workspace will be read from `rust-runtime`, causing the build to fail.
[workspace]
members = [
"pokemon-service-common",
"pokemon-service",
"pokemon-service-tls",
"pokemon-service-lambda",
"pokemon-service-server-sdk",
"pokemon-service-client",
"pokemon-service-client"
]
[profile.release]

49
examples/README.md Normal file
View File

@ -0,0 +1,49 @@
# Smithy Rust Server SDK examples
This folder contains an example services showcasing the service framework capabilities and to run benchmarks.
- `/pokemon-service`, a HTTP server implementation demonstrating [middleware](https://awslabs.github.io/smithy-rs/design/server/middleware.html)
and [extractors](https://awslabs.github.io/smithy-rs/design/server/from_parts.html).
- `/pokemon-service-tls`, a minimal HTTPS server implementation.
- `/pokemon-service-lambda`, a minimal Lambda deployment.
The `/{binary}/tests` folders are integration tests involving the generated clients.
## Build
Since this example requires both the server and client SDK to be code-generated
from their [model](/codegen-server-test/model/pokemon.smithy), a Makefile is
provided to build and run the service. Just run `make` to prepare the first
build.
Once the example has been built successfully the first time, idiomatic `cargo`
can be used directly.
`make distclean` can be used for a complete cleanup of all artefacts.
## Run
To run a binary use
```bash
cargo run -p $BINARY
```
CLI arguments can be passed to the servers, use
```bash
cargo run -p $BINARY -- --help
```
for more information.
## Test
`cargo test` can be used to spawn a service and run some simple integration
tests against it. Use `-p $BINARY` to filter by package.
More info can be found in the `tests` folder of each package.
## Benchmarks
Please see [BENCHMARKS.md](/examples/BENCHMARKS.md).

View File

@ -0,0 +1,24 @@
[package]
name = "pokemon-service-common"
version = "0.1.0"
edition = "2021"
publish = false
authors = ["Smithy-rs Server Team <smithy-rs-server@amazon.com>"]
description = "A smithy Rust service to retrieve information about Pokémon."
[dependencies]
async-stream = "0.3"
http = "0.2.9"
rand = "0.8"
tracing = "0.1"
tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] }
tokio = { version = "1", default-features = false, features = ["time"] }
# Local paths
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]
tower = "0.4"

View File

@ -10,17 +10,19 @@
use std::{
collections::HashMap,
convert::TryInto,
process::Child,
sync::{atomic::AtomicUsize, Arc},
};
use async_stream::stream;
use aws_smithy_http::operation::Request;
use aws_smithy_http_server::Extension;
use pokemon_service_server_sdk::{error, input, model, model::CapturingPayload, output, types::Blob};
use pokemon_service_server_sdk::{
error, input, model, model::CapturingPayload, output, types::Blob,
};
use rand::Rng;
use tracing_subscriber::{prelude::*, EnvFilter};
pub mod plugin;
const PIKACHU_ENGLISH_FLAVOR_TEXT: &str =
"When several of these Pokémon gather, their electricity could build and cause lightning storms.";
const PIKACHU_SPANISH_FLAVOR_TEXT: &str =
@ -30,13 +32,37 @@ const PIKACHU_ITALIAN_FLAVOR_TEXT: &str =
const PIKACHU_JAPANESE_FLAVOR_TEXT: &str =
"ほっぺたの りょうがわに ちいさい でんきぶくろを もつ。ピンチのときに ほうでんする。";
/// Rewrites the base URL of a request
pub fn rewrite_base_url(base_url: String) -> impl Fn(Request) -> Request + Clone {
move |mut req| {
let http_req = req.http_mut();
let uri = format!("{base_url}{}", http_req.uri().path());
*http_req.uri_mut() = uri.parse().unwrap();
req
}
}
/// Kills [`Child`] process when dropped.
#[derive(Debug)]
#[must_use]
pub struct ChildDrop(pub Child);
impl Drop for ChildDrop {
fn drop(&mut self) {
self.0.kill().expect("failed to kill process")
}
}
/// Setup `tracing::subscriber` to read the log level from RUST_LOG environment variable.
pub fn setup_tracing() {
let format = tracing_subscriber::fmt::layer().json();
let filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry().with(format).with(filter).init();
tracing_subscriber::registry()
.with(format)
.with(filter)
.init();
}
/// Structure holding the translations for a Pokémon description.
@ -134,7 +160,10 @@ pub async fn get_pokemon_species(
input: input::GetPokemonSpeciesInput,
state: Extension<Arc<State>>,
) -> Result<output::GetPokemonSpeciesOutput, error::GetPokemonSpeciesError> {
state.0.call_count.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
state
.0
.call_count
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
// We only support retrieving information about Pikachu.
let pokemon = state.0.pokemons_translations.get(&input.name);
match pokemon.as_ref() {
@ -215,7 +244,9 @@ pub async fn capture_pokemon(
) -> Result<output::CapturePokemonOutput, error::CapturePokemonError> {
if input.region != "Kanto" {
return Err(error::CapturePokemonError::UnsupportedRegionError(
error::UnsupportedRegionError { region: input.region },
error::UnsupportedRegionError {
region: input.region,
},
));
}
let output_stream = stream! {
@ -307,7 +338,10 @@ mod tests {
.find(|flavor_text| flavor_text.language == model::Language::Spanish)
.unwrap();
assert_eq!(PIKACHU_SPANISH_FLAVOR_TEXT, actual_spanish_flavor_text.flavor_text());
assert_eq!(
PIKACHU_SPANISH_FLAVOR_TEXT,
actual_spanish_flavor_text.flavor_text()
);
let input = input::GetServerStatisticsInput {};
let stats = get_server_statistics(input, Extension(state.clone())).await;

View File

@ -2,20 +2,25 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use std::{
ops::Deref,
sync::Arc,
sync::Mutex,
task::{Context, Poll},
};
use aws_smithy_http::body::SdkBody;
use aws_smithy_http_server::operation::Operation;
use aws_smithy_http_server::plugin::{Plugin, PluginPipeline};
use hyper::http;
use pokemon_service::do_nothing;
use pokemon_service_client::operation::do_nothing::DoNothingInput;
use pokemon_service_client::Config;
use std::ops::Deref;
use std::sync::Arc;
use std::sync::Mutex;
use std::task::{Context, Poll};
use aws_smithy_http_server::{
operation::Operation,
plugin::{Plugin, PluginPipeline},
};
use tower::layer::util::Stack;
use tower::{Layer, Service};
use pokemon_service_client::{operation::do_nothing::DoNothingInput, Config};
use pokemon_service_common::do_nothing;
trait OperationExt {
/// Convert an SDK operation into an `http::Request`.
fn into_http(self) -> http::Request<SdkBody>;
@ -59,7 +64,10 @@ struct SentinelPlugin {
impl SentinelPlugin {
pub fn new(name: &'static str, output: Arc<Mutex<Vec<&'static str>>>) -> Self {
Self { name, output: output }
Self {
name,
output: output,
}
}
}

View File

@ -0,0 +1,21 @@
[package]
name = "pokemon-service-lambda"
version = "0.1.0"
edition = "2021"
publish = false
authors = ["Smithy-rs Server Team <smithy-rs-server@amazon.com>"]
description = "A smithy Rust service to retrieve information about Pokémon via Lambda."
[dependencies]
async-stream = "0.3.4"
clap = { version = "4.1.11", features = ["derive"] }
hyper = {version = "0.14.25", features = ["server"] }
tokio = "1.26.0"
tracing = "0.1"
lambda_http = "0.7.3"
# Local paths
aws-smithy-http-server = { path = "../../rust-runtime/aws-smithy-http-server", features = ["aws-lambda"] }
pokemon-service-server-sdk = { path = "../pokemon-service-server-sdk/" }
pokemon-service-common = { path = "../pokemon-service-common/" }

View File

@ -0,0 +1,33 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use std::sync::Arc;
use aws_smithy_http_server::{request::lambda::Context, Extension};
use pokemon_service_common::State;
use pokemon_service_server_sdk::{
error::{GetStorageError, StorageAccessNotAuthorized},
input::GetStorageInput,
output::GetStorageOutput,
};
/// Retrieves the user's storage and logs the lambda request ID.
pub async fn get_storage_lambda(
input: GetStorageInput,
_state: Extension<Arc<State>>,
context: Context,
) -> Result<GetStorageOutput, GetStorageError> {
tracing::debug!(request_id = %context.request_id, "attempting to authenticate storage user");
// We currently only support Ash and he has nothing stored
if !(input.user == "ash" && input.passcode == "pikachu123") {
tracing::debug!("authentication failed");
return Err(GetStorageError::StorageAccessNotAuthorized(
StorageAccessNotAuthorized {},
));
}
Ok(GetStorageOutput { collection: vec![] })
}

View File

@ -0,0 +1,39 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use std::sync::Arc;
use aws_smithy_http_server::{routing::LambdaHandler, AddExtensionLayer};
use pokemon_service_common::{
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, State,
};
use pokemon_service_lambda::get_storage_lambda;
use pokemon_service_server_sdk::PokemonService;
#[tokio::main]
pub async fn main() {
let app = PokemonService::builder_without_plugins()
// Build a registry containing implementations to all the operations in the service. These
// are async functions or async closures that take as input the operation's input and
// return the operation's output.
.get_pokemon_species(get_pokemon_species)
.get_storage(get_storage_lambda)
.get_server_statistics(get_server_statistics)
.capture_pokemon(capture_pokemon)
.do_nothing(do_nothing)
.check_health(check_health)
.build()
.expect("failed to build an instance of PokemonService")
// Set up shared state and middlewares.
.layer(&AddExtensionLayer::new(Arc::new(State::default())));
let handler = LambdaHandler::new(app);
let lambda = lambda_http::run(handler);
if let Err(err) = lambda.await {
eprintln!("lambda error: {}", err);
}
}

View File

@ -0,0 +1,36 @@
[package]
name = "pokemon-service-tls"
version = "0.1.0"
edition = "2021"
publish = false
authors = ["Smithy-rs Server Team <smithy-rs-server@amazon.com>"]
description = "A smithy Rust service to retrieve information about Pokémon."
[dependencies]
clap = { version = "4.1.11", features = ["derive"] }
hyper = { version = "0.14.25", features = ["server"] }
tokio = "1.26.0"
# These dependencies are only required for the `pokemon-service-tls` program.
tls-listener = { version = "0.6.0", features = ["rustls", "hyper-h2"] }
tokio-rustls = "0.23.4"
rustls-pemfile = "1.0.2"
futures-util = { version = "0.3.27", default-features = false }
# Local paths
aws-smithy-http-server = { path = "../../rust-runtime/aws-smithy-http-server" }
pokemon-service-server-sdk = { path = "../pokemon-service-server-sdk/" }
pokemon-service-common = { path = "../pokemon-service-common/" }
[dev-dependencies]
assert_cmd = "2.0"
serial_test = "1.0.0"
# This dependency is only required for testing the `pokemon-service-tls` program.
hyper-rustls = { version = "0.23.2", features = ["http2"] }
# Local paths
aws-smithy-client = { path = "../../rust-runtime/aws-smithy-client/", features = ["rustls"] }
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/" }

View File

@ -0,0 +1,13 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
// Defaults shared between `main.rs` and `/tests`.
pub const DEFAULT_TEST_KEY: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testdata/localhost.key");
pub const DEFAULT_TEST_CERT: &str =
concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testdata/localhost.crt");
pub const DEFAULT_ADDRESS: &str = "127.0.0.1";
pub const DEFAULT_PORT: u16 = 13734;
pub const DEFAULT_DOMAIN: &str = "localhost";

View File

@ -22,39 +22,37 @@
// note that by default created certificates will be unknown and you should use `-k|--insecure`
// flag while making requests with cURL or you can run `mkcert -install` to trust certificates created by `mkcert`.
use std::fs::File;
use std::future;
use std::io::BufReader;
use std::net::SocketAddr;
use std::sync::Arc;
use std::{fs::File, future, io::BufReader, net::SocketAddr, sync::Arc};
use aws_smithy_http_server::{plugin::PluginPipeline, AddExtensionLayer};
use aws_smithy_http_server::AddExtensionLayer;
use clap::Parser;
use futures_util::stream::StreamExt;
use pokemon_service::{
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, get_storage,
plugin::PrintExt, setup_tracing, State,
};
use pokemon_service_server_sdk::PokemonService;
use tokio_rustls::{
rustls::{Certificate, PrivateKey, ServerConfig},
TlsAcceptor,
};
use pokemon_service_common::{
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics,
get_storage, setup_tracing, State,
};
use pokemon_service_server_sdk::PokemonService;
use pokemon_service_tls::{DEFAULT_ADDRESS, DEFAULT_PORT, DEFAULT_TEST_CERT, DEFAULT_TEST_KEY};
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// Hyper server bind address.
#[clap(short, long, action, default_value = "127.0.0.1")]
#[clap(short, long, action, default_value = DEFAULT_ADDRESS)]
address: String,
/// Hyper server bind port.
#[clap(short, long, action, default_value = "13734")]
#[clap(short, long, action, default_value_t = DEFAULT_PORT)]
port: u16,
/// Hyper server TLS certificate path. Must be a PEM file.
#[clap(long, default_value = "")]
#[clap(long, default_value = DEFAULT_TEST_CERT)]
tls_cert_path: String,
/// Hyper server TLS private key path. Must be a PEM file.
#[clap(long, default_value = "")]
#[clap(long, default_value = DEFAULT_TEST_KEY)]
tls_key_path: String,
}
@ -62,9 +60,8 @@ struct Args {
pub async fn main() {
let args = Args::parse();
setup_tracing();
// Apply the `PrintPlugin` defined in `plugin.rs`
let plugins = PluginPipeline::new().print();
let app = PokemonService::builder_with_plugins(plugins)
let app = PokemonService::builder_without_plugins()
// Build a registry containing implementations to all the operations in the service. These
// are async functions or async closures that take as input the operation's input and
// return the operation's output.
@ -96,7 +93,8 @@ pub async fn main() {
future::ready(true)
}
});
let server = hyper::Server::builder(hyper::server::accept::from_stream(listener)).serve(app.into_make_service());
let server = hyper::Server::builder(hyper::server::accept::from_stream(listener))
.serve(app.into_make_service());
if let Err(err) = server.await {
eprintln!("server error: {}", err);
}

View File

@ -0,0 +1,58 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use std::{fs::File, io::BufReader, process::Command, time::Duration};
use assert_cmd::prelude::*;
use aws_smithy_client::{
erase::{DynConnector, DynMiddleware},
hyper_ext::Adapter,
};
use tokio::time::sleep;
use pokemon_service_client::{Builder, Client, Config};
use pokemon_service_common::{rewrite_base_url, ChildDrop};
use pokemon_service_tls::{DEFAULT_DOMAIN, DEFAULT_PORT, DEFAULT_TEST_CERT};
pub async fn run_server() -> ChildDrop {
let child = Command::cargo_bin("pokemon-service-tls")
.unwrap()
.spawn()
.unwrap();
sleep(Duration::from_millis(500)).await;
ChildDrop(child)
}
// Returns a client that only talks through https and http2 connections.
// It is useful in testing whether our server can talk to http2.
pub fn client_http2_only() -> Client<DynConnector, DynMiddleware<DynConnector>> {
// Create custom cert store and add our test certificate to prevent unknown cert issues.
let mut reader =
BufReader::new(File::open(DEFAULT_TEST_CERT).expect("could not open certificate"));
let certs = rustls_pemfile::certs(&mut reader).expect("could not parse certificate");
let mut roots = tokio_rustls::rustls::RootCertStore::empty();
roots.add_parsable_certificates(&certs);
let connector = hyper_rustls::HttpsConnectorBuilder::new()
.with_tls_config(
tokio_rustls::rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth(),
)
.https_only()
.enable_http2()
.build();
let base_url = format!("https://{DEFAULT_DOMAIN}:{DEFAULT_PORT}");
let raw_client = Builder::new()
.connector(DynConnector::new(Adapter::builder().build(connector)))
.middleware_fn(rewrite_base_url(base_url))
.build_dyn();
let config = Config::builder().build();
Client::with_config(raw_client, config)
}

View File

@ -0,0 +1,14 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
pub mod common;
#[tokio::test]
async fn test_check_health_http2() {
let _child = common::run_server().await;
let client = common::client_http2_only();
let _check_health = client.check_health().send().await.unwrap();
}

View File

@ -0,0 +1,34 @@
[package]
name = "pokemon-service"
version = "0.1.0"
edition = "2021"
publish = false
authors = ["Smithy-rs Server Team <smithy-rs-server@amazon.com>"]
description = "A smithy Rust service to retrieve information about Pokémon."
[dependencies]
clap = { version = "4.1.11", features = ["derive"] }
hyper = {version = "0.14.25", features = ["server"] }
tokio = "1.26.0"
tower = "0.4"
tracing = "0.1"
# Local paths
aws-smithy-http-server = { path = "../../rust-runtime/aws-smithy-http-server", features = ["request-id"] }
pokemon-service-server-sdk = { path = "../pokemon-service-server-sdk/" }
pokemon-service-common = { path = "../pokemon-service-common/" }
[dev-dependencies]
assert_cmd = "2.0"
async-stream = "0.3"
rand = "0.8.5"
serial_test = "1.0.0"
# This dependency is only required for testing the `pokemon-service-tls` program.
hyper-rustls = { version = "0.23.2", features = ["http2"] }
# Local paths
aws-smithy-client = { path = "../../rust-runtime/aws-smithy-client/", features = ["rustls"] }
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/" }

View File

@ -0,0 +1,59 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use std::net::{IpAddr, SocketAddr};
use aws_smithy_http_server::request::{connect_info::ConnectInfo, request_id::ServerRequestId};
use pokemon_service_server_sdk::{
error::{GetStorageError, StorageAccessNotAuthorized},
input::{DoNothingInput, GetStorageInput},
output::{DoNothingOutput, GetStorageOutput},
};
// Defaults shared between `main.rs` and `/tests`.
pub const DEFAULT_ADDRESS: &str = "127.0.0.1";
pub const DEFAULT_PORT: u16 = 13734;
/// Logs the request IDs to `DoNothing` operation.
pub async fn do_nothing_but_log_request_ids(
_input: DoNothingInput,
request_id: ServerRequestId,
) -> DoNothingOutput {
tracing::debug!(%request_id, "do nothing");
DoNothingOutput {}
}
/// Retrieves the user's storage. No authentication required for locals.
pub async fn get_storage_with_local_approved(
input: GetStorageInput,
connect_info: ConnectInfo<SocketAddr>,
) -> Result<GetStorageOutput, GetStorageError> {
tracing::debug!("attempting to authenticate storage user");
if !(input.user == "ash" && input.passcode == "pikachu123") {
tracing::debug!("authentication failed");
return Err(GetStorageError::StorageAccessNotAuthorized(
StorageAccessNotAuthorized {},
));
}
// We support trainers in our local gym
let local = connect_info.0.ip() == "127.0.0.1".parse::<IpAddr>().unwrap();
if local {
tracing::info!("welcome back");
return Ok(GetStorageOutput {
collection: vec![
String::from("bulbasaur"),
String::from("charmander"),
String::from("squirtle"),
String::from("pikachu"),
],
});
}
Ok(GetStorageOutput {
collection: vec![String::from("pikachu")],
})
}

View File

@ -3,14 +3,22 @@
* SPDX-License-Identifier: Apache-2.0
*/
// This program is exported as a binary named `pokemon-service`.
mod plugin;
use std::{net::SocketAddr, sync::Arc};
use aws_smithy_http_server::{extension::OperationExtensionExt, plugin::PluginPipeline, AddExtensionLayer};
use aws_smithy_http_server::{
extension::OperationExtensionExt, instrumentation::InstrumentExt, plugin::PluginPipeline,
request::request_id::ServerRequestIdProviderLayer, AddExtensionLayer,
};
use clap::Parser;
use plugin::PrintExt;
use pokemon_service::{
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, get_storage,
plugin::PrintExt, setup_tracing, State,
do_nothing_but_log_request_ids, get_storage_with_local_approved, DEFAULT_ADDRESS, DEFAULT_PORT,
};
use pokemon_service_common::{
capture_pokemon, check_health, get_pokemon_species, get_server_statistics, setup_tracing, State,
};
use pokemon_service_server_sdk::PokemonService;
@ -18,10 +26,10 @@ use pokemon_service_server_sdk::PokemonService;
#[clap(author, version, about, long_about = None)]
struct Args {
/// Hyper server bind address.
#[clap(short, long, action, default_value = "127.0.0.1")]
#[clap(short, long, action, default_value = DEFAULT_ADDRESS)]
address: String,
/// Hyper server bind port.
#[clap(short, long, action, default_value = "13734")]
#[clap(short, long, action, default_value_t = DEFAULT_PORT)]
port: u16,
}
@ -29,33 +37,44 @@ struct Args {
pub async fn main() {
let args = Args::parse();
setup_tracing();
let plugins = PluginPipeline::new()
// Apply the `PrintPlugin` defined in `plugin.rs`
.print()
// Apply the `OperationExtensionPlugin` defined in `aws_smithy_http_server::extension`. This allows other
// plugins or tests to access a `aws_smithy_http_server::extension::OperationExtension` from
// `Response::extensions`, or infer routing failure when it's missing.
.insert_operation_extension();
.insert_operation_extension()
// Adds `tracing` spans and events to the request lifecycle.
.instrument();
let app = PokemonService::builder_with_plugins(plugins)
// Build a registry containing implementations to all the operations in the service. These
// are async functions or async closures that take as input the operation's input and
// return the operation's output.
.get_pokemon_species(get_pokemon_species)
.get_storage(get_storage)
.get_storage(get_storage_with_local_approved)
.get_server_statistics(get_server_statistics)
.capture_pokemon(capture_pokemon)
.do_nothing(do_nothing)
.do_nothing(do_nothing_but_log_request_ids)
.check_health(check_health)
.build()
.expect("failed to build an instance of PokemonService")
// Setup shared state and middlewares.
.layer(&AddExtensionLayer::new(Arc::new(State::default())));
.expect("failed to build an instance of PokemonService");
// Start the [`hyper::Server`].
let app = app
// Setup shared state and middlewares.
.layer(&AddExtensionLayer::new(Arc::new(State::default())))
// Add request IDs
.layer(&ServerRequestIdProviderLayer::new());
// Using `into_make_service_with_connect_info`, rather than `into_make_service`, to adjoin the `SocketAddr`
// connection info.
let make_app = app.into_make_service_with_connect_info::<SocketAddr>();
// Bind the application to a socket.
let bind: SocketAddr = format!("{}:{}", args.address, args.port)
.parse()
.expect("unable to parse the server bind address and port");
let server = hyper::Server::bind(&bind).serve(app.into_make_service());
let server = hyper::Server::bind(&bind).serve(make_app);
// Run forever-ish...
if let Err(err) = server.await {

View File

@ -0,0 +1,35 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use std::{process::Command, time::Duration};
use assert_cmd::prelude::*;
use aws_smithy_client::erase::{DynConnector, DynMiddleware};
use tokio::time::sleep;
use pokemon_service::{DEFAULT_ADDRESS, DEFAULT_PORT};
use pokemon_service_client::{Builder, Client, Config};
use pokemon_service_common::{rewrite_base_url, ChildDrop};
pub async fn run_server() -> ChildDrop {
let child = Command::cargo_bin("pokemon-service")
.unwrap()
.spawn()
.unwrap();
sleep(Duration::from_millis(500)).await;
ChildDrop(child)
}
pub fn client() -> Client<DynConnector, DynMiddleware<DynConnector>> {
let base_url = format!("http://{DEFAULT_ADDRESS}:{DEFAULT_PORT}");
let raw_client = Builder::new()
.rustls_connector(Default::default())
.middleware_fn(rewrite_base_url(base_url))
.build_dyn();
let config = Config::builder().build();
Client::with_config(raw_client, config)
}

View File

@ -3,24 +3,21 @@
* SPDX-License-Identifier: Apache-2.0
*/
// Files here are for running integration tests.
// These tests only have access to your crate's public API.
// See: https://doc.rust-lang.org/book/ch11-03-test-organization.html#integration-tests
use crate::helpers::{client, client_http2_only, PokemonService};
pub mod common;
use async_stream::stream;
use aws_smithy_types::error::display::DisplayErrorContext;
use pokemon_service_client::{
error::SdkError,
operation::get_storage::GetStorageError,
types::error::{AttemptCapturingPokemonEventError, MasterBallUnsuccessful, StorageAccessNotAuthorized},
types::{AttemptCapturingPokemonEvent, CapturingEvent, CapturingPayload},
};
use rand::Rng;
use serial_test::serial;
mod helpers;
use pokemon_service_client::types::{
error::{AttemptCapturingPokemonEventError, MasterBallUnsuccessful},
AttemptCapturingPokemonEvent, CapturingEvent, CapturingPayload,
};
fn get_pokemon_to_capture() -> String {
let pokemons = vec!["Charizard", "Pikachu", "Regieleki"];
pokemons[rand::thread_rng().gen_range(0..pokemons.len())].to_string()
}
fn get_pokeball() -> String {
let random = rand::thread_rng().gen_range(0..100);
@ -36,82 +33,11 @@ fn get_pokeball() -> String {
pokeball.to_string()
}
fn get_pokemon_to_capture() -> String {
let pokemons = vec!["Charizard", "Pikachu", "Regieleki"];
pokemons[rand::thread_rng().gen_range(0..pokemons.len())].to_string()
}
#[tokio::test]
#[serial]
async fn test_check_health() {
let _program = PokemonService::run().await;
let _check_health = client().check_health().send().await.unwrap();
}
#[tokio::test]
#[serial]
async fn test_check_health_http2() {
// Make sure our server can serve http2
let _program = PokemonService::run_https().await;
let _check_health = client_http2_only().check_health().send().await.unwrap();
}
#[tokio::test]
#[serial]
async fn simple_integration_test() {
let _program = PokemonService::run().await;
let service_statistics_out = client().get_server_statistics().send().await.unwrap();
assert_eq!(0, service_statistics_out.calls_count.unwrap());
let pokemon_species_output = client().get_pokemon_species().name("pikachu").send().await.unwrap();
assert_eq!("pikachu", pokemon_species_output.name().unwrap());
let service_statistics_out = client().get_server_statistics().send().await.unwrap();
assert_eq!(1, service_statistics_out.calls_count.unwrap());
let storage_err = client().get_storage().user("ash").passcode("pikachu321").send().await;
let has_not_authorized_error = if let Err(SdkError::ServiceError(context)) = storage_err {
matches!(
context.err(),
GetStorageError::StorageAccessNotAuthorized(StorageAccessNotAuthorized { .. }),
)
} else {
false
};
assert!(has_not_authorized_error, "expected NotAuthorized error");
let storage_out = client()
.get_storage()
.user("ash")
.passcode("pikachu123")
.send()
.await
.unwrap();
assert_eq!(Some(vec![]), storage_out.collection);
let pokemon_species_error = client()
.get_pokemon_species()
.name("some_pokémon")
.send()
.await
.unwrap_err();
let message = DisplayErrorContext(pokemon_species_error).to_string();
let expected = r#"ResourceNotFoundError [ResourceNotFoundException]: Requested Pokémon not available"#;
assert!(
message.contains(expected),
"expected '{message}' to contain '{expected}'"
);
let service_statistics_out = client().get_server_statistics().send().await.unwrap();
assert_eq!(2, service_statistics_out.calls_count.unwrap());
}
#[tokio::test]
#[serial]
async fn event_stream_test() {
let _program = PokemonService::run().await;
let _child = common::run_server().await;
let client = common::client();
let mut team = vec![];
let input_stream = stream! {
@ -145,7 +71,7 @@ async fn event_stream_test() {
};
// Throw many!
let mut output = client()
let mut output = common::client()
.capture_pokemon()
.region("Kanto")
.events(input_stream.into())
@ -156,7 +82,13 @@ async fn event_stream_test() {
match output.events.recv().await {
Ok(Some(capture)) => {
let pokemon = capture.as_event().unwrap().name.as_ref().unwrap().clone();
let pokedex = capture.as_event().unwrap().pokedex_update.as_ref().unwrap().clone();
let pokedex = capture
.as_event()
.unwrap()
.pokedex_update
.as_ref()
.unwrap()
.clone();
let shiny = if *capture.as_event().unwrap().shiny.as_ref().unwrap() {
""
} else {
@ -190,7 +122,7 @@ async fn event_stream_test() {
.build()
))
};
let mut output = client()
let mut output = client
.capture_pokemon()
.region("Kanto")
.events(input_stream.into())
@ -200,7 +132,13 @@ async fn event_stream_test() {
match output.events.recv().await {
Ok(Some(capture)) => {
let pokemon = capture.as_event().unwrap().name.as_ref().unwrap().clone();
let pokedex = capture.as_event().unwrap().pokedex_update.as_ref().unwrap().clone();
let pokedex = capture
.as_event()
.unwrap()
.pokedex_update
.as_ref()
.unwrap()
.clone();
let shiny = if *capture.as_event().unwrap().shiny.as_ref().unwrap() {
""
} else {

View File

@ -0,0 +1,84 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use pokemon_service_client::{
error::{DisplayErrorContext, SdkError},
operation::get_storage::GetStorageError,
types::error::StorageAccessNotAuthorized,
};
use serial_test::serial;
pub mod common;
#[tokio::test]
#[serial]
async fn simple_integration_test() {
let _child = common::run_server().await;
let client = common::client();
let service_statistics_out = client.get_server_statistics().send().await.unwrap();
assert_eq!(0, service_statistics_out.calls_count.unwrap());
let pokemon_species_output = client
.get_pokemon_species()
.name("pikachu")
.send()
.await
.unwrap();
assert_eq!("pikachu", pokemon_species_output.name().unwrap());
let service_statistics_out = client.get_server_statistics().send().await.unwrap();
assert_eq!(1, service_statistics_out.calls_count.unwrap());
let storage_err = client
.get_storage()
.user("ash")
.passcode("pikachu321")
.send()
.await;
let has_not_authorized_error = if let Err(SdkError::ServiceError(context)) = storage_err {
matches!(
context.err(),
GetStorageError::StorageAccessNotAuthorized(StorageAccessNotAuthorized { .. }),
)
} else {
false
};
assert!(has_not_authorized_error, "expected NotAuthorized error");
let storage_out = client
.get_storage()
.user("ash")
.passcode("pikachu123")
.send()
.await
.unwrap();
assert_eq!(
Some(vec![
"bulbasaur".to_string(),
"charmander".to_string(),
"squirtle".to_string(),
"pikachu".to_string()
]),
storage_out.collection
);
let pokemon_species_error = client
.get_pokemon_species()
.name("some_pokémon")
.send()
.await
.unwrap_err();
let message = DisplayErrorContext(pokemon_species_error).to_string();
let expected =
r#"ResourceNotFoundError [ResourceNotFoundException]: Requested Pokémon not available"#;
assert!(
message.contains(expected),
"expected '{message}' to contain '{expected}'"
);
let service_statistics_out = client.get_server_statistics().send().await.unwrap();
assert_eq!(2, service_statistics_out.calls_count.unwrap());
}

View File

@ -1,32 +0,0 @@
# Smithy Rust Server SDK example
This folder contains an example service called Pokémon Service used to showcase
the service framework capabilities and to run benchmarks.
## Build
Since this example requires both the server and client SDK to be code-generated
from their [model](/codegen-server-test/model/pokemon.smithy), a Makefile is
provided to build and run the service. Just run `make` to prepare the first
build.
Once the example has been built successfully the first time, idiomatic `cargo`
can be used directly.
`make distclean` can be used for a complete cleanup of all artefacts.
## Run
`cargo run` can be used to start the Pokémon service on
`http://localhost:13734`.
## Test
`cargo test` can be used to spawn the service and run some simple integration
tests against it.
More info can be found in the `tests` folder of `pokemon-service` package.
## Benchmarks
Please see [BENCHMARKS.md](/rust-runtime/aws-smithy-http-server/examples/BENCHMARKS.md).

View File

@ -1,62 +0,0 @@
[package]
name = "pokemon-service"
version = "0.1.0"
edition = "2021"
publish = false
authors = ["Smithy-rs Server Team <smithy-rs-server@amazon.com>"]
description = "A smithy Rust service to retrieve information about Pokémon."
default-run = "pokemon-service"
[[bin]]
name = "pokemon-service"
path = "src/bin/pokemon-service.rs"
[[bin]]
name = "pokemon-service-tls"
path = "src/bin/pokemon-service-tls.rs"
[[bin]]
name = "pokemon-service-lambda"
path = "src/bin/pokemon-service-lambda.rs"
[[bin]]
name = "pokemon-service-connect-info"
path = "src/bin/pokemon-service-connect-info.rs"
[dependencies]
async-stream = "0.3"
clap = { version = "~3.2.1", features = ["derive"] }
hyper = {version = "0.14.12", features = ["server"] }
rand = "0.8"
tokio = "1.20.1"
tower = "0.4"
tower-http = { version = "0.3", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3.15", features = ["env-filter", "json"] }
# These dependencies are only required for the `pokemon-service-tls` program.
tls-listener = { version = "0.5.1", features = ["rustls", "hyper-h2"] }
tokio-rustls = "0.23.4"
rustls-pemfile = "1.0.1"
futures-util = { version = "0.3.16", default-features = false }
# This dependency is only required for the `pokemon-service-lambda` program.
lambda_http = "0.7.1"
# Local paths
aws-smithy-http-server = { path = "../../", features = ["aws-lambda", "request-id"] }
pokemon-service-server-sdk = { path = "../pokemon-service-server-sdk/" }
[dev-dependencies]
assert_cmd = "2.0"
home = "0.5"
serial_test = "0.7.0"
# This dependency is only required for testing the `pokemon-service-tls` program.
hyper-rustls = { version = "0.23.0", features = ["http2"] }
# Local paths
aws-smithy-client = { path = "../../../aws-smithy-client/", features = ["rustls"] }
aws-smithy-http = { path = "../../../aws-smithy-http/" }
aws-smithy-types = { path = "../../../aws-smithy-types/" }
pokemon-service-client = { path = "../pokemon-service-client/" }

View File

@ -1,95 +0,0 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use std::net::{IpAddr, SocketAddr};
use aws_smithy_http_server::{
request::connect_info::ConnectInfo, request::request_id::ServerRequestId,
request::request_id::ServerRequestIdProviderLayer,
};
use clap::Parser;
use pokemon_service::{capture_pokemon, check_health, get_pokemon_species, get_server_statistics, setup_tracing};
use pokemon_service_server_sdk::{
error::{GetStorageError, StorageAccessNotAuthorized},
input::{DoNothingInput, GetStorageInput},
output::{DoNothingOutput, GetStorageOutput},
PokemonService,
};
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// Hyper server bind address.
#[clap(short, long, action, default_value = "127.0.0.1")]
address: String,
/// Hyper server bind port.
#[clap(short, long, action, default_value = "13734")]
port: u16,
}
/// Retrieves the user's storage. No authentication required for locals.
pub async fn get_storage_with_local_approved(
input: GetStorageInput,
connect_info: ConnectInfo<SocketAddr>,
) -> Result<GetStorageOutput, GetStorageError> {
tracing::debug!("attempting to authenticate storage user");
let local = connect_info.0.ip() == "127.0.0.1".parse::<IpAddr>().unwrap();
// We currently support Ash: he has nothing stored
if input.user == "ash" && input.passcode == "pikachu123" {
return Ok(GetStorageOutput { collection: vec![] });
}
// We support trainers in our gym
if local {
tracing::info!("welcome back");
return Ok(GetStorageOutput {
collection: vec![
String::from("bulbasaur"),
String::from("charmander"),
String::from("squirtle"),
],
});
}
tracing::debug!("authentication failed");
Err(GetStorageError::StorageAccessNotAuthorized(
StorageAccessNotAuthorized {},
))
}
pub async fn do_nothing_but_log_request_ids(
_input: DoNothingInput,
server_request_id: ServerRequestId,
) -> DoNothingOutput {
tracing::debug!("This request has this server ID: {}", server_request_id);
DoNothingOutput {}
}
#[tokio::main]
async fn main() {
let args = Args::parse();
setup_tracing();
let app = PokemonService::builder_without_plugins()
.get_pokemon_species(get_pokemon_species)
.get_storage(get_storage_with_local_approved)
.get_server_statistics(get_server_statistics)
.capture_pokemon(capture_pokemon)
.do_nothing(do_nothing_but_log_request_ids)
.check_health(check_health)
.build()
.expect("failed to build an instance of PokemonService");
let app = app.layer(&ServerRequestIdProviderLayer::new());
// Start the [`hyper::Server`].
let bind: SocketAddr = format!("{}:{}", args.address, args.port)
.parse()
.expect("unable to parse the server bind address and port");
let server = hyper::Server::bind(&bind).serve(app.into_make_service_with_connect_info::<SocketAddr>());
// Run forever-ish...
if let Err(err) = server.await {
eprintln!("server error: {}", err);
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
// This program is exported as a binary named `pokemon-service-lambda`.
use std::sync::Arc;
use aws_smithy_http_server::{
plugin::PluginPipeline, request::lambda::Context, routing::LambdaHandler, AddExtensionLayer, Extension,
};
use pokemon_service::{
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, plugin::PrintExt,
setup_tracing, State,
};
use pokemon_service_server_sdk::{error, input, output, PokemonService};
/// Retrieves the user's storage and records the .
pub async fn get_storage_lambda(
input: input::GetStorageInput,
_state: Extension<Arc<State>>,
context: Context,
) -> Result<output::GetStorageOutput, error::GetStorageError> {
tracing::debug!(request_id = %context.request_id, "attempting to authenticate storage user");
// We currently only support Ash and he has nothing stored
if !(input.user == "ash" && input.passcode == "pikachu123") {
tracing::debug!("authentication failed");
return Err(error::GetStorageError::StorageAccessNotAuthorized(
error::StorageAccessNotAuthorized {},
));
}
Ok(output::GetStorageOutput { collection: vec![] })
}
#[tokio::main]
pub async fn main() {
setup_tracing();
// Apply the `PrintPlugin` defined in `plugin.rs`
let plugins = PluginPipeline::new().print();
let app = PokemonService::builder_with_plugins(plugins)
// Build a registry containing implementations to all the operations in the service. These
// are async functions or async closures that take as input the operation's input and
// return the operation's output.
.get_pokemon_species(get_pokemon_species)
.get_storage(get_storage_lambda)
.get_server_statistics(get_server_statistics)
.capture_pokemon(capture_pokemon)
.do_nothing(do_nothing)
.check_health(check_health)
.build()
.expect("failed to build an instance of PokemonService")
// Set up shared state and middlewares.
.layer(&AddExtensionLayer::new(Arc::new(State::default())));
let handler = LambdaHandler::new(app);
let lambda = lambda_http::run(handler);
if let Err(err) = lambda.await {
eprintln!("lambda error: {}", err);
}
}

View File

@ -1,132 +0,0 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
use std::fs::File;
use std::io::BufReader;
use std::process::{Child, Command};
use std::time::Duration;
use assert_cmd::prelude::*;
use aws_smithy_client::{erase::DynConnector, hyper_ext::Adapter};
use aws_smithy_http::operation::Request;
use pokemon_service_client::{Builder, Client, Config};
use tokio::time;
const TEST_KEY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testdata/localhost.key");
const TEST_CERT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/testdata/localhost.crt");
enum PokemonServiceVariant {
Http,
Https,
}
impl PokemonServiceVariant {
async fn run_process(&self) -> Child {
let process = match self {
PokemonServiceVariant::Http => Command::cargo_bin("pokemon-service").unwrap().spawn().unwrap(),
PokemonServiceVariant::Https => Command::cargo_bin("pokemon-service-tls")
.unwrap()
.args(["--tls-cert-path", TEST_CERT, "--tls-key-path", TEST_KEY])
.spawn()
.unwrap(),
};
// Give PokémonService some time to start up.
time::sleep(Duration::from_millis(500)).await;
process
}
fn base_url(&self) -> String {
match self {
PokemonServiceVariant::Http => "http://localhost:13734".to_string(),
PokemonServiceVariant::Https => "https://localhost:13734".to_string(),
}
}
}
pub(crate) struct PokemonService {
child_process: Child,
}
impl PokemonService {
#[allow(dead_code)]
pub(crate) async fn run() -> Self {
Self {
child_process: PokemonServiceVariant::Http.run_process().await,
}
}
#[allow(dead_code)]
pub(crate) async fn run_https() -> Self {
Self {
child_process: PokemonServiceVariant::Https.run_process().await,
}
}
}
impl Drop for PokemonService {
fn drop(&mut self) {
self.child_process
.kill()
.expect("failed to kill Pokémon Service program")
}
}
#[allow(dead_code)]
pub fn client() -> Client<
aws_smithy_client::erase::DynConnector,
aws_smithy_client::erase::DynMiddleware<aws_smithy_client::erase::DynConnector>,
> {
let base_url = PokemonServiceVariant::Http.base_url();
let raw_client = Builder::new()
.rustls_connector(Default::default())
.middleware_fn(rewrite_base_url(base_url))
.build_dyn();
let config = Config::builder().build();
Client::with_config(raw_client, config)
}
// Returns a client that only talks through https and http2 connections.
// It is useful in testing whether our server can talk to http2.
#[allow(dead_code)]
pub fn client_http2_only() -> Client<
aws_smithy_client::erase::DynConnector,
aws_smithy_client::erase::DynMiddleware<aws_smithy_client::erase::DynConnector>,
> {
// Create custom cert store and add our test certificate to prevent unknown cert issues.
let mut reader = BufReader::new(File::open(TEST_CERT).expect("could not open certificate"));
let certs = rustls_pemfile::certs(&mut reader).expect("could not parse certificate");
let mut roots = tokio_rustls::rustls::RootCertStore::empty();
roots.add_parsable_certificates(&certs);
let connector = hyper_rustls::HttpsConnectorBuilder::new()
.with_tls_config(
tokio_rustls::rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(roots)
.with_no_client_auth(),
)
.https_only()
.enable_http2()
.build();
let base_url = PokemonServiceVariant::Https.base_url();
let raw_client = Builder::new()
.connector(DynConnector::new(Adapter::builder().build(connector)))
.middleware_fn(rewrite_base_url(base_url))
.build_dyn();
let config = Config::builder().build();
Client::with_config(raw_client, config)
}
fn rewrite_base_url(base_url: String) -> impl Fn(Request) -> Request + Clone {
move |mut req| {
let http_req = req.http_mut();
let uri = format!("{base_url}{}", http_req.uri().path());
*http_req.uri_mut() = uri.parse().unwrap();
req
}
}

View File

@ -7,8 +7,8 @@
//! [`IntoMakeServiceWithConnectInfo`](crate::routing::IntoMakeServiceWithConnectInfo) is used. [`ConnectInfo`]'s
//! [`FromParts`] implementation allows it to be extracted from the [`http::Request`].
//!
//! The [`pokemon-service-connect-info.rs`](https://github.com/awslabs/smithy-rs/blob/main/rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/bin/pokemon-service-connect-info.rs)
//! example illustrates the use of [`IntoMakeServiceWithConnectInfo`](crate::routing::IntoMakeServiceWithConnectInfo)
//! The [`example service`](https://github.com/awslabs/smithy-rs/blob/main/examples/pokemon-service/src/main.rs)
//! illustrates the use of [`IntoMakeServiceWithConnectInfo`](crate::routing::IntoMakeServiceWithConnectInfo)
//! and [`ConnectInfo`] with a service builder.
use http::request::Parts;

View File

@ -4,6 +4,6 @@
# SPDX-License-Identifier: Apache-2.0
set -eux
cd smithy-rs/rust-runtime/aws-smithy-http-server/examples
cd smithy-rs/examples
make test clippy