From 5a6bd15c080f296cd5e30ea3015963a8394be910 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Wed, 6 Oct 2021 09:47:10 +0200 Subject: [PATCH 01/11] Add `SetRequestId` middleware Fixes https://github.com/tower-rs/tower-http/issues/135 --- tower-http/CHANGELOG.md | 1 + tower-http/Cargo.toml | 5 +- tower-http/src/lib.rs | 4 + tower-http/src/request_id.rs | 172 +++++++++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 tower-http/src/request_id.rs diff --git a/tower-http/CHANGELOG.md b/tower-http/CHANGELOG.md index c02af291..ac423424 100644 --- a/tower-http/CHANGELOG.md +++ b/tower-http/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 be used instead. ([#170]) - Add `ServiceBuilderExt` which adds methods to `tower::ServiceBuilder` for adding middleware from tower-http. +- Add `SetRequestId` middleware. [#124]: https://github.com/tower-rs/tower-http/pull/124 [#156]: https://github.com/tower-rs/tower-http/pull/156 diff --git a/tower-http/Cargo.toml b/tower-http/Cargo.toml index 9498e49b..81b41dbf 100644 --- a/tower-http/Cargo.toml +++ b/tower-http/Cargo.toml @@ -27,11 +27,12 @@ base64 = { version = "0.13", optional = true } iri-string = { version = "0.4", optional = true } mime = { version = "0.3", optional = true, default_features = false } mime_guess = { version = "2", optional = true, default_features = false } +percent-encoding = { version = "2.1.0", optional = true } tokio = { version = "1", optional = true, default_features = false } tokio-util = { version = "0.6", optional = true, default_features = false, features = ["io"] } tower = { version = "0.4.1", optional = true } tracing = { version = "0.1", default_features = false, optional = true } -percent-encoding = { version = "2.1.0", optional = true } +uuid = { version = "0.8", features = ["v4"], optional = true } [dev-dependencies] bytes = "1" @@ -59,6 +60,7 @@ full = [ "metrics", "propagate-header", "redirect", + "request-id", "sensitive-headers", "set-header", "trace", @@ -75,6 +77,7 @@ map-response-body = [] metrics = ["tokio/time"] propagate-header = [] redirect = [] +request-id = [] sensitive-headers = [] set-header = [] trace = ["tracing"] diff --git a/tower-http/src/lib.rs b/tower-http/src/lib.rs index 294163c0..40d1e7f2 100644 --- a/tower-http/src/lib.rs +++ b/tower-http/src/lib.rs @@ -318,6 +318,10 @@ pub mod metrics; #[cfg_attr(docsrs, doc(cfg(feature = "cors")))] pub mod cors; +#[cfg(feature = "request-id")] +#[cfg_attr(docsrs, doc(cfg(feature = "request-id")))] +pub mod request_id; + pub mod classify; pub mod services; diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs new file mode 100644 index 00000000..090e5aae --- /dev/null +++ b/tower-http/src/request_id.rs @@ -0,0 +1,172 @@ +use http::{ + header::{HeaderName, HeaderValue}, + request::Parts, + Request, +}; +use std::task::{Context, Poll}; +use tower_layer::Layer; +use tower_service::Service; + +pub trait MakeRequestId { + fn make_request_id(&mut self, request_parts: &Parts) -> (HeaderName, Option); +} + +impl MakeRequestId for F +where + F: FnMut(&Parts) -> (HeaderName, Option), +{ + fn make_request_id(&mut self, request_parts: &Parts) -> (HeaderName, Option) { + self(request_parts) + } +} + +#[cfg(feature = "uuid")] +#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] +#[derive(Debug, Clone)] +pub struct UuidRequestId { + header_name: HeaderName, +} + +#[cfg(feature = "uuid")] +#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] +impl UuidRequestId { + pub fn new(header_name: HeaderName) -> Self { + Self { header_name } + } +} + +#[cfg(feature = "uuid")] +impl MakeRequestId for UuidRequestId { + fn make_request_id(&mut self, request_parts: &Parts) -> (HeaderName, Option) { + if request_parts.headers.contains_key(&self.header_name) { + (self.header_name.clone(), None) + } else { + let id = HeaderValue::from_str(&uuid::Uuid::new_v4().to_string()) + .expect("uuid wasn't valid header value"); + let id = RequestId::new(id); + (self.header_name.clone(), Some(id)) + } + } +} + +#[derive(Debug, Clone)] +pub struct RequestId(HeaderValue); + +impl RequestId { + pub fn new(header_value: T) -> Self + where + T: Into, + { + Self(header_value.into()) + } + + pub fn header_value(&self) -> &HeaderValue { + &self.0 + } + + pub fn into_header_value(self) -> HeaderValue { + self.0 + } + + pub fn from_request(request: &Request) -> Option { + request.extensions().get().cloned() + } +} + +#[derive(Debug, Clone)] +pub struct SetRequestIdLayer { + make_request_id: M, +} + +impl SetRequestIdLayer { + pub fn new(make_request_id: M) -> Self { + SetRequestIdLayer { make_request_id } + } +} + +#[cfg(feature = "uuid")] +#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] +impl SetRequestIdLayer { + pub fn uuid(header_name: HeaderName) -> Self { + Self::new(UuidRequestId::new(header_name)) + } +} + +impl Layer for SetRequestIdLayer +where + M: Clone, +{ + type Service = SetRequestId; + + fn layer(&self, inner: S) -> Self::Service { + SetRequestId::new(inner, self.make_request_id.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct SetRequestId { + inner: S, + make_request_id: M, +} + +impl SetRequestId { + pub fn new(inner: S, make_request_id: M) -> Self { + Self { + inner, + make_request_id, + } + } + + define_inner_service_accessors!(); + + pub fn layer(make_request_id: M) -> SetRequestIdLayer { + SetRequestIdLayer::new(make_request_id) + } +} + +#[cfg(feature = "uuid")] +#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] +impl SetRequestId { + pub fn uuid(inner: S, header_name: HeaderName) -> Self { + Self::new(inner, UuidRequestId::new(header_name)) + } +} + +impl Service> for SetRequestId +where + S: Service>, + M: MakeRequestId, +{ + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + #[inline] + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: Request) -> Self::Future { + let (mut parts, body) = req.into_parts(); + + match self.make_request_id.make_request_id(&parts) { + (header, Some(request_id)) => { + parts.extensions.insert(request_id.clone()); + parts.headers.insert(header, request_id.0); + } + (header, None) => { + if parts.extensions.get::().is_none() { + if let Some(request_id) = parts.headers.get(header) { + parts.extensions.insert(RequestId::new(request_id.clone())); + } + } + } + } + + let req = Request::from_parts(parts, body); + + self.inner.call(req) + } +} + +// TODO(david): tests From d3e9d3fd28ad360824ec24e5da315fe76a63cf7a Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Fri, 19 Nov 2021 12:32:55 +0100 Subject: [PATCH 02/11] change things around a bit --- tower-http/Cargo.toml | 1 - tower-http/src/builder.rs | 69 +++++++- tower-http/src/request_id.rs | 305 +++++++++++++++++++++++++---------- 3 files changed, 286 insertions(+), 89 deletions(-) diff --git a/tower-http/Cargo.toml b/tower-http/Cargo.toml index 81b41dbf..98415e50 100644 --- a/tower-http/Cargo.toml +++ b/tower-http/Cargo.toml @@ -32,7 +32,6 @@ tokio = { version = "1", optional = true, default_features = false } tokio-util = { version = "0.6", optional = true, default_features = false, features = ["io"] } tower = { version = "0.4.1", optional = true } tracing = { version = "0.1", default_features = false, optional = true } -uuid = { version = "0.8", features = ["v4"], optional = true } [dev-dependencies] bytes = "1" diff --git a/tower-http/src/builder.rs b/tower-http/src/builder.rs index 5bd50483..dbf8498e 100644 --- a/tower-http/src/builder.rs +++ b/tower-http/src/builder.rs @@ -37,7 +37,7 @@ use tower_layer::Stack; /// # service.ready().await.unwrap().call(Request::new(Body::empty())).await.unwrap(); /// # } /// ``` -pub trait ServiceBuilderExt: crate::sealed::Sealed { +pub trait ServiceBuilderExt: crate::sealed::Sealed + Sized { /// Propagate a header from the request to the response. /// /// See [`tower_http::propagate_header`] for more details. @@ -302,6 +302,53 @@ pub trait ServiceBuilderExt: crate::sealed::Sealed { header_name: HeaderName, make: M, ) -> ServiceBuilder, L>>; + + /// Add request id header and extension. + /// + /// See [`tower_http::request_id`] for more details. + /// + /// [`tower_http::request_id`]: crate::request_id + #[cfg(feature = "request-id")] + #[cfg_attr(docsrs, doc(cfg(feature = "request-id")))] + fn set_request_id( + self, + header_name: HeaderName, + make_request_id: M, + ) -> ServiceBuilder, L>>; + + /// TODO + #[cfg(feature = "request-id")] + #[cfg_attr(docsrs, doc(cfg(feature = "request-id")))] + fn set_x_request_id( + self, + make_request_id: M, + ) -> ServiceBuilder, L>> { + self.set_request_id( + HeaderName::from_static(crate::request_id::X_REQUEST_ID), + make_request_id, + ) + } + + /// Propgate request ids from requests to responses. + /// + /// See [`tower_http::request_id`] for more details. + /// + /// [`tower_http::request_id`]: crate::request_id + #[cfg(feature = "request-id")] + #[cfg_attr(docsrs, doc(cfg(feature = "request-id")))] + fn propagate_request_id( + self, + header_name: HeaderName, + ) -> ServiceBuilder>; + + /// TODO + #[cfg(feature = "request-id")] + #[cfg_attr(docsrs, doc(cfg(feature = "request-id")))] + fn propagate_x_request_id( + self, + ) -> ServiceBuilder> { + self.propagate_request_id(HeaderName::from_static(crate::request_id::X_REQUEST_ID)) + } } impl crate::sealed::Sealed for ServiceBuilder {} @@ -502,4 +549,24 @@ impl ServiceBuilderExt for ServiceBuilder { make, )) } + + #[cfg(feature = "request-id")] + fn set_request_id( + self, + header_name: HeaderName, + make_request_id: M, + ) -> ServiceBuilder, L>> { + self.layer(crate::request_id::SetRequestIdLayer::new( + header_name, + make_request_id, + )) + } + + #[cfg(feature = "request-id")] + fn propagate_request_id( + self, + header_name: HeaderName, + ) -> ServiceBuilder> { + self.layer(crate::request_id::PropagateRequestIdLayer::new(header_name)) + } } diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs index 090e5aae..52b7f06e 100644 --- a/tower-http/src/request_id.rs +++ b/tower-http/src/request_id.rs @@ -1,63 +1,28 @@ +// NOTE: when uuid 1.0 is shipped we can include a `MakeRequestId` that uses that +// See https://github.com/uuid-rs/uuid/issues/113 + use http::{ header::{HeaderName, HeaderValue}, - request::Parts, - Request, + Request, Response, }; +use pin_project_lite::pin_project; use std::task::{Context, Poll}; +use std::{future::Future, pin::Pin}; use tower_layer::Layer; use tower_service::Service; -pub trait MakeRequestId { - fn make_request_id(&mut self, request_parts: &Parts) -> (HeaderName, Option); -} - -impl MakeRequestId for F -where - F: FnMut(&Parts) -> (HeaderName, Option), -{ - fn make_request_id(&mut self, request_parts: &Parts) -> (HeaderName, Option) { - self(request_parts) - } -} - -#[cfg(feature = "uuid")] -#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] -#[derive(Debug, Clone)] -pub struct UuidRequestId { - header_name: HeaderName, -} - -#[cfg(feature = "uuid")] -#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] -impl UuidRequestId { - pub fn new(header_name: HeaderName) -> Self { - Self { header_name } - } -} +pub(crate) const X_REQUEST_ID: &str = "x-request-id"; -#[cfg(feature = "uuid")] -impl MakeRequestId for UuidRequestId { - fn make_request_id(&mut self, request_parts: &Parts) -> (HeaderName, Option) { - if request_parts.headers.contains_key(&self.header_name) { - (self.header_name.clone(), None) - } else { - let id = HeaderValue::from_str(&uuid::Uuid::new_v4().to_string()) - .expect("uuid wasn't valid header value"); - let id = RequestId::new(id); - (self.header_name.clone(), Some(id)) - } - } +pub trait MakeRequestId { + fn make_request_id(&mut self, request: &Request) -> RequestId; } #[derive(Debug, Clone)] pub struct RequestId(HeaderValue); impl RequestId { - pub fn new(header_value: T) -> Self - where - T: Into, - { - Self(header_value.into()) + pub fn new(header_value: HeaderValue) -> Self { + Self(header_value) } pub fn header_value(&self) -> &HeaderValue { @@ -67,28 +32,27 @@ impl RequestId { pub fn into_header_value(self) -> HeaderValue { self.0 } - - pub fn from_request(request: &Request) -> Option { - request.extensions().get().cloned() - } } #[derive(Debug, Clone)] pub struct SetRequestIdLayer { + header_name: HeaderName, make_request_id: M, } impl SetRequestIdLayer { - pub fn new(make_request_id: M) -> Self { - SetRequestIdLayer { make_request_id } + pub fn new(header_name: HeaderName, make_request_id: M) -> Self { + SetRequestIdLayer { + header_name, + make_request_id, + } } -} -#[cfg(feature = "uuid")] -#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] -impl SetRequestIdLayer { - pub fn uuid(header_name: HeaderName) -> Self { - Self::new(UuidRequestId::new(header_name)) + pub fn x_request_id(make_request_id: M) -> Self { + SetRequestIdLayer { + header_name: HeaderName::from_static(X_REQUEST_ID), + make_request_id, + } } } @@ -99,42 +63,48 @@ where type Service = SetRequestId; fn layer(&self, inner: S) -> Self::Service { - SetRequestId::new(inner, self.make_request_id.clone()) + SetRequestId::new( + inner, + self.header_name.clone(), + self.make_request_id.clone(), + ) } } #[derive(Debug, Clone)] pub struct SetRequestId { inner: S, + header_name: HeaderName, make_request_id: M, } impl SetRequestId { - pub fn new(inner: S, make_request_id: M) -> Self { + pub fn new(inner: S, header_name: HeaderName, make_request_id: M) -> Self { Self { inner, + header_name, make_request_id, } } - define_inner_service_accessors!(); - - pub fn layer(make_request_id: M) -> SetRequestIdLayer { - SetRequestIdLayer::new(make_request_id) + pub fn x_request_id(inner: S, make_request_id: M) -> Self { + Self::new( + inner, + HeaderName::from_static(X_REQUEST_ID), + make_request_id, + ) } -} -#[cfg(feature = "uuid")] -#[cfg_attr(docsrs, doc(cfg(feature = "uuid")))] -impl SetRequestId { - pub fn uuid(inner: S, header_name: HeaderName) -> Self { - Self::new(inner, UuidRequestId::new(header_name)) + define_inner_service_accessors!(); + + pub fn layer(header_name: HeaderName, make_request_id: M) -> SetRequestIdLayer { + SetRequestIdLayer::new(header_name, make_request_id) } } -impl Service> for SetRequestId +impl Service> for SetRequestId where - S: Service>, + S: Service, Response = Response>, M: MakeRequestId, { type Response = S::Response; @@ -146,27 +116,188 @@ where self.inner.poll_ready(cx) } + fn call(&mut self, mut req: Request) -> Self::Future { + if let Some(request_id) = req.headers().get(&self.header_name).cloned() { + if req.extensions().get::().is_none() { + req.extensions_mut().insert(RequestId::new(request_id)); + } + } else { + let request_id = self.make_request_id.make_request_id(&req); + req.extensions_mut().insert(request_id.clone()); + req.headers_mut() + .insert(self.header_name.clone(), request_id.0); + } + + self.inner.call(req) + } +} + +#[derive(Debug, Clone)] +pub struct PropagateRequestIdLayer { + header_name: HeaderName, +} + +impl PropagateRequestIdLayer { + pub fn new(header_name: HeaderName) -> Self { + PropagateRequestIdLayer { header_name } + } + + pub fn x_request_id() -> Self { + Self::new(HeaderName::from_static(X_REQUEST_ID)) + } +} + +impl Layer for PropagateRequestIdLayer { + type Service = PropagateRequestId; + + fn layer(&self, inner: S) -> Self::Service { + PropagateRequestId::new(inner, self.header_name.clone()) + } +} + +#[derive(Debug, Clone)] +pub struct PropagateRequestId { + inner: S, + header_name: HeaderName, +} + +impl PropagateRequestId { + pub fn new(inner: S, header_name: HeaderName) -> Self { + Self { inner, header_name } + } + + pub fn x_request_id(inner: S) -> Self { + Self::new(inner, HeaderName::from_static(X_REQUEST_ID)) + } + + define_inner_service_accessors!(); + + pub fn layer(header_name: HeaderName) -> PropagateRequestIdLayer { + PropagateRequestIdLayer::new(header_name) + } +} + +impl Service> for PropagateRequestId +where + S: Service, Response = Response>, +{ + type Response = S::Response; + type Error = S::Error; + type Future = PropagateRequestIdResponseFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + fn call(&mut self, req: Request) -> Self::Future { - let (mut parts, body) = req.into_parts(); + let request_id = req + .headers() + .get(&self.header_name) + .cloned() + .map(RequestId::new); + + PropagateRequestIdResponseFuture { + inner: self.inner.call(req), + header_name: self.header_name.clone(), + request_id, + } + } +} + +pin_project! { + /// Response future for [`PropagateRequestId`]. + pub struct PropagateRequestIdResponseFuture { + #[pin] + inner: F, + header_name: HeaderName, + request_id: Option, + } +} + +impl Future for PropagateRequestIdResponseFuture +where + F: Future, E>>, +{ + type Output = Result, E>; - match self.make_request_id.make_request_id(&parts) { - (header, Some(request_id)) => { - parts.extensions.insert(request_id.clone()); - parts.headers.insert(header, request_id.0); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let mut response = futures_core::ready!(this.inner.poll(cx))?; + + if let Some(current_id) = response.headers().get(&*this.header_name) { + if response.extensions().get::().is_none() { + let current_id = current_id.clone(); + response.extensions_mut().insert(current_id); } - (header, None) => { - if parts.extensions.get::().is_none() { - if let Some(request_id) = parts.headers.get(header) { - parts.extensions.insert(RequestId::new(request_id.clone())); - } - } + } else if let Some(request_id) = this.request_id.take() { + response + .headers_mut() + .insert(this.header_name.clone(), request_id.0.clone()); + response.extensions_mut().insert(request_id); + } + + Poll::Ready(Ok(response)) + } +} + +#[cfg(test)] +mod tests { + use crate::ServiceBuilderExt as _; + use hyper::{Body, Response}; + use std::{ + convert::Infallible, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, + }; + use tower::{ServiceBuilder, ServiceExt}; + + #[allow(unused_imports)] + use super::*; + + #[tokio::test] + async fn test_something() { + #[derive(Clone, Default)] + struct Counter(Arc); + + impl MakeRequestId for Counter { + fn make_request_id(&mut self, _request: &Request) -> RequestId { + let id = HeaderValue::from_str(&self.0.fetch_add(1, Ordering::SeqCst).to_string()) + .unwrap(); + RequestId::new(id) } } - let req = Request::from_parts(parts, body); + let svc = ServiceBuilder::new() + .set_x_request_id(Counter::default()) + .propagate_x_request_id() + .service_fn(handler); - self.inner.call(req) + // header on response + let req = Request::builder().body(Body::empty()).unwrap(); + let res = svc.clone().oneshot(req).await.unwrap(); + assert_eq!(res.headers()["x-request-id"], "0"); + + let req = Request::builder().body(Body::empty()).unwrap(); + let res = svc.clone().oneshot(req).await.unwrap(); + assert_eq!(res.headers()["x-request-id"], "1"); + + // doesn't override if header is already there + let req = Request::builder() + .header("x-request-id", "foo") + .body(Body::empty()) + .unwrap(); + let res = svc.clone().oneshot(req).await.unwrap(); + assert_eq!(res.headers()["x-request-id"], "foo"); + + // extension propagated + let req = Request::builder().body(Body::empty()).unwrap(); + let res = svc.clone().oneshot(req).await.unwrap(); + assert_eq!(res.extensions().get::().unwrap().0, "2"); } -} -// TODO(david): tests + async fn handler(_: Request) -> Result, Infallible> { + Ok(Response::new(Body::empty())) + } +} From a1a940312b80eaf4d648ece0b6831cdfd8441c53 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Fri, 19 Nov 2021 15:43:48 +0100 Subject: [PATCH 03/11] docs --- tower-http/Cargo.toml | 1 + tower-http/src/builder.rs | 14 +- tower-http/src/request_id.rs | 294 +++++++++++++++++++++++++++++++++-- 3 files changed, 295 insertions(+), 14 deletions(-) diff --git a/tower-http/Cargo.toml b/tower-http/Cargo.toml index 98415e50..e466f2cd 100644 --- a/tower-http/Cargo.toml +++ b/tower-http/Cargo.toml @@ -43,6 +43,7 @@ once_cell = "1" tokio = { version = "1", features = ["full"] } tower = { version = "0.4.10", features = ["buffer", "util", "retry", "make", "timeout"] } tracing-subscriber = "0.2" +uuid = { version = "0.8", features = ["v4"] } [features] default = [] diff --git a/tower-http/src/builder.rs b/tower-http/src/builder.rs index dbf8498e..66896a32 100644 --- a/tower-http/src/builder.rs +++ b/tower-http/src/builder.rs @@ -314,7 +314,9 @@ pub trait ServiceBuilderExt: crate::sealed::Sealed + Sized { self, header_name: HeaderName, make_request_id: M, - ) -> ServiceBuilder, L>>; + ) -> ServiceBuilder, L>> + where + M: crate::request_id::MakeRequestId; /// TODO #[cfg(feature = "request-id")] @@ -322,7 +324,10 @@ pub trait ServiceBuilderExt: crate::sealed::Sealed + Sized { fn set_x_request_id( self, make_request_id: M, - ) -> ServiceBuilder, L>> { + ) -> ServiceBuilder, L>> + where + M: crate::request_id::MakeRequestId, + { self.set_request_id( HeaderName::from_static(crate::request_id::X_REQUEST_ID), make_request_id, @@ -555,7 +560,10 @@ impl ServiceBuilderExt for ServiceBuilder { self, header_name: HeaderName, make_request_id: M, - ) -> ServiceBuilder, L>> { + ) -> ServiceBuilder, L>> + where + M: crate::request_id::MakeRequestId, + { self.layer(crate::request_id::SetRequestIdLayer::new( header_name, make_request_id, diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs index 52b7f06e..f3c7ca7a 100644 --- a/tower-http/src/request_id.rs +++ b/tower-http/src/request_id.rs @@ -1,3 +1,212 @@ +//! Set and propagate request ids. +//! +//! # Example +//! +//! ``` +//! use http::{Request, Response, header::HeaderName}; +//! use tower::{Service, ServiceExt, ServiceBuilder}; +//! use tower_http::request_id::{ +//! SetRequestIdLayer, PropagateRequestIdLayer, MakeRequestId, RequestId, +//! }; +//! use hyper::Body; +//! use std::sync::{Arc, atomic::{AtomicU64, Ordering}}; +//! +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! # let handler = tower::service_fn(|request: Request| async move { +//! # Ok::<_, std::convert::Infallible>(Response::new(request.into_body())) +//! # }); +//! # +//! // A `MakeRequestId` that increments an atomic counter +//! #[derive(Clone, Default)] +//! struct MyMakeRequestId { +//! counter: Arc, +//! } +//! +//! impl MakeRequestId for MyMakeRequestId { +//! fn make_request_id(&mut self, request: &Request) -> Option { +//! let request_id = self.counter +//! .fetch_add(1, Ordering::SeqCst) +//! .to_string() +//! .parse() +//! .unwrap(); +//! +//! Some(RequestId::new(request_id)) +//! } +//! } +//! +//! let x_request_id = HeaderName::from_static("x-request-id"); +//! +//! let mut svc = ServiceBuilder::new() +//! // set `x-request-id` header on all requests +//! .layer(SetRequestIdLayer::new( +//! x_request_id.clone(), +//! MyMakeRequestId::default(), +//! )) +//! // propagate `x-request-id` headers from request to response +//! .layer(PropagateRequestIdLayer::new(x_request_id)) +//! .service(handler); +//! +//! let request = Request::new(Body::empty()); +//! let response = svc.ready().await?.call(request).await?; +//! +//! assert_eq!(response.headers()["x-request-id"], "0"); +//! # +//! # Ok(()) +//! # } +//! ``` +//! +//! Additional convenience methods are available on [`ServiceBuilderExt`]: +//! +//! ``` +//! use tower_http::ServiceBuilderExt; +//! # use http::{Request, Response, header::HeaderName}; +//! # use tower::{Service, ServiceExt, ServiceBuilder}; +//! # use tower_http::request_id::{ +//! # SetRequestIdLayer, PropagateRequestIdLayer, MakeRequestId, RequestId, +//! # }; +//! # use hyper::Body; +//! # use std::sync::{Arc, atomic::{AtomicU64, Ordering}}; +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! # let handler = tower::service_fn(|request: Request| async move { +//! # Ok::<_, std::convert::Infallible>(Response::new(request.into_body())) +//! # }); +//! # #[derive(Clone, Default)] +//! # struct MyMakeRequestId { +//! # counter: Arc, +//! # } +//! # impl MakeRequestId for MyMakeRequestId { +//! # fn make_request_id(&mut self, request: &Request) -> Option { +//! # let request_id = self.counter +//! # .fetch_add(1, Ordering::SeqCst) +//! # .to_string() +//! # .parse() +//! # .unwrap(); +//! # Some(RequestId::new(request_id)) +//! # } +//! # } +//! # +//! let mut svc = ServiceBuilder::new() +//! .set_x_request_id(MyMakeRequestId::default()) +//! .propagate_x_request_id() +//! .service(handler); +//! +//! let request = Request::new(Body::empty()); +//! let response = svc.ready().await?.call(request).await?; +//! +//! assert_eq!(response.headers()["x-request-id"], "0"); +//! # +//! # Ok(()) +//! # } +//! ``` +//! +//! See [`SetRequestId`] and [`PropagateRequestId`] for more details. +//! +//! # Using `Trace` +//! +//! To have request ids show up correctly in logs produced by [`Trace`] you must apply the layers +//! in this order: +//! +//! ``` +//! use tower_http::{ +//! ServiceBuilderExt, +//! trace::{TraceLayer, DefaultMakeSpan, DefaultOnResponse}, +//! }; +//! # use http::{Request, Response, header::HeaderName}; +//! # use tower::{Service, ServiceExt, ServiceBuilder}; +//! # use tower_http::request_id::{ +//! # SetRequestIdLayer, PropagateRequestIdLayer, MakeRequestId, RequestId, +//! # }; +//! # use hyper::Body; +//! # use std::sync::{Arc, atomic::{AtomicU64, Ordering}}; +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! # let handler = tower::service_fn(|request: Request| async move { +//! # Ok::<_, std::convert::Infallible>(Response::new(request.into_body())) +//! # }); +//! # #[derive(Clone, Default)] +//! # struct MyMakeRequestId { +//! # counter: Arc, +//! # } +//! # impl MakeRequestId for MyMakeRequestId { +//! # fn make_request_id(&mut self, request: &Request) -> Option { +//! # let request_id = self.counter +//! # .fetch_add(1, Ordering::SeqCst) +//! # .to_string() +//! # .parse() +//! # .unwrap(); +//! # Some(RequestId::new(request_id)) +//! # } +//! # } +//! +//! let svc = ServiceBuilder::new() +//! // make sure to set request ids before the request reaches `TraceLayer` +//! .set_x_request_id(MyMakeRequestId::default()) +//! // log requests and responses +//! .layer( +//! TraceLayer::new_for_http() +//! .make_span_with(DefaultMakeSpan::new().include_headers(true)) +//! .on_response(DefaultOnResponse::new().include_headers(true)) +//! ) +//! // propagate the header to the response before the response reaches `TraceLayer` +//! .propagate_x_request_id() +//! .service(handler); +//! # +//! # Ok(()) +//! # } +//! ``` +//! +//! # Using `UUID`s +//! +//! Generating request ids from [`Uuid`]s can be done like so: +//! +//! ``` +//! use tower_http::ServiceBuilderExt; +//! use http::{Request, Response, header::HeaderName}; +//! use tower::{Service, ServiceExt, ServiceBuilder}; +//! use tower_http::request_id::{ +//! SetRequestIdLayer, PropagateRequestIdLayer, MakeRequestId, RequestId, +//! }; +//! use uuid::Uuid; +//! # use hyper::Body; +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! # let handler = tower::service_fn(|request: Request| async move { +//! # Ok::<_, std::convert::Infallible>(Response::new(request.into_body())) +//! # }); +//! +//! #[derive(Clone, Copy)] +//! struct MakeRequestUuid; +//! +//! impl MakeRequestId for MakeRequestUuid { +//! fn make_request_id(&mut self, request: &Request) -> Option { +//! let request_id = Uuid::new_v4() +//! .to_string() +//! .parse() +//! .unwrap(); +//! Some(RequestId::new(request_id)) +//! } +//! } +//! +//! let mut svc = ServiceBuilder::new() +//! .set_x_request_id(MakeRequestUuid) +//! .propagate_x_request_id() +//! .service(handler); +//! +//! let request = Request::new(Body::empty()); +//! let response = svc.ready().await?.call(request).await?; +//! +//! assert!(response.headers().get("x-request-id").is_some()); +//! # +//! # Ok(()) +//! # } +//! ``` +//! +//! [`ServiceBuilderExt`]: crate::ServiceBuilderExt +//! [`Uuid`]: https://crates.io/crates/uuid +//! [`Trace`]: crate::trace::Trace + // NOTE: when uuid 1.0 is shipped we can include a `MakeRequestId` that uses that // See https://github.com/uuid-rs/uuid/issues/113 @@ -13,27 +222,46 @@ use tower_service::Service; pub(crate) const X_REQUEST_ID: &str = "x-request-id"; +/// Trait for producing [`RequestId`]s. +/// +/// Used by [`SetRequestId`]. pub trait MakeRequestId { - fn make_request_id(&mut self, request: &Request) -> RequestId; + /// Try and produce a [`RequestId`] from the request. + fn make_request_id(&mut self, request: &Request) -> Option; } +/// An identifier for a request. #[derive(Debug, Clone)] pub struct RequestId(HeaderValue); impl RequestId { + /// Create a new `RequestId` from a [`HeaderValue`]. pub fn new(header_value: HeaderValue) -> Self { Self(header_value) } + /// Gets a reference to the underlying [`HeaderValue`]. pub fn header_value(&self) -> &HeaderValue { &self.0 } + /// Consumes `self`, returning the underlying [`HeaderValue`]. pub fn into_header_value(self) -> HeaderValue { self.0 } } +impl From for RequestId { + fn from(value: HeaderValue) -> Self { + Self::new(value) + } +} + +/// Set request id headers and extensions on requests. +/// +/// This layer applies the [`SetRequestId`] middleware. +/// +/// See the [module docs](self) and [`SetRequestId`] for more details. #[derive(Debug, Clone)] pub struct SetRequestIdLayer { header_name: HeaderName, @@ -41,14 +269,22 @@ pub struct SetRequestIdLayer { } impl SetRequestIdLayer { - pub fn new(header_name: HeaderName, make_request_id: M) -> Self { + /// Create a new `SetRequestIdLayer`. + pub fn new(header_name: HeaderName, make_request_id: M) -> Self + where + M: MakeRequestId, + { SetRequestIdLayer { header_name, make_request_id, } } - pub fn x_request_id(make_request_id: M) -> Self { + /// Create a new `SetRequestIdLayer` that uses `x-request-id` as the header name. + pub fn x_request_id(make_request_id: M) -> Self + where + M: MakeRequestId, + { SetRequestIdLayer { header_name: HeaderName::from_static(X_REQUEST_ID), make_request_id, @@ -58,7 +294,7 @@ impl SetRequestIdLayer { impl Layer for SetRequestIdLayer where - M: Clone, + M: Clone + MakeRequestId, { type Service = SetRequestId; @@ -71,6 +307,15 @@ where } } +/// Set request id headers and extensions on requests. +/// +/// See the [module docs](self) for an example. +/// +/// If [`MakeRequestId::make_request_id`] returns `Some(_)` and the request doesn't already have a +/// header with the same name, then the header will be inserted. +/// +/// Additionally [`RequestId`] will be inserted into [`Request::extensions`] so other +/// services can access it. #[derive(Debug, Clone)] pub struct SetRequestId { inner: S, @@ -79,7 +324,11 @@ pub struct SetRequestId { } impl SetRequestId { - pub fn new(inner: S, header_name: HeaderName, make_request_id: M) -> Self { + /// Create a new `SetRequestId`. + pub fn new(inner: S, header_name: HeaderName, make_request_id: M) -> Self + where + M: MakeRequestId, + { Self { inner, header_name, @@ -87,7 +336,11 @@ impl SetRequestId { } } - pub fn x_request_id(inner: S, make_request_id: M) -> Self { + /// Create a new `SetRequestId` that uses `x-request-id` as the header name. + pub fn x_request_id(inner: S, make_request_id: M) -> Self + where + M: MakeRequestId, + { Self::new( inner, HeaderName::from_static(X_REQUEST_ID), @@ -97,7 +350,11 @@ impl SetRequestId { define_inner_service_accessors!(); - pub fn layer(header_name: HeaderName, make_request_id: M) -> SetRequestIdLayer { + /// Returns a new [`Layer`] that wraps services with a `SetRequestId` middleware. + pub fn layer(header_name: HeaderName, make_request_id: M) -> SetRequestIdLayer + where + M: MakeRequestId, + { SetRequestIdLayer::new(header_name, make_request_id) } } @@ -121,8 +378,7 @@ where if req.extensions().get::().is_none() { req.extensions_mut().insert(RequestId::new(request_id)); } - } else { - let request_id = self.make_request_id.make_request_id(&req); + } else if let Some(request_id) = self.make_request_id.make_request_id(&req) { req.extensions_mut().insert(request_id.clone()); req.headers_mut() .insert(self.header_name.clone(), request_id.0); @@ -132,16 +388,23 @@ where } } +/// Propagate request ids from requests to responses. +/// +/// This layer applies the [`PropagateRequestId`] middleware. +/// +/// See the [module docs](self) and [`PropagateRequestId`] for more details. #[derive(Debug, Clone)] pub struct PropagateRequestIdLayer { header_name: HeaderName, } impl PropagateRequestIdLayer { + /// Create a new `PropagateRequestIdLayer`. pub fn new(header_name: HeaderName) -> Self { PropagateRequestIdLayer { header_name } } + /// Create a new `PropagateRequestIdLayer` that uses `x-request-id` as the header name. pub fn x_request_id() -> Self { Self::new(HeaderName::from_static(X_REQUEST_ID)) } @@ -155,6 +418,12 @@ impl Layer for PropagateRequestIdLayer { } } +/// Propagate request ids from requests to responses. +/// +/// See the [module docs](self) for an example. +/// +/// If the request contains a matching header that header will be applied to responses. If a +/// [`RequestId`] extension is also present it will be propagated as well. #[derive(Debug, Clone)] pub struct PropagateRequestId { inner: S, @@ -162,16 +431,19 @@ pub struct PropagateRequestId { } impl PropagateRequestId { + /// Create a new `PropagateRequestId`. pub fn new(inner: S, header_name: HeaderName) -> Self { Self { inner, header_name } } + /// Create a new `PropagateRequestId` that uses `x-request-id` as the header name. pub fn x_request_id(inner: S) -> Self { Self::new(inner, HeaderName::from_static(X_REQUEST_ID)) } define_inner_service_accessors!(); + /// Returns a new [`Layer`] that wraps services with a `PropagateRequestId` middleware. pub fn layer(header_name: HeaderName) -> PropagateRequestIdLayer { PropagateRequestIdLayer::new(header_name) } @@ -262,10 +534,10 @@ mod tests { struct Counter(Arc); impl MakeRequestId for Counter { - fn make_request_id(&mut self, _request: &Request) -> RequestId { + fn make_request_id(&mut self, _request: &Request) -> Option { let id = HeaderValue::from_str(&self.0.fetch_add(1, Ordering::SeqCst).to_string()) .unwrap(); - RequestId::new(id) + Some(RequestId::new(id)) } } From c8fc4fb84ef334eea9356440b5af917cfaad38af Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Fri, 19 Nov 2021 16:00:46 +0100 Subject: [PATCH 04/11] update changelog --- tower-http/CHANGELOG.md | 5 +++-- tower-http/src/request_id.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tower-http/CHANGELOG.md b/tower-http/CHANGELOG.md index ac423424..00dc4b88 100644 --- a/tower-http/CHANGELOG.md +++ b/tower-http/CHANGELOG.md @@ -24,12 +24,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 be used instead. ([#170]) - Add `ServiceBuilderExt` which adds methods to `tower::ServiceBuilder` for adding middleware from tower-http. -- Add `SetRequestId` middleware. +- Add `SetRequestId` and `PropagateRequestId` middleware ([#150]) [#124]: https://github.com/tower-rs/tower-http/pull/124 +[#148]: https://github.com/tower-rs/tower-http/pull/148 +[#150]: https://github.com/tower-rs/tower-http/pull/150 [#156]: https://github.com/tower-rs/tower-http/pull/156 [#166]: https://github.com/tower-rs/tower-http/pull/166 -[#148]: https://github.com/tower-rs/tower-http/pull/148 [#170]: https://github.com/tower-rs/tower-http/pull/170 # 0.1.2 (November 13, 2021) diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs index f3c7ca7a..2e2abc5e 100644 --- a/tower-http/src/request_id.rs +++ b/tower-http/src/request_id.rs @@ -86,7 +86,7 @@ //! # Some(RequestId::new(request_id)) //! # } //! # } -//! # +//! //! let mut svc = ServiceBuilder::new() //! .set_x_request_id(MyMakeRequestId::default()) //! .propagate_x_request_id() From a670ad89aba4f8c0dc7ec9fed05a533f1c4bf92e Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 20 Nov 2021 08:46:37 +0100 Subject: [PATCH 05/11] only clone if necessary --- tower-http/src/request_id.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs index 2e2abc5e..66836fba 100644 --- a/tower-http/src/request_id.rs +++ b/tower-http/src/request_id.rs @@ -374,8 +374,9 @@ where } fn call(&mut self, mut req: Request) -> Self::Future { - if let Some(request_id) = req.headers().get(&self.header_name).cloned() { + if let Some(request_id) = req.headers().get(&self.header_name) { if req.extensions().get::().is_none() { + let request_id = request_id.clone(); req.extensions_mut().insert(RequestId::new(request_id)); } } else if let Some(request_id) = self.make_request_id.make_request_id(&req) { From 72bef7b302b1a64ee722413a9dccca48598f9c89 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Sat, 20 Nov 2021 08:53:06 +0100 Subject: [PATCH 06/11] use constructor --- tower-http/src/request_id.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs index 66836fba..3f3b0657 100644 --- a/tower-http/src/request_id.rs +++ b/tower-http/src/request_id.rs @@ -285,10 +285,7 @@ impl SetRequestIdLayer { where M: MakeRequestId, { - SetRequestIdLayer { - header_name: HeaderName::from_static(X_REQUEST_ID), - make_request_id, - } + SetRequestIdLayer::new(HeaderName::from_static(X_REQUEST_ID), make_request_id) } } From 39e2e3d9364b5d93c8c0ac890c9948e1a13f079c Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 22 Nov 2021 10:36:35 +0100 Subject: [PATCH 07/11] Fix not overriding extensions on responses --- tower-http/src/request_id.rs | 71 +++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs index 3f3b0657..a01a4edd 100644 --- a/tower-http/src/request_id.rs +++ b/tower-http/src/request_id.rs @@ -497,7 +497,7 @@ where if let Some(current_id) = response.headers().get(&*this.header_name) { if response.extensions().get::().is_none() { let current_id = current_id.clone(); - response.extensions_mut().insert(current_id); + response.extensions_mut().insert(RequestId::new(current_id)); } } else if let Some(request_id) = this.request_id.take() { response @@ -527,18 +527,7 @@ mod tests { use super::*; #[tokio::test] - async fn test_something() { - #[derive(Clone, Default)] - struct Counter(Arc); - - impl MakeRequestId for Counter { - fn make_request_id(&mut self, _request: &Request) -> Option { - let id = HeaderValue::from_str(&self.0.fetch_add(1, Ordering::SeqCst).to_string()) - .unwrap(); - Some(RequestId::new(id)) - } - } - + async fn basic() { let svc = ServiceBuilder::new() .set_x_request_id(Counter::default()) .propagate_x_request_id() @@ -567,6 +556,62 @@ mod tests { assert_eq!(res.extensions().get::().unwrap().0, "2"); } + #[tokio::test] + async fn other_middleware_setting_request_id() { + let svc = ServiceBuilder::new() + .override_request_header( + HeaderName::from_static("x-request-id"), + HeaderValue::from_str("foo").unwrap(), + ) + .set_x_request_id(Counter::default()) + .map_request(|request: Request<_>| { + // `set_x_request_id` should set the extension if its missing + assert_eq!(request.extensions().get::().unwrap().0, "foo"); + request + }) + .propagate_x_request_id() + .service_fn(handler); + + let req = Request::builder() + .header("x-request-id", "foo") + .body(Body::empty()) + .unwrap(); + let res = svc.clone().oneshot(req).await.unwrap(); + assert_eq!(res.headers()["x-request-id"], "foo"); + assert_eq!(res.extensions().get::().unwrap().0, "foo"); + } + + #[tokio::test] + async fn other_middleware_setting_request_id_on_response() { + let svc = ServiceBuilder::new() + .set_x_request_id(Counter::default()) + .propagate_x_request_id() + .override_response_header( + HeaderName::from_static("x-request-id"), + HeaderValue::from_str("foo").unwrap(), + ) + .service_fn(handler); + + let req = Request::builder() + .header("x-request-id", "foo") + .body(Body::empty()) + .unwrap(); + let res = svc.clone().oneshot(req).await.unwrap(); + assert_eq!(res.headers()["x-request-id"], "foo"); + assert_eq!(res.extensions().get::().unwrap().0, "foo"); + } + + #[derive(Clone, Default)] + struct Counter(Arc); + + impl MakeRequestId for Counter { + fn make_request_id(&mut self, _request: &Request) -> Option { + let id = + HeaderValue::from_str(&self.0.fetch_add(1, Ordering::SeqCst).to_string()).unwrap(); + Some(RequestId::new(id)) + } + } + async fn handler(_: Request) -> Result, Infallible> { Ok(Response::new(Body::empty())) } From 66dc79e9b9108b9a3f070ca1797d05ca3e2777d4 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 22 Nov 2021 10:38:40 +0100 Subject: [PATCH 08/11] add missing docs --- tower-http/src/builder.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tower-http/src/builder.rs b/tower-http/src/builder.rs index 66896a32..d76f7fa4 100644 --- a/tower-http/src/builder.rs +++ b/tower-http/src/builder.rs @@ -318,7 +318,11 @@ pub trait ServiceBuilderExt: crate::sealed::Sealed + Sized { where M: crate::request_id::MakeRequestId; - /// TODO + /// Add request id header and extension, using `x-request-id` as the header name. + /// + /// See [`tower_http::request_id`] for more details. + /// + /// [`tower_http::request_id`]: crate::request_id #[cfg(feature = "request-id")] #[cfg_attr(docsrs, doc(cfg(feature = "request-id")))] fn set_x_request_id( @@ -346,7 +350,11 @@ pub trait ServiceBuilderExt: crate::sealed::Sealed + Sized { header_name: HeaderName, ) -> ServiceBuilder>; - /// TODO + /// Propgate request ids from requests to responses, using `x-request-id` as the header name. + /// + /// See [`tower_http::request_id`] for more details. + /// + /// [`tower_http::request_id`]: crate::request_id #[cfg(feature = "request-id")] #[cfg_attr(docsrs, doc(cfg(feature = "request-id")))] fn propagate_x_request_id( From a9a704d6e801b6e581222105556c4ad047595832 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 22 Nov 2021 10:45:28 +0100 Subject: [PATCH 09/11] document not overriding headers in module docs --- tower-http/src/request_id.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs index a01a4edd..1c33cf0f 100644 --- a/tower-http/src/request_id.rs +++ b/tower-http/src/request_id.rs @@ -203,6 +203,12 @@ //! # } //! ``` //! +//! # Doesn't override existing headers +//! +//! [`SetRequestId`] and [`PropagateRequestId`] wont override request ids if its already present on +//! requests or responses. Among other things, this allows other middleware to conditionally set +//! request ids and use the middleware in this module as a fallback. +//! //! [`ServiceBuilderExt`]: crate::ServiceBuilderExt //! [`Uuid`]: https://crates.io/crates/uuid //! [`Trace`]: crate::trace::Trace From bc2ec14e35c857807e201d575515df776a2b7feb Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 22 Nov 2021 11:19:14 +0100 Subject: [PATCH 10/11] make test more clear --- tower-http/src/request_id.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs index 1c33cf0f..6328a9ce 100644 --- a/tower-http/src/request_id.rs +++ b/tower-http/src/request_id.rs @@ -579,7 +579,7 @@ mod tests { .service_fn(handler); let req = Request::builder() - .header("x-request-id", "foo") + .header("x-request-id", "this-will-be-overriden-by-override_request_header-middleware") .body(Body::empty()) .unwrap(); let res = svc.clone().oneshot(req).await.unwrap(); From 1f1f90e46611029834f6b6448f028f0670116566 Mon Sep 17 00:00:00 2001 From: David Pedersen Date: Mon, 22 Nov 2021 12:51:21 +0100 Subject: [PATCH 11/11] formatting --- tower-http/src/request_id.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tower-http/src/request_id.rs b/tower-http/src/request_id.rs index 6328a9ce..e9126634 100644 --- a/tower-http/src/request_id.rs +++ b/tower-http/src/request_id.rs @@ -579,7 +579,10 @@ mod tests { .service_fn(handler); let req = Request::builder() - .header("x-request-id", "this-will-be-overriden-by-override_request_header-middleware") + .header( + "x-request-id", + "this-will-be-overriden-by-override_request_header-middleware", + ) .body(Body::empty()) .unwrap(); let res = svc.clone().oneshot(req).await.unwrap();