use std::{any::type_name, ops::Deref}; use actix_utils::future::{err, ok, Ready}; use crate::{ dev::Payload, error::ErrorInternalServerError, Error, FromRequest, HttpMessage as _, HttpRequest, }; /// Request-local data extractor. /// /// Request-local data is arbitrary data attached to an individual request, usually /// by middleware. It can be set via `extensions_mut` on [`HttpRequest`][htr_ext_mut] /// or [`ServiceRequest`][srv_ext_mut]. /// /// Unlike app data, request data is dropped when the request has finished processing. This makes it /// useful as a kind of messaging system between middleware and request handlers. It uses the same /// types-as-keys storage system as app data. /// /// # Mutating Request Data /// Note that since extractors must output owned data, only types that `impl Clone` can use this /// extractor. A clone is taken of the required request data and can, therefore, not be directly /// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or /// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not /// provided to make this potential foot-gun more obvious. /// /// # Examples /// ```no_run /// # use actix_web::{web, HttpResponse, HttpRequest, Responder, HttpMessage as _}; /// /// #[derive(Debug, Clone, PartialEq)] /// struct FlagFromMiddleware(String); /// /// /// Use the `ReqData` extractor to access request data in a handler. /// async fn handler( /// req: HttpRequest, /// opt_flag: Option>, /// ) -> impl Responder { /// // use an option extractor if middleware is not guaranteed to add this type of req data /// if let Some(flag) = opt_flag { /// assert_eq!(&flag.into_inner(), req.extensions().get::().unwrap()); /// } /// /// HttpResponse::Ok() /// } /// ``` /// /// [htr_ext_mut]: crate::HttpRequest::extensions_mut /// [srv_ext_mut]: crate::dev::ServiceRequest::extensions_mut #[derive(Debug, Clone)] pub struct ReqData(T); impl ReqData { /// Consumes the `ReqData`, returning its wrapped data. pub fn into_inner(self) -> T { self.0 } } impl Deref for ReqData { type Target = T; fn deref(&self) -> &T { &self.0 } } impl FromRequest for ReqData { type Error = Error; type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { if let Some(st) = req.extensions().get::() { ok(ReqData(st.clone())) } else { log::debug!( "Failed to construct App-level ReqData extractor. \ Request path: {:?} (type: {})", req.path(), type_name::(), ); err(ErrorInternalServerError( "Missing expected request extension data", )) } } } #[cfg(test)] mod tests { use std::{cell::RefCell, rc::Rc}; use futures_util::TryFutureExt as _; use super::*; use crate::{ dev::Service, http::{Method, StatusCode}, test::{init_service, TestRequest}, web, App, HttpMessage, HttpResponse, }; #[actix_rt::test] async fn req_data_extractor() { let srv = init_service( App::new() .wrap_fn(|req, srv| { if req.method() == Method::POST { req.extensions_mut().insert(42u32); } srv.call(req) }) .service(web::resource("/test").to( |req: HttpRequest, data: Option>| { if req.method() != Method::POST { assert!(data.is_none()); } if let Some(data) = data { assert_eq!(*data, 42); assert_eq!( Some(data.into_inner()), req.extensions().get::().copied() ); } HttpResponse::Ok() }, )), ) .await; let req = TestRequest::get().uri("/test").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::post().uri("/test").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn req_data_internal_mutability() { let srv = init_service( App::new() .wrap_fn(|req, srv| { let data_before = Rc::new(RefCell::new(42u32)); req.extensions_mut().insert(data_before); srv.call(req).map_ok(|res| { { let ext = res.request().extensions(); let data_after = ext.get::>>().unwrap(); assert_eq!(*data_after.borrow(), 53u32); } res }) }) .default_service(web::to(|data: ReqData>>| { assert_eq!(*data.borrow(), 42); *data.borrow_mut() += 11; assert_eq!(*data.borrow(), 53); HttpResponse::Ok() })), ) .await; let req = TestRequest::get().uri("/test").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } }