diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt index 7b553ee119..512ccd6912 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt @@ -355,9 +355,9 @@ class ServerServiceGeneratorV2( #{SmithyHttpServer}::routing::IntoMakeService::new(self) } - /// Converts [`$serviceName`] into a [`MakeService`](tower::make::MakeService) with [`ConnectInfo`](#{SmithyHttpServer}::routing::into_make_service_with_connect_info::ConnectInfo). - pub fn into_make_service_with_connect_info(self) -> #{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo { - #{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo::new(self) + /// Converts [`$serviceName`] into a [`MakeService`](tower::make::MakeService) with [`ConnectInfo`](#{SmithyHttpServer}::request::connect_info::ConnectInfo). + pub fn into_make_service_with_connect_info(self) -> #{SmithyHttpServer}::request::connect_info::IntoMakeServiceWithConnectInfo { + #{SmithyHttpServer}::request::connect_info::IntoMakeServiceWithConnectInfo::new(self) } /// Applies a [`Layer`](#{Tower}::Layer) uniformly to all routes. diff --git a/rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/bin/pokemon-service-connect-info.rs b/rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/bin/pokemon-service-connect-info.rs index e7422f7db0..8d17756ff1 100644 --- a/rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/bin/pokemon-service-connect-info.rs +++ b/rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/bin/pokemon-service-connect-info.rs @@ -3,10 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ +use std::net::{IpAddr, SocketAddr}; + +use aws_smithy_http_server::request::connect_info::ConnectInfo; use clap::Parser; use pokemon_service::{ capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, setup_tracing, }; +use pokemon_service_server_sdk::{ + error::{GetStorageError, NotAuthorized}, + input::GetStorageInput, + output::GetStorageOutput, + PokemonService, +}; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] @@ -21,20 +30,20 @@ struct Args { /// Retrieves the user's storage. No authentication required for locals. pub async fn get_storage_with_local_approved( - input: pokemon_service_server_sdk::input::GetStorageInput, - connect_info: aws_smithy_http_server::Extension>, -) -> Result { + input: GetStorageInput, + connect_info: ConnectInfo, +) -> Result { tracing::debug!("attempting to authenticate storage user"); - let local = connect_info.0 .0.ip() == "127.0.0.1".parse::().unwrap(); + let local = connect_info.0.ip() == "127.0.0.1".parse::().unwrap(); // We currently support Ash: he has nothing stored if input.user == "ash" && input.passcode == "pikachu123" { - return Ok(pokemon_service_server_sdk::output::GetStorageOutput { collection: vec![] }); + return Ok(GetStorageOutput { collection: vec![] }); } // We support trainers in our gym if local { tracing::info!("welcome back"); - return Ok(pokemon_service_server_sdk::output::GetStorageOutput { + return Ok(GetStorageOutput { collection: vec![ String::from("bulbasaur"), String::from("charmander"), @@ -43,16 +52,14 @@ pub async fn get_storage_with_local_approved( }); } tracing::debug!("authentication failed"); - Err(pokemon_service_server_sdk::error::GetStorageError::NotAuthorized( - pokemon_service_server_sdk::error::NotAuthorized {}, - )) + Err(GetStorageError::NotAuthorized(NotAuthorized {})) } #[tokio::main] async fn main() { let args = Args::parse(); setup_tracing(); - let app = pokemon_service_server_sdk::service::PokemonService::builder_without_plugins() + 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) diff --git a/rust-runtime/aws-smithy-http-server/src/extension.rs b/rust-runtime/aws-smithy-http-server/src/extension.rs index 220b697c78..3dfc4fc5d5 100644 --- a/rust-runtime/aws-smithy-http-server/src/extension.rs +++ b/rust-runtime/aws-smithy-http-server/src/extension.rs @@ -3,35 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -// This code was copied and then modified from Tokio's Axum. - -/* Copyright (c) 2021 Tower Contributors - * - * Permission is hereby granted, free of charge, to any - * person obtaining a copy of this software and associated - * documentation files (the "Software"), to deal in the - * Software without restriction, including without - * limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software - * is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice - * shall be included in all copies or substantial portions - * of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF - * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED - * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT - * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR - * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - //! Extension types. //! //! Extension types are types that are stored in and extracted from _both_ requests and @@ -50,14 +21,12 @@ use std::ops::Deref; -use http::StatusCode; use thiserror::Error; -use crate::{ - body::{empty, BoxBody}, - request::{FromParts, RequestParts}, - response::IntoResponse, -}; +use crate::request::RequestParts; + +pub use crate::request::extension::Extension; +pub use crate::request::extension::MissingExtension; /// Extension type used to store information about Smithy operations in HTTP responses. /// This extension type is set when it has been correctly determined that the request should be @@ -151,49 +120,6 @@ impl Deref for RuntimeErrorExtension { } } -/// Generic extension type stored in and extracted from [request extensions]. -/// -/// This is commonly used to share state across handlers. -/// -/// If the extension is missing it will reject the request with a `500 Internal -/// Server Error` response. -/// -/// [request extensions]: https://docs.rs/http/latest/http/struct.Extensions.html -#[derive(Debug, Clone)] -pub struct Extension(pub T); - -impl Deref for Extension { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// The extension has not been added to the [`Request`](http::Request) or has been previously removed. -#[derive(Debug, Error)] -#[error("the `Extension` is not present in the `http::Request`")] -pub struct MissingExtension; - -impl IntoResponse for MissingExtension { - fn into_response(self) -> http::Response { - let mut response = http::Response::new(empty()); - *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; - response - } -} - -impl FromParts for Extension -where - T: Send + Sync + 'static, -{ - type Rejection = MissingExtension; - - fn from_parts(parts: &mut http::request::Parts) -> Result { - parts.extensions.remove::().map(Extension).ok_or(MissingExtension) - } -} - /// Extract an [`Extension`] from a request. /// This is essentially the implementation of `FromRequest` for `Extension`, but with a /// protocol-agnostic rejection type. The actual code-generated implementation simply delegates to diff --git a/rust-runtime/aws-smithy-http-server/src/lib.rs b/rust-runtime/aws-smithy-http-server/src/lib.rs index 18ae005d08..64f512a905 100644 --- a/rust-runtime/aws-smithy-http-server/src/lib.rs +++ b/rust-runtime/aws-smithy-http-server/src/lib.rs @@ -36,7 +36,7 @@ pub mod routers; #[doc(inline)] pub(crate) use self::error::Error; -pub use self::extension::Extension; +pub use self::request::extension::Extension; #[doc(inline)] pub use self::routing::Router; #[doc(inline)] diff --git a/rust-runtime/aws-smithy-http-server/src/routing/into_make_service_with_connect_info.rs b/rust-runtime/aws-smithy-http-server/src/request/connect_info.rs similarity index 84% rename from rust-runtime/aws-smithy-http-server/src/routing/into_make_service_with_connect_info.rs rename to rust-runtime/aws-smithy-http-server/src/request/connect_info.rs index 8bd75238ef..fd66475fc9 100644 --- a/rust-runtime/aws-smithy-http-server/src/routing/into_make_service_with_connect_info.rs +++ b/rust-runtime/aws-smithy-http-server/src/request/connect_info.rs @@ -32,6 +32,8 @@ * DEALINGS IN THE SOFTWARE. */ +//! Extractor for getting connection information from a client. + use std::{ convert::Infallible, fmt, @@ -48,12 +50,11 @@ use tower_http::add_extension::{AddExtension, AddExtensionLayer}; use crate::{request::FromParts, Extension}; -/// A [`MakeService`] created from a router. +/// A [`MakeService`] used to insert [`ConnectInfo`] into [`http::Request`]s. /// -/// See [`Router::into_make_service_with_connect_info`] for more details. +/// The `T` must be derivable from the underlying IO resource using the [`Connected`] trait. /// /// [`MakeService`]: tower::make::MakeService -/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info pub struct IntoMakeServiceWithConnectInfo { inner: S, _connect_info: PhantomData C>, @@ -96,10 +97,6 @@ where /// /// The goal for this trait is to allow users to implement custom IO types that /// can still provide the same connection metadata. -/// -/// See [`Router::into_make_service_with_connect_info`] for more details. -/// -/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info pub trait Connected: Clone { /// Create type holding information about the connection. fn connect_info(target: T) -> Self; @@ -140,13 +137,9 @@ opaque_future! { /// Extractor for getting connection information produced by a `Connected`. /// -/// Note this extractor requires you to use -/// [`Router::into_make_service_with_connect_info`] to run your app -/// otherwise it will fail at runtime. -/// -/// See [`Router::into_make_service_with_connect_info`] for more details. -/// -/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info +/// Note this extractor requires the existence of [`Extension>`] in the [`http::Extensions`]. This is +/// automatically inserted by the [`IntoMakeServiceWithConnectInfo`] middleware, which can be applied using the +/// `into_make_service_with_connect_info` method on your generated service. #[derive(Clone, Debug)] pub struct ConnectInfo(pub T); diff --git a/rust-runtime/aws-smithy-http-server/src/request/extension.rs b/rust-runtime/aws-smithy-http-server/src/request/extension.rs new file mode 100644 index 0000000000..7aaf23ff2c --- /dev/null +++ b/rust-runtime/aws-smithy-http-server/src/request/extension.rs @@ -0,0 +1,103 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// This code was copied and then modified from Tokio's Axum. + +/* Copyright (c) 2021 Tower Contributors + * + * Permission is hereby granted, free of charge, to any + * person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the + * Software without restriction, including without + * limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software + * is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice + * shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF + * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT + * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +//! Extension types. +//! +//! Extension types are types that are stored in and extracted from _both_ requests and +//! responses. +//! +//! There is only one _generic_ extension type _for requests_, [`Extension`]. +//! +//! On the other hand, the server SDK uses multiple concrete extension types for responses in order +//! to store a variety of information, like the operation that was executed, the operation error +//! that got returned, or the runtime error that happened, among others. The information stored in +//! these types may be useful to [`tower::Layer`]s that post-process the response: for instance, a +//! particular metrics layer implementation might want to emit metrics about the number of times an +//! an operation got executed. +//! +//! [extensions]: https://docs.rs/http/latest/http/struct.Extensions.html + +use std::ops::Deref; + +use http::StatusCode; +use thiserror::Error; + +use crate::{ + body::{empty, BoxBody}, + request::FromParts, + response::IntoResponse, +}; + +/// Generic extension type stored in and extracted from [request extensions]. +/// +/// This is commonly used to share state across handlers. +/// +/// If the extension is missing it will reject the request with a `500 Internal +/// Server Error` response. +/// +/// [request extensions]: https://docs.rs/http/latest/http/struct.Extensions.html +#[derive(Debug, Clone)] +pub struct Extension(pub T); + +impl Deref for Extension { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// The extension has not been added to the [`Request`](http::Request) or has been previously removed. +#[derive(Debug, Error)] +#[error("the `Extension` is not present in the `http::Request`")] +pub struct MissingExtension; + +impl IntoResponse for MissingExtension { + fn into_response(self) -> http::Response { + let mut response = http::Response::new(empty()); + *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + response + } +} + +impl FromParts for Extension +where + T: Send + Sync + 'static, +{ + type Rejection = MissingExtension; + + fn from_parts(parts: &mut http::request::Parts) -> Result { + parts.extensions.remove::().map(Extension).ok_or(MissingExtension) + } +} diff --git a/rust-runtime/aws-smithy-http-server/src/request.rs b/rust-runtime/aws-smithy-http-server/src/request/mod.rs similarity index 96% rename from rust-runtime/aws-smithy-http-server/src/request.rs rename to rust-runtime/aws-smithy-http-server/src/request/mod.rs index 9faf1ea58d..145116fa96 100644 --- a/rust-runtime/aws-smithy-http-server/src/request.rs +++ b/rust-runtime/aws-smithy-http-server/src/request/mod.rs @@ -32,6 +32,11 @@ * DEALINGS IN THE SOFTWARE. */ +//! Types and traits for extracting data from requests. +//! +//! See [Accessing Un-modelled data](https://github.com/awslabs/smithy-rs/blob/main/design/src/server/from_parts.md) +//! a comprehensive overview. + use std::{ convert::Infallible, future::{ready, Future, Ready}, @@ -45,6 +50,9 @@ use http::{request::Parts, Extensions, HeaderMap, Request, Uri}; use crate::{rejection::any_rejections, response::IntoResponse}; +pub mod connect_info; +pub mod extension; + #[doc(hidden)] #[derive(Debug)] pub struct RequestParts { diff --git a/rust-runtime/aws-smithy-http-server/src/routing/mod.rs b/rust-runtime/aws-smithy-http-server/src/routing/mod.rs index 1c84d794cf..1cd3dd21e7 100644 --- a/rust-runtime/aws-smithy-http-server/src/routing/mod.rs +++ b/rust-runtime/aws-smithy-http-server/src/routing/mod.rs @@ -29,7 +29,6 @@ use tower_http::map_response_body::MapResponseBodyLayer; mod future; mod into_make_service; -mod into_make_service_with_connect_info; mod lambda_handler; #[doc(hidden)] @@ -40,10 +39,7 @@ mod route; pub(crate) mod tiny_map; pub use self::lambda_handler::LambdaHandler; -pub use self::{ - future::RouterFuture, into_make_service::IntoMakeService, into_make_service_with_connect_info::ConnectInfo, - into_make_service_with_connect_info::IntoMakeServiceWithConnectInfo, route::Route, -}; +pub use self::{future::RouterFuture, into_make_service::IntoMakeService, route::Route}; /// The router is a [`tower::Service`] that routes incoming requests to other `Service`s /// based on the request's URI and HTTP method or on some specific header setting the target operation. @@ -120,18 +116,6 @@ where IntoMakeService::new(self) } - /// Convert this router into a [`MakeService`], that is a [`Service`] whose - /// response is another service, and provides a [`ConnectInfo`] object to service handlers. - /// - /// This is useful when running your application with hyper's - /// [`Server`]. - /// - /// [`Server`]: hyper::server::Server - /// [`MakeService`]: tower::make::MakeService - pub fn into_make_service_with_connect_info(self) -> IntoMakeServiceWithConnectInfo { - IntoMakeServiceWithConnectInfo::new(self) - } - /// Apply a [`tower::Layer`] to the router. /// /// All requests to the router will be processed by the layer's