Skip to content

Commit a438b7c

Browse files
authored
Service with ConnectInfo (#1955)
* Service with ConnectInfo Signed-off-by: Daniele Ahmed <ahmeddan@amazon.de>
1 parent 15f2fbb commit a438b7c

File tree

6 files changed

+264
-2
lines changed

6 files changed

+264
-2
lines changed

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ class ServerServiceGeneratorV2(
308308
#{SmithyHttpServer}::routing::IntoMakeService::new(self)
309309
}
310310
311+
/// Converts [`$serviceName`] into a [`MakeService`](tower::make::MakeService) with [`ConnectInfo`](#{SmithyHttpServer}::routing::into_make_service_with_connect_info::ConnectInfo).
312+
pub fn into_make_service_with_connect_info<C>(self) -> #{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo<Self, C> {
313+
#{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo::new(self)
314+
}
315+
311316
/// Applies a [`Layer`](#{Tower}::Layer) uniformly to all routes.
312317
pub fn layer<L>(self, layer: &L) -> $serviceName<L::Service>
313318
where

rust-runtime/aws-smithy-http-server/examples/pokemon-service/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ path = "src/bin/pokemon-service-tls.rs"
1919
name = "pokemon-service-lambda"
2020
path = "src/bin/pokemon-service-lambda.rs"
2121

22+
[[bin]]
23+
name = "pokemon-service-connect-info"
24+
path = "src/bin/pokemon-service-connect-info.rs"
25+
2226
[dependencies]
2327
async-stream = "0.3"
2428
clap = { version = "~3.2.1", features = ["derive"] }
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
use clap::Parser;
7+
use pokemon_service::{
8+
capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, setup_tracing,
9+
};
10+
11+
#[derive(Parser, Debug)]
12+
#[clap(author, version, about, long_about = None)]
13+
struct Args {
14+
/// Hyper server bind address.
15+
#[clap(short, long, action, default_value = "127.0.0.1")]
16+
address: String,
17+
/// Hyper server bind port.
18+
#[clap(short, long, action, default_value = "13734")]
19+
port: u16,
20+
}
21+
22+
/// Retrieves the user's storage. No authentication required for locals.
23+
pub async fn get_storage_with_local_approved(
24+
input: pokemon_service_server_sdk::input::GetStorageInput,
25+
connect_info: aws_smithy_http_server::Extension<aws_smithy_http_server::routing::ConnectInfo<std::net::SocketAddr>>,
26+
) -> Result<pokemon_service_server_sdk::output::GetStorageOutput, pokemon_service_server_sdk::error::GetStorageError> {
27+
tracing::debug!("attempting to authenticate storage user");
28+
let local = connect_info.0 .0.ip() == "127.0.0.1".parse::<std::net::IpAddr>().unwrap();
29+
30+
// We currently support Ash: he has nothing stored
31+
if input.user == "ash" && input.passcode == "pikachu123" {
32+
return Ok(pokemon_service_server_sdk::output::GetStorageOutput { collection: vec![] });
33+
}
34+
// We support trainers in our gym
35+
if local {
36+
tracing::info!("welcome back");
37+
return Ok(pokemon_service_server_sdk::output::GetStorageOutput {
38+
collection: vec![
39+
String::from("bulbasaur"),
40+
String::from("charmander"),
41+
String::from("squirtle"),
42+
],
43+
});
44+
}
45+
tracing::debug!("authentication failed");
46+
Err(pokemon_service_server_sdk::error::GetStorageError::NotAuthorized(
47+
pokemon_service_server_sdk::error::NotAuthorized {},
48+
))
49+
}
50+
51+
#[tokio::main]
52+
async fn main() {
53+
let args = Args::parse();
54+
setup_tracing();
55+
let app = pokemon_service_server_sdk::service::PokemonService::builder()
56+
.get_pokemon_species(get_pokemon_species)
57+
.get_storage(get_storage_with_local_approved)
58+
.get_server_statistics(get_server_statistics)
59+
.capture_pokemon(capture_pokemon)
60+
.do_nothing(do_nothing)
61+
.check_health(check_health)
62+
.build();
63+
64+
// Start the [`hyper::Server`].
65+
let bind: std::net::SocketAddr = format!("{}:{}", args.address, args.port)
66+
.parse()
67+
.expect("unable to parse the server bind address and port");
68+
let server = hyper::Server::bind(&bind).serve(app.into_make_service_with_connect_info::<std::net::SocketAddr>());
69+
70+
// Run forever-ish...
71+
if let Err(err) = server.await {
72+
eprintln!("server error: {}", err);
73+
}
74+
}

rust-runtime/aws-smithy-http-server/examples/pokemon-service/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ pub async fn get_pokemon_species(
176176
}
177177
}
178178

179-
/// Retrieves the users storage.
179+
/// Retrieves the user's storage.
180180
pub async fn get_storage(
181181
input: input::GetStorageInput,
182182
_state: Extension<Arc<State>>,
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
// This code was copied and then modified from Tokio's Axum.
7+
8+
/* Copyright (c) 2021 Tower Contributors
9+
*
10+
* Permission is hereby granted, free of charge, to any
11+
* person obtaining a copy of this software and associated
12+
* documentation files (the "Software"), to deal in the
13+
* Software without restriction, including without
14+
* limitation the rights to use, copy, modify, merge,
15+
* publish, distribute, sublicense, and/or sell copies of
16+
* the Software, and to permit persons to whom the Software
17+
* is furnished to do so, subject to the following
18+
* conditions:
19+
*
20+
* The above copyright notice and this permission notice
21+
* shall be included in all copies or substantial portions
22+
* of the Software.
23+
*
24+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
25+
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
26+
* TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
27+
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
28+
* SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
29+
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
31+
* IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32+
* DEALINGS IN THE SOFTWARE.
33+
*/
34+
35+
use std::{
36+
convert::Infallible,
37+
fmt,
38+
future::ready,
39+
marker::PhantomData,
40+
net::SocketAddr,
41+
task::{Context, Poll},
42+
};
43+
44+
use http::request::Parts;
45+
use hyper::server::conn::AddrStream;
46+
use tower::{Layer, Service};
47+
use tower_http::add_extension::{AddExtension, AddExtensionLayer};
48+
49+
use crate::{request::FromParts, Extension};
50+
51+
/// A [`MakeService`] created from a router.
52+
///
53+
/// See [`Router::into_make_service_with_connect_info`] for more details.
54+
///
55+
/// [`MakeService`]: tower::make::MakeService
56+
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
57+
pub struct IntoMakeServiceWithConnectInfo<S, C> {
58+
inner: S,
59+
_connect_info: PhantomData<fn() -> C>,
60+
}
61+
62+
impl<S, C> IntoMakeServiceWithConnectInfo<S, C> {
63+
pub fn new(svc: S) -> Self {
64+
Self {
65+
inner: svc,
66+
_connect_info: PhantomData,
67+
}
68+
}
69+
}
70+
71+
impl<S, C> fmt::Debug for IntoMakeServiceWithConnectInfo<S, C>
72+
where
73+
S: fmt::Debug,
74+
{
75+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76+
f.debug_struct("IntoMakeServiceWithConnectInfo")
77+
.field("inner", &self.inner)
78+
.finish()
79+
}
80+
}
81+
82+
impl<S, C> Clone for IntoMakeServiceWithConnectInfo<S, C>
83+
where
84+
S: Clone,
85+
{
86+
fn clone(&self) -> Self {
87+
Self {
88+
inner: self.inner.clone(),
89+
_connect_info: PhantomData,
90+
}
91+
}
92+
}
93+
94+
/// Trait that connected IO resources implement and use to produce information
95+
/// about the connection.
96+
///
97+
/// The goal for this trait is to allow users to implement custom IO types that
98+
/// can still provide the same connection metadata.
99+
///
100+
/// See [`Router::into_make_service_with_connect_info`] for more details.
101+
///
102+
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
103+
pub trait Connected<T>: Clone {
104+
/// Create type holding information about the connection.
105+
fn connect_info(target: T) -> Self;
106+
}
107+
108+
impl Connected<&AddrStream> for SocketAddr {
109+
fn connect_info(target: &AddrStream) -> Self {
110+
target.remote_addr()
111+
}
112+
}
113+
114+
impl<S, C, T> Service<T> for IntoMakeServiceWithConnectInfo<S, C>
115+
where
116+
S: Clone,
117+
C: Connected<T>,
118+
{
119+
type Response = AddExtension<S, ConnectInfo<C>>;
120+
type Error = Infallible;
121+
type Future = ResponseFuture<S, C>;
122+
123+
#[inline]
124+
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
125+
Poll::Ready(Ok(()))
126+
}
127+
128+
fn call(&mut self, target: T) -> Self::Future {
129+
let connect_info = ConnectInfo(C::connect_info(target));
130+
let svc = AddExtensionLayer::new(connect_info).layer(self.inner.clone());
131+
ResponseFuture::new(ready(Ok(svc)))
132+
}
133+
}
134+
135+
opaque_future! {
136+
/// Response future for [`IntoMakeServiceWithConnectInfo`].
137+
pub type ResponseFuture<S, C> =
138+
std::future::Ready<Result<AddExtension<S, ConnectInfo<C>>, Infallible>>;
139+
}
140+
141+
/// Extractor for getting connection information produced by a `Connected`.
142+
///
143+
/// Note this extractor requires you to use
144+
/// [`Router::into_make_service_with_connect_info`] to run your app
145+
/// otherwise it will fail at runtime.
146+
///
147+
/// See [`Router::into_make_service_with_connect_info`] for more details.
148+
///
149+
/// [`Router::into_make_service_with_connect_info`]: crate::routing::Router::into_make_service_with_connect_info
150+
#[derive(Clone, Debug)]
151+
pub struct ConnectInfo<T>(pub T);
152+
153+
impl<P, T> FromParts<P> for ConnectInfo<T>
154+
where
155+
T: Send + Sync + 'static,
156+
{
157+
type Rejection = <Extension<Self> as FromParts<P>>::Rejection;
158+
159+
fn from_parts(parts: &mut Parts) -> Result<Self, Self::Rejection> {
160+
let Extension(connect_info) = <Extension<Self> as FromParts<P>>::from_parts(parts)?;
161+
Ok(connect_info)
162+
}
163+
}

rust-runtime/aws-smithy-http-server/src/routing/mod.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ use tower_http::map_response_body::MapResponseBodyLayer;
2929

3030
mod future;
3131
mod into_make_service;
32+
mod into_make_service_with_connect_info;
3233
mod lambda_handler;
3334

3435
#[doc(hidden)]
@@ -39,7 +40,10 @@ mod route;
3940
pub(crate) mod tiny_map;
4041

4142
pub use self::lambda_handler::LambdaHandler;
42-
pub use self::{future::RouterFuture, into_make_service::IntoMakeService, route::Route};
43+
pub use self::{
44+
future::RouterFuture, into_make_service::IntoMakeService, into_make_service_with_connect_info::ConnectInfo,
45+
into_make_service_with_connect_info::IntoMakeServiceWithConnectInfo, route::Route,
46+
};
4347

4448
/// The router is a [`tower::Service`] that routes incoming requests to other `Service`s
4549
/// based on the request's URI and HTTP method or on some specific header setting the target operation.
@@ -116,6 +120,18 @@ where
116120
IntoMakeService::new(self)
117121
}
118122

123+
/// Convert this router into a [`MakeService`], that is a [`Service`] whose
124+
/// response is another service, and provides a [`ConnectInfo`] object to service handlers.
125+
///
126+
/// This is useful when running your application with hyper's
127+
/// [`Server`].
128+
///
129+
/// [`Server`]: hyper::server::Server
130+
/// [`MakeService`]: tower::make::MakeService
131+
pub fn into_make_service_with_connect_info<C>(self) -> IntoMakeServiceWithConnectInfo<Self, C> {
132+
IntoMakeServiceWithConnectInfo::new(self)
133+
}
134+
119135
/// Apply a [`tower::Layer`] to the router.
120136
///
121137
/// All requests to the router will be processed by the layer's

0 commit comments

Comments
 (0)