actix_web/
request_data.rs

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