mirror of https://github.com/smithy-lang/smithy-rs
Request IDs (#2054)
* Request IDs Signed-off-by: Daniele Ahmed <ahmeddan@amazon.de>
This commit is contained in:
parent
42f9ad9887
commit
87e45f6016
|
@ -1,7 +1,7 @@
|
|||
RFC: RequestID in business logic handlers
|
||||
=============
|
||||
|
||||
> Status: RFC
|
||||
> Status: Implemented
|
||||
>
|
||||
> Applies to: server
|
||||
|
||||
|
@ -85,7 +85,7 @@ where
|
|||
}
|
||||
|
||||
fn call(&mut self, mut req: http::Request<R>) -> Self::Future {
|
||||
request.extensions_mut().insert(ServerRequestId::new());
|
||||
req.extensions_mut().insert(ServerRequestId::new());
|
||||
self.inner.call(req)
|
||||
}
|
||||
}
|
||||
|
@ -131,7 +131,12 @@ Although the generated ID is opaque, this will give guarantees to customers as t
|
|||
Changes checklist
|
||||
-----------------
|
||||
|
||||
- [ ] Implement `ServerRequestId`: a `new()` function that generates a UUID, with `Display`, `Debug` and `ToStr` implementations
|
||||
- [x] Implement `ServerRequestId`: a `new()` function that generates a UUID, with `Display`, `Debug` and `ToStr` implementations
|
||||
- [ ] Implement `ClientRequestId`: `new()` that wraps a string (the header value) and the header in which the value could be found, with `Display`, `Debug` and `ToStr` implementations
|
||||
- [x] Implement `FromParts` for `Extension<ServerRequestId>`
|
||||
- [x] Implement `FromParts` for `Extension<ClientRequestId>`
|
||||
- [ ] Implement `FromParts` for `Extension<ClientRequestId>`
|
||||
|
||||
Changes since the RFC has been approved
|
||||
---------------------------------------
|
||||
|
||||
This RFC has been changed to only implement `ServerRequestId`.
|
||||
|
|
|
@ -15,6 +15,7 @@ publish = true
|
|||
[features]
|
||||
aws-lambda = ["dep:lambda_http"]
|
||||
unredacted-logging = []
|
||||
request-id = ["dep:uuid"]
|
||||
|
||||
[dependencies]
|
||||
aws-smithy-http = { path = "../aws-smithy-http", features = ["rt-tokio"] }
|
||||
|
@ -40,6 +41,7 @@ tracing = "0.1.35"
|
|||
tokio = { version = "1.8.4", features = ["full"] }
|
||||
tower = { version = "0.4.11", features = ["util", "make"], default-features = false }
|
||||
tower-http = { version = "0.3", features = ["add-extension", "map-response-body"] }
|
||||
uuid = { version = "1", features = ["v4", "fast-rng"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1"
|
||||
|
|
|
@ -44,7 +44,7 @@ futures-util = "0.3"
|
|||
lambda_http = "0.7.1"
|
||||
|
||||
# Local paths
|
||||
aws-smithy-http-server = { path = "../../", features = ["aws-lambda"] }
|
||||
aws-smithy-http-server = { path = "../../", features = ["aws-lambda", "request-id"] }
|
||||
pokemon-service-server-sdk = { path = "../pokemon-service-server-sdk/" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -5,15 +5,16 @@
|
|||
|
||||
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 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, NotAuthorized},
|
||||
input::GetStorageInput,
|
||||
output::GetStorageOutput,
|
||||
input::{DoNothingInput, GetStorageInput},
|
||||
output::{DoNothingOutput, GetStorageOutput},
|
||||
PokemonService,
|
||||
};
|
||||
|
||||
|
@ -55,6 +56,14 @@ pub async fn get_storage_with_local_approved(
|
|||
Err(GetStorageError::NotAuthorized(NotAuthorized {}))
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -64,11 +73,13 @@ async fn main() {
|
|||
.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");
|
||||
|
||||
let app = app.layer(&ServerRequestIdProviderLayer::new());
|
||||
|
||||
// Start the [`hyper::Server`].
|
||||
let bind: SocketAddr = format!("{}:{}", args.address, args.port)
|
||||
.parse()
|
||||
|
|
|
@ -67,6 +67,9 @@ pub mod extension;
|
|||
#[cfg(feature = "aws-lambda")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "aws-lambda")))]
|
||||
pub mod lambda;
|
||||
#[cfg(feature = "request-id")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "request-id")))]
|
||||
pub mod request_id;
|
||||
|
||||
fn internal_server_error() -> http::Response<BoxBody> {
|
||||
let mut response = http::Response::new(empty());
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
//! # Request IDs
|
||||
//!
|
||||
//! `aws-smithy-http-server` provides the [`ServerRequestId`].
|
||||
//!
|
||||
//! ## `ServerRequestId`
|
||||
//!
|
||||
//! A [`ServerRequestId`] is an opaque random identifier generated by the server every time it receives a request.
|
||||
//! It uniquely identifies the request within that service instance. It can be used to collate all logs, events and
|
||||
//! data related to a single operation.
|
||||
//!
|
||||
//! The [`ServerRequestId`] can be returned to the caller, who can in turn share the [`ServerRequestId`] to help the service owner in troubleshooting issues related to their usage of the service.
|
||||
//!
|
||||
//! The [`ServerRequestId`] is not meant to be propagated to downstream dependencies of the service. You should rely on a distributed tracing implementation for correlation purposes (e.g. OpenTelemetry).
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! Your handler can now optionally take as input a [`ServerRequestId`].
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! pub async fn handler(
|
||||
//! _input: Input,
|
||||
//! server_request_id: ServerRequestId,
|
||||
//! ) -> Output {
|
||||
//! /* Use server_request_id */
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
//! let app = Service::builder_without_plugins()
|
||||
//! .operation(handler)
|
||||
//! .build().unwrap();
|
||||
//!
|
||||
//! let app = app.layer(&ServerRequestIdProviderLayer::new()); /* Generate a server request ID */
|
||||
//!
|
||||
//! let bind: std::net::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());
|
||||
//! ```
|
||||
|
||||
use std::{
|
||||
fmt::Display,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use http::request::Parts;
|
||||
use thiserror::Error;
|
||||
use tower::{Layer, Service};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{body::BoxBody, response::IntoResponse};
|
||||
|
||||
use super::{internal_server_error, FromParts};
|
||||
|
||||
/// Opaque type for Server Request IDs.
|
||||
///
|
||||
/// If it is missing, the request will be rejected with a `500 Internal Server Error` response.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ServerRequestId {
|
||||
id: Uuid,
|
||||
}
|
||||
|
||||
/// The server request ID has not been added to the [`Request`](http::Request) or has been previously removed.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Error)]
|
||||
#[error("the `ServerRequestId` is not present in the `http::Request`")]
|
||||
pub struct MissingServerRequestId;
|
||||
|
||||
impl ServerRequestId {
|
||||
pub fn new() -> Self {
|
||||
Self { id: Uuid::new_v4() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ServerRequestId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.id.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> FromParts<P> for ServerRequestId {
|
||||
type Rejection = MissingServerRequestId;
|
||||
|
||||
fn from_parts(parts: &mut Parts) -> Result<Self, Self::Rejection> {
|
||||
parts.extensions.remove().ok_or(MissingServerRequestId)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ServerRequestId {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ServerRequestIdProvider<S> {
|
||||
inner: S,
|
||||
}
|
||||
|
||||
/// A layer that provides services with a unique request ID instance
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct ServerRequestIdProviderLayer;
|
||||
|
||||
impl ServerRequestIdProviderLayer {
|
||||
/// Generate a new unique request ID
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ServerRequestIdProviderLayer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Layer<S> for ServerRequestIdProviderLayer {
|
||||
type Service = ServerRequestIdProvider<S>;
|
||||
|
||||
fn layer(&self, inner: S) -> Self::Service {
|
||||
ServerRequestIdProvider { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Body, S> Service<http::Request<Body>> for ServerRequestIdProvider<S>
|
||||
where
|
||||
S: Service<http::Request<Body>>,
|
||||
{
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, mut req: http::Request<Body>) -> Self::Future {
|
||||
req.extensions_mut().insert(ServerRequestId::new());
|
||||
self.inner.call(req)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Protocol> IntoResponse<Protocol> for MissingServerRequestId {
|
||||
fn into_response(self) -> http::Response<BoxBody> {
|
||||
internal_server_error()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue