actix_web_middleware_requestid/
lib.rs

1//! # actix-web-middleware-requestid
2//!
3//! Request ID middleware for the actix-web framework v3.0+. Adds a custom header with a unique token to every request.
4//!
5//! # Usage
6//!
7//! Add the package to Cargo.toml:
8//!
9//! ```toml
10//! [dependencies]
11//! actix-web-middleware-requestid = "3.0"
12//! ```
13//!
14//! Import and add middleware to your server definition:
15//!
16//! ```rust
17//! use actix_web_middleware_requestid::RequestID;
18//!
19//! ...
20//!
21//! App::new()
22//!     ...
23//!     .wrap(RequestID)
24//! ```
25//!
26//! For actix-web v1.x use version "1.0" of the same package. The usage pattern and all exported names remain the same.
27//!
28//! # For actix-web < 1.0
29//!
30//! Consider using a similar crate [actix-web-requestid](https://crates.io/crates/actix-web-requestid)
31//!
32
33use std::task::{Context, Poll};
34
35use actix_service::{Service, Transform};
36use actix_web::error::ErrorBadRequest;
37use actix_web::http::header::{HeaderName, HeaderValue};
38use actix_web::Result;
39use actix_web::{dev, Error, FromRequest, HttpMessage, HttpRequest};
40use actix_web::{dev::ServiceRequest, dev::ServiceResponse};
41use futures::future::{err, ok, Ready};
42
43/// The header set by the middleware
44pub const REQUEST_ID_HEADER: &str = "x-request-id";
45
46/// Request ID wrapper.
47pub struct RequestIDWrapper;
48
49impl<S, B> Transform<S> for RequestIDWrapper
50where
51    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
52    S::Future: 'static,
53    B: 'static,
54{
55    type Request = ServiceRequest;
56    type Response = ServiceResponse<B>;
57    type Error = Error;
58    type InitError = ();
59    type Transform = RequestIDMiddleware<S>;
60    type Future = Ready<Result<Self::Transform, Self::InitError>>;
61
62    fn new_transform(&self, service: S) -> Self::Future {
63        ok(RequestIDMiddleware { service })
64    }
65}
66
67/// Actual actix-web middleware
68pub struct RequestIDMiddleware<S> {
69    service: S,
70}
71
72impl<S, B> Service for RequestIDMiddleware<S>
73where
74    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
75    S::Future: 'static,
76    B: 'static,
77{
78    type Request = ServiceRequest;
79    type Response = ServiceResponse<B>;
80    type Error = Error;
81    type Future = S::Future;
82
83    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
84        self.service.poll_ready(cx)
85    }
86
87    fn call(&mut self, req: ServiceRequest) -> Self::Future {
88        use rand::{distributions::Alphanumeric, thread_rng, Rng};
89
90        // generate request id token
91        let request_id: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
92
93        // make object mutable (required as the header must be used inside `.call`)
94        let mut req = req;
95
96        // add request id header (for using in the log wrapper)
97        req.headers_mut().append(
98            HeaderName::from_static(REQUEST_ID_HEADER),
99            HeaderValue::from_str(&request_id).unwrap(),
100        );
101
102        // add request id extension (for extractor)
103        req.extensions_mut().insert(RequestID(request_id));
104
105        // propagate the call
106        self.service.call(req)
107    }
108}
109
110/// Request ID extractor
111pub struct RequestID(pub String);
112
113impl FromRequest for RequestID {
114    type Error = Error;
115    type Future = Ready<Result<Self, Self::Error>>;
116    type Config = ();
117
118    fn from_request(req: &HttpRequest, _payload: &mut dev::Payload) -> Self::Future {
119        if let Some(RequestID(req_id)) = req.extensions().get::<RequestID>() {
120            ok(RequestID(req_id.clone()))
121        } else {
122            err(ErrorBadRequest("request id is missing"))
123        }
124    }
125}