Skip to main content

rama_http/layer/
request_id.rs

1//! Set and propagate request ids.
2//!
3//! # Example
4//!
5//! ```
6//! use rama_http::layer::request_id::{
7//!     SetRequestIdLayer, PropagateRequestIdLayer, MakeRequestId, RequestId,
8//! };
9//! use rama_http::{Body, Request, Response, header::HeaderName};
10//! use rama_core::service::service_fn;
11//! use rama_core::{Service, Layer};
12//! use rama_core::error::BoxError;
13//! use std::sync::{Arc, atomic::{AtomicU64, Ordering}};
14//!
15//! # #[tokio::main]
16//! # async fn main() -> Result<(), BoxError> {
17//! # let handler = service_fn(async |request: Request| {
18//! #     Ok::<_, std::convert::Infallible>(Response::new(request.into_body()))
19//! # });
20//! #
21//! // A `MakeRequestId` that increments an atomic counter
22//! #[derive(Clone, Default)]
23//! struct MyMakeRequestId {
24//!     counter: Arc<AtomicU64>,
25//! }
26//!
27//! impl MakeRequestId for MyMakeRequestId {
28//!     fn make_request_id<B>(&self, request: &Request<B>) -> Option<RequestId> {
29//!         let request_id = self.counter
30//!             .fetch_add(1, Ordering::AcqRel)
31//!             .to_string()
32//!             .parse()
33//!             .unwrap();
34//!
35//!         Some(RequestId::new(request_id))
36//!     }
37//! }
38//!
39//! let x_request_id = HeaderName::from_static("x-request-id");
40//!
41//! let mut svc = (
42//!     // set `x-request-id` header on all requests
43//!     SetRequestIdLayer::new(
44//!         x_request_id.clone(),
45//!         MyMakeRequestId::default(),
46//!     ),
47//!     // propagate `x-request-id` headers from request to response
48//!     PropagateRequestIdLayer::new(x_request_id),
49//! ).into_layer(handler);
50//!
51//! let request = Request::new(Body::empty());
52//! let response = svc.serve(request).await?;
53//!
54//! assert_eq!(response.headers()["x-request-id"], "0");
55//! #
56//! # Ok(())
57//! # }
58//! ```
59
60use crate::{
61    Request, Response,
62    header::{HeaderName, HeaderValue},
63};
64use rama_core::{
65    Layer, Service,
66    extensions::{Extension, ExtensionsRef},
67    telemetry::tracing,
68};
69use rama_utils::macros::define_inner_service_accessors;
70use rama_utils::str::smol_str::ToSmolStr as _;
71
72use rand::RngExt as _;
73use uuid::Uuid;
74
75/// cfr: <https://github.com/plabayo/rama/blob/main/rama-http/specifications/rfc6648.txt>
76pub(crate) const REQUEST_ID: HeaderName = HeaderName::from_static("request-id");
77
78pub(crate) const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id");
79
80/// Trait for producing [`RequestId`]s.
81///
82/// Used by [`SetRequestId`].
83pub trait MakeRequestId: Send + Sync + 'static {
84    /// Try and produce a [`RequestId`] from the request.
85    fn make_request_id<B>(&self, request: &Request<B>) -> Option<RequestId>;
86}
87
88/// An identifier for a request.
89#[derive(Debug, Clone, Extension)]
90#[extension(tags(http))]
91pub struct RequestId(HeaderValue);
92
93impl RequestId {
94    /// Create a new `RequestId` from a [`HeaderValue`].
95    pub const fn new(header_value: HeaderValue) -> Self {
96        Self(header_value)
97    }
98
99    /// Gets a reference to the underlying [`HeaderValue`].
100    pub fn header_value(&self) -> &HeaderValue {
101        &self.0
102    }
103
104    /// Consumes `self`, returning the underlying [`HeaderValue`].
105    pub fn into_header_value(self) -> HeaderValue {
106        self.0
107    }
108}
109
110impl From<HeaderValue> for RequestId {
111    fn from(value: HeaderValue) -> Self {
112        Self::new(value)
113    }
114}
115
116/// Set request id headers and extensions on requests.
117///
118/// This layer applies the [`SetRequestId`] middleware.
119///
120/// See the [module docs](self) and [`SetRequestId`] for more details.
121#[derive(Debug, Clone)]
122pub struct SetRequestIdLayer<M> {
123    header_name: HeaderName,
124    make_request_id: M,
125}
126
127impl<M> SetRequestIdLayer<M> {
128    /// Create a new `SetRequestIdLayer`.
129    pub const fn new(header_name: HeaderName, make_request_id: M) -> Self
130    where
131        M: MakeRequestId,
132    {
133        Self {
134            header_name,
135            make_request_id,
136        }
137    }
138
139    /// Create a new `SetRequestIdLayer` that uses `request-id` as the header name.
140    pub const fn request_id(make_request_id: M) -> Self
141    where
142        M: MakeRequestId,
143    {
144        Self::new(REQUEST_ID, make_request_id)
145    }
146
147    /// Create a new `SetRequestIdLayer` that uses `x-request-id` as the header name.
148    pub const fn x_request_id(make_request_id: M) -> Self
149    where
150        M: MakeRequestId,
151    {
152        Self::new(X_REQUEST_ID, make_request_id)
153    }
154}
155
156impl<S, M> Layer<S> for SetRequestIdLayer<M>
157where
158    M: Clone + MakeRequestId,
159{
160    type Service = SetRequestId<S, M>;
161
162    fn layer(&self, inner: S) -> Self::Service {
163        SetRequestId::new(
164            inner,
165            self.header_name.clone(),
166            self.make_request_id.clone(),
167        )
168    }
169
170    fn into_layer(self, inner: S) -> Self::Service {
171        SetRequestId::new(inner, self.header_name, self.make_request_id)
172    }
173}
174
175/// Set request id headers and extensions on requests.
176///
177/// See the [module docs](self) for an example.
178///
179/// If [`MakeRequestId::make_request_id`] returns `Some(_)` and the request doesn't already have a
180/// header with the same name, then the header will be inserted.
181///
182/// Additionally [`RequestId`] will be inserted into [`Request::extensions`] so other
183/// services can access it.
184#[derive(Debug, Clone)]
185pub struct SetRequestId<S, M> {
186    inner: S,
187    header_name: HeaderName,
188    make_request_id: M,
189}
190
191impl<S, M> SetRequestId<S, M> {
192    /// Create a new `SetRequestId`.
193    pub const fn new(inner: S, header_name: HeaderName, make_request_id: M) -> Self
194    where
195        M: MakeRequestId,
196    {
197        Self {
198            inner,
199            header_name,
200            make_request_id,
201        }
202    }
203
204    /// Create a new `SetRequestId` that uses `request-id` as the header name.
205    pub const fn request_id(inner: S, make_request_id: M) -> Self
206    where
207        M: MakeRequestId,
208    {
209        Self::new(inner, REQUEST_ID, make_request_id)
210    }
211
212    /// Create a new `SetRequestId` that uses `x-request-id` as the header name.
213    pub const fn x_request_id(inner: S, make_request_id: M) -> Self
214    where
215        M: MakeRequestId,
216    {
217        Self::new(inner, X_REQUEST_ID, make_request_id)
218    }
219
220    define_inner_service_accessors!();
221}
222
223impl<S, M, ReqBody, ResBody> Service<Request<ReqBody>> for SetRequestId<S, M>
224where
225    S: Service<Request<ReqBody>, Output = Response<ResBody>>,
226    M: MakeRequestId,
227    ReqBody: Send + 'static,
228    ResBody: Send + 'static,
229{
230    type Output = S::Output;
231    type Error = S::Error;
232
233    async fn serve(&self, mut req: Request<ReqBody>) -> Result<Self::Output, Self::Error> {
234        if let Some(request_id) = req.headers().get(&self.header_name) {
235            if req.extensions().get_ref::<RequestId>().is_none() {
236                let request_id = request_id.clone();
237                req.extensions().insert(RequestId::new(request_id));
238            }
239        } else if let Some(request_id) = self.make_request_id.make_request_id(&req) {
240            req.extensions().insert(request_id.clone());
241            req.headers_mut()
242                .insert(self.header_name.clone(), request_id.0);
243        }
244
245        self.inner.serve(req).await
246    }
247}
248
249/// Propagate request ids from requests to responses.
250///
251/// This layer applies the [`PropagateRequestId`] middleware.
252///
253/// See the [module docs](self) and [`PropagateRequestId`] for more details.
254#[derive(Debug, Clone)]
255pub struct PropagateRequestIdLayer {
256    header_name: HeaderName,
257}
258
259impl PropagateRequestIdLayer {
260    /// Create a new `PropagateRequestIdLayer`.
261    pub const fn new(header_name: HeaderName) -> Self {
262        Self { header_name }
263    }
264
265    /// Create a new `PropagateRequestIdLayer` that uses `request-id` as the header name.
266    pub const fn request_id() -> Self {
267        Self::new(REQUEST_ID)
268    }
269
270    /// Create a new `PropagateRequestIdLayer` that uses `x-request-id` as the header name.
271    pub const fn x_request_id() -> Self {
272        Self::new(X_REQUEST_ID)
273    }
274}
275
276impl<S> Layer<S> for PropagateRequestIdLayer {
277    type Service = PropagateRequestId<S>;
278
279    fn layer(&self, inner: S) -> Self::Service {
280        PropagateRequestId::new(inner, self.header_name.clone())
281    }
282}
283
284/// Propagate request ids from requests to responses.
285///
286/// See the [module docs](self) for an example.
287///
288/// If the request contains a matching header that header will be applied to responses. If a
289/// [`RequestId`] extension is also present it will be propagated as well.
290#[derive(Debug, Clone)]
291pub struct PropagateRequestId<S> {
292    inner: S,
293    header_name: HeaderName,
294}
295
296impl<S> PropagateRequestId<S> {
297    /// Create a new `PropagateRequestId`.
298    pub const fn new(inner: S, header_name: HeaderName) -> Self {
299        Self { inner, header_name }
300    }
301
302    /// Create a new `PropagateRequestId` that uses `request-id` as the header name.
303    pub const fn request_id(inner: S) -> Self {
304        Self::new(inner, REQUEST_ID)
305    }
306
307    /// Create a new `PropagateRequestId` that uses `x-request-id` as the header name.
308    pub const fn x_request_id(inner: S) -> Self {
309        Self::new(inner, X_REQUEST_ID)
310    }
311
312    define_inner_service_accessors!();
313}
314
315impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for PropagateRequestId<S>
316where
317    S: Service<Request<ReqBody>, Output = Response<ResBody>>,
318    ReqBody: Send + 'static,
319    ResBody: Send + 'static,
320{
321    type Output = S::Output;
322    type Error = S::Error;
323
324    async fn serve(&self, req: Request<ReqBody>) -> Result<Self::Output, Self::Error> {
325        let request_id = req
326            .headers()
327            .get(&self.header_name)
328            .cloned()
329            .map(RequestId::new);
330
331        let mut response = self.inner.serve(req).await?;
332
333        if let Some(current_id) = response.headers().get(&self.header_name) {
334            if response.extensions().get_ref::<RequestId>().is_none() {
335                let current_id = current_id.clone();
336                response.extensions().insert(RequestId::new(current_id));
337            }
338        } else if let Some(request_id) = request_id {
339            response
340                .headers_mut()
341                .insert(self.header_name.clone(), request_id.0.clone());
342            response.extensions().insert(request_id);
343        }
344
345        Ok(response)
346    }
347}
348
349/// A [`MakeRequestId`] that generates `UUID`s.
350#[derive(Debug, Clone, Copy, Default)]
351pub struct MakeRequestUuid;
352
353impl MakeRequestId for MakeRequestUuid {
354    fn make_request_id<B>(&self, _request: &Request<B>) -> Option<RequestId> {
355        let request_id = Uuid::new_v4()
356            .to_smolstr()
357            .parse()
358            .inspect_err(|err| {
359                tracing::debug!("failed to parse UUID4 as RequestId: {err}");
360            })
361            .ok()?;
362        Some(RequestId::new(request_id))
363    }
364}
365
366/// A [`MakeRequestId`] that generates `NanoID`s.
367#[derive(Debug, Clone, Copy, Default)]
368pub struct MakeRequestNanoid;
369
370impl MakeRequestId for MakeRequestNanoid {
371    fn make_request_id<B>(&self, _request: &Request<B>) -> Option<RequestId> {
372        let request_id = make_nano_id();
373        Some(RequestId::new(request_id))
374    }
375}
376
377fn make_nano_id() -> HeaderValue {
378    const ALPHABET_LEN: usize = 64;
379    const ALPHABET: [u8; ALPHABET_LEN] =
380        *b"_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
381    const ID_LEN: usize = 21;
382    const STEP: usize = 8 * ID_LEN / 5;
383    const MASK: usize = (ALPHABET_LEN * 2) - 1;
384
385    let mut id = [0u8; ID_LEN];
386
387    let mut index = 0;
388    loop {
389        // Draw fresh entropy on every pass. A single draw of STEP bytes yields
390        // ~16.5 accepted characters on average (each byte is accepted with
391        // probability ALPHABET_LEN / (MASK + 1) = 1/2), so roughly 95% of calls
392        // need a second pass. Re-using the same buffer would mirror the first
393        // pass into the tail of the id and slash effective entropy.
394        let input: [u8; STEP] = rand::rng().random();
395        for byte in input {
396            let byte = byte as usize & MASK;
397
398            if ALPHABET_LEN > byte {
399                id[index] = ALPHABET[byte];
400                index += 1;
401                if index == ID_LEN {
402                    return unsafe { HeaderValue::from_maybe_shared_unchecked(id) };
403                }
404            }
405        }
406    }
407}
408
409#[cfg(test)]
410mod tests {
411    use crate::layer::set_header;
412    use crate::{Body, Response};
413    use rama_core::Layer;
414    use rama_core::service::service_fn;
415    use std::{
416        convert::Infallible,
417        sync::{
418            Arc,
419            atomic::{AtomicU64, Ordering},
420        },
421    };
422
423    use super::*;
424
425    #[tokio::test]
426    async fn basic() {
427        let svc = (
428            SetRequestIdLayer::x_request_id(Counter::default()),
429            PropagateRequestIdLayer::x_request_id(),
430        )
431            .into_layer(service_fn(handler));
432
433        // header on response
434        let req = Request::builder().body(Body::empty()).unwrap();
435        let res = svc.serve(req).await.unwrap();
436        assert_eq!(res.headers()["x-request-id"], "0");
437
438        let req = Request::builder().body(Body::empty()).unwrap();
439        let res = svc.serve(req).await.unwrap();
440        assert_eq!(res.headers()["x-request-id"], "1");
441
442        // doesn't override if header is already there
443        let req = Request::builder()
444            .header("x-request-id", "foo")
445            .body(Body::empty())
446            .unwrap();
447        let res = svc.serve(req).await.unwrap();
448        assert_eq!(res.headers()["x-request-id"], "foo");
449
450        // extension propagated
451        let req = Request::builder().body(Body::empty()).unwrap();
452        let res = svc.serve(req).await.unwrap();
453        assert_eq!(res.extensions().get_ref::<RequestId>().unwrap().0, "2");
454    }
455
456    #[tokio::test]
457    async fn basic_with_request_id() {
458        let svc = (
459            SetRequestIdLayer::request_id(Counter::default()),
460            PropagateRequestIdLayer::request_id(),
461        )
462            .into_layer(service_fn(handler));
463
464        // header on response
465        let req = Request::builder().body(Body::empty()).unwrap();
466        let res = svc.serve(req).await.unwrap();
467        assert_eq!(res.headers()["request-id"], "0");
468
469        let req = Request::builder().body(Body::empty()).unwrap();
470        let res = svc.serve(req).await.unwrap();
471        assert_eq!(res.headers()["request-id"], "1");
472
473        // doesn't override if header is already there
474        let req = Request::builder()
475            .header("request-id", "foo")
476            .body(Body::empty())
477            .unwrap();
478        let res = svc.serve(req).await.unwrap();
479        assert_eq!(res.headers()["request-id"], "foo");
480
481        // extension propagated
482        let req = Request::builder().body(Body::empty()).unwrap();
483        let res = svc.serve(req).await.unwrap();
484        assert_eq!(res.extensions().get_ref::<RequestId>().unwrap().0, "2");
485    }
486
487    #[tokio::test]
488    async fn other_middleware_setting_request_id_on_response() {
489        let svc = (
490            SetRequestIdLayer::x_request_id(Counter::default()),
491            PropagateRequestIdLayer::x_request_id(),
492            set_header::SetResponseHeaderLayer::overriding(
493                HeaderName::from_static("x-request-id"),
494                HeaderValue::from_static("foo"),
495            ),
496        )
497            .into_layer(service_fn(handler));
498
499        let req = Request::builder()
500            .header("x-request-id", "foo")
501            .body(Body::empty())
502            .unwrap();
503        let res = svc.serve(req).await.unwrap();
504        assert_eq!(res.headers()["x-request-id"], "foo");
505        assert_eq!(res.extensions().get_ref::<RequestId>().unwrap().0, "foo");
506    }
507
508    #[derive(Clone, Default)]
509    struct Counter(Arc<AtomicU64>);
510
511    impl MakeRequestId for Counter {
512        fn make_request_id<B>(&self, _request: &Request<B>) -> Option<RequestId> {
513            let id =
514                HeaderValue::from_str(&self.0.fetch_add(1, Ordering::AcqRel).to_string()).unwrap();
515            Some(RequestId::new(id))
516        }
517    }
518
519    async fn handler(_: Request<Body>) -> Result<Response<Body>, Infallible> {
520        Ok(Response::new(Body::empty()))
521    }
522
523    #[tokio::test]
524    async fn uuid() {
525        let svc = (
526            SetRequestIdLayer::x_request_id(MakeRequestUuid),
527            PropagateRequestIdLayer::x_request_id(),
528        )
529            .into_layer(service_fn(handler));
530
531        // header on response
532        let req = Request::builder().body(Body::empty()).unwrap();
533        let mut res = svc.serve(req).await.unwrap();
534        let id = res.headers_mut().remove("x-request-id").unwrap();
535        id.to_str().unwrap().parse::<Uuid>().unwrap();
536    }
537
538    #[tokio::test]
539    async fn nanoid() {
540        let svc = (
541            SetRequestIdLayer::x_request_id(MakeRequestNanoid),
542            PropagateRequestIdLayer::x_request_id(),
543        )
544            .into_layer(service_fn(handler));
545
546        // header on response
547        let req = Request::builder().body(Body::empty()).unwrap();
548        let mut res = svc.serve(req).await.unwrap();
549        let id = res.headers_mut().remove("x-request-id").unwrap();
550        assert_eq!(id.to_str().unwrap().chars().count(), 21);
551    }
552
553    /// Regression test for the entropy bug where a single 33-byte RNG draw
554    /// was reused across mask-rejection passes, mirroring the first pass'
555    /// accepted bytes into the tail of the id. Under the bug, for ~50% of ids
556    /// there exists `k` in `[11..=16]` such that `id[k..21] == id[0..21-k]`
557    /// (a mirror tail of length 5..10). After the fix that probability is
558    /// bounded by ~6 * 64^-5 ≈ 10^-8 per id, so over 2000 samples we expect
559    /// effectively zero hits. The narrow `k` range avoids short-mirror
560    /// coincidences (e.g. `k = 20` would compare only 1 byte, 1/64 by chance).
561    #[test]
562    fn nanoid_no_intra_id_mirror() {
563        fn has_mirror(id: &[u8]) -> bool {
564            (11..=16).any(|k| id[k..21] == id[0..21 - k])
565        }
566
567        use ahash::HashSet;
568        let samples = 2_000;
569        let mut mirrors = 0;
570        let mut ids: HashSet<Vec<u8>> = HashSet::default();
571        for _ in 0..samples {
572            let hv = make_nano_id();
573            let id = hv.as_bytes();
574            assert_eq!(id.len(), 21);
575            if has_mirror(id) {
576                mirrors += 1;
577            }
578            ids.insert(id.to_vec());
579        }
580        // Under the bug, essentially every id mirrors; allow a small slack
581        // (chance of a spurious match across k values is bounded by
582        // sum_{k=11..20} 64^-(21-k) which is well under 1 in 4096).
583        assert!(
584            mirrors < 5,
585            "{mirrors}/{samples} ids exhibit the entropy-bug mirror pattern",
586        );
587        // All ids distinct (collision probability is negligible at this scale).
588        assert_eq!(ids.len(), samples, "duplicate ids generated");
589    }
590}