Skip to main content

actix_web/
request_data.rs

1use std::{any::type_name, ops::Deref};
2
3#[expect(deprecated, reason = "preserve the public ready future type")]
4use actix_utils::future::{ready, Ready};
5
6use crate::{
7    dev::Payload, error::ErrorInternalServerError, Error, FromRequest, HttpMessage as _,
8    HttpRequest,
9};
10
11/// Request-local data extractor.
12///
13/// Request-local data is arbitrary data attached to an individual request, usually
14/// by middleware. It can be set via `extensions_mut` on [`HttpRequest`][htr_ext_mut]
15/// or [`ServiceRequest`][srv_ext_mut].
16///
17/// Unlike app data, request data is dropped when the request has finished processing. This makes it
18/// useful as a kind of messaging system between middleware and request handlers. It uses the same
19/// types-as-keys storage system as app data.
20///
21/// # Mutating Request Data
22/// Note that since extractors must output owned data, only types that `impl Clone` can use this
23/// extractor. A clone is taken of the required request data and can, therefore, not be directly
24/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or
25/// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not
26/// provided to make this potential foot-gun more obvious.
27///
28/// # Examples
29/// ```no_run
30/// # use actix_web::{web, HttpResponse, HttpRequest, Responder, HttpMessage as _};
31/// #[derive(Debug, Clone, PartialEq)]
32/// struct FlagFromMiddleware(String);
33///
34/// /// Use the `ReqData<T>` extractor to access request data in a handler.
35/// async fn handler(
36///     req: HttpRequest,
37///     opt_flag: Option<web::ReqData<FlagFromMiddleware>>,
38/// ) -> impl Responder {
39///     // use an option extractor if middleware is not guaranteed to add this type of req data
40///     if let Some(flag) = opt_flag {
41///         assert_eq!(&flag.into_inner(), req.extensions().get::<FlagFromMiddleware>().unwrap());
42///     }
43///
44///     HttpResponse::Ok()
45/// }
46/// ```
47///
48/// [htr_ext_mut]: crate::HttpRequest::extensions_mut
49/// [srv_ext_mut]: crate::dev::ServiceRequest::extensions_mut
50#[derive(Debug, Clone)]
51pub struct ReqData<T: Clone + 'static>(T);
52
53impl<T: Clone + 'static> ReqData<T> {
54    /// Consumes the `ReqData`, returning its wrapped data.
55    pub fn into_inner(self) -> T {
56        self.0
57    }
58}
59
60impl<T: Clone + 'static> Deref for ReqData<T> {
61    type Target = T;
62
63    fn deref(&self) -> &T {
64        &self.0
65    }
66}
67
68#[expect(deprecated, reason = "preserve the public ready future type")]
69impl<T: Clone + 'static> FromRequest for ReqData<T> {
70    type Error = Error;
71    type Future = Ready<Result<Self, Error>>;
72
73    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
74        if let Some(st) = req.extensions().get::<T>() {
75            ready(Ok(ReqData(st.clone())))
76        } else {
77            log::debug!(
78                "Failed to construct App-level ReqData extractor. \
79                 Request path: {:?} (type: {})",
80                req.path(),
81                type_name::<T>(),
82            );
83            ready(Err(ErrorInternalServerError(
84                "Missing expected request extension data",
85            )))
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use std::{cell::RefCell, rc::Rc};
93
94    use futures_util::TryFutureExt as _;
95
96    use super::*;
97    use crate::{
98        dev::Service,
99        http::{Method, StatusCode},
100        test::{init_service, TestRequest},
101        web, App, HttpMessage, HttpResponse,
102    };
103
104    #[actix_rt::test]
105    async fn req_data_extractor() {
106        let srv = init_service(
107            App::new()
108                .wrap_fn(|req, srv| {
109                    if req.method() == Method::POST {
110                        req.extensions_mut().insert(42u32);
111                    }
112
113                    srv.call(req)
114                })
115                .service(web::resource("/test").to(
116                    |req: HttpRequest, data: Option<ReqData<u32>>| {
117                        if req.method() != Method::POST {
118                            assert!(data.is_none());
119                        }
120
121                        if let Some(data) = data {
122                            assert_eq!(*data, 42);
123                            assert_eq!(
124                                Some(data.into_inner()),
125                                req.extensions().get::<u32>().copied()
126                            );
127                        }
128
129                        HttpResponse::Ok()
130                    },
131                )),
132        )
133        .await;
134
135        let req = TestRequest::get().uri("/test").to_request();
136        let resp = srv.call(req).await.unwrap();
137        assert_eq!(resp.status(), StatusCode::OK);
138
139        let req = TestRequest::post().uri("/test").to_request();
140        let resp = srv.call(req).await.unwrap();
141        assert_eq!(resp.status(), StatusCode::OK);
142    }
143
144    #[actix_rt::test]
145    async fn req_data_internal_mutability() {
146        let srv = init_service(
147            App::new()
148                .wrap_fn(|req, srv| {
149                    let data_before = Rc::new(RefCell::new(42u32));
150                    req.extensions_mut().insert(data_before);
151
152                    srv.call(req).map_ok(|res| {
153                        {
154                            let ext = res.request().extensions();
155                            let data_after = ext.get::<Rc<RefCell<u32>>>().unwrap();
156                            assert_eq!(*data_after.borrow(), 53u32);
157                        }
158
159                        res
160                    })
161                })
162                .default_service(web::to(|data: ReqData<Rc<RefCell<u32>>>| {
163                    assert_eq!(*data.borrow(), 42);
164                    *data.borrow_mut() += 11;
165                    assert_eq!(*data.borrow(), 53);
166
167                    HttpResponse::Ok()
168                })),
169        )
170        .await;
171
172        let req = TestRequest::get().uri("/test").to_request();
173        let resp = srv.call(req).await.unwrap();
174        assert_eq!(resp.status(), StatusCode::OK);
175    }
176}