1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
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<T>` extractor to access request data in a handler.
/// async fn handler(
/// req: HttpRequest,
/// opt_flag: Option<web::ReqData<FlagFromMiddleware>>,
/// ) -> 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::<FlagFromMiddleware>().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: Clone + 'static>(T);
impl<T: Clone + 'static> ReqData<T> {
/// Consumes the `ReqData`, returning its wrapped data.
pub fn into_inner(self) -> T {
self.0
}
}
impl<T: Clone + 'static> Deref for ReqData<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T: Clone + 'static> FromRequest for ReqData<T> {
type Error = Error;
type Future = Ready<Result<Self, Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
if let Some(st) = req.extensions().get::<T>() {
ok(ReqData(st.clone()))
} else {
log::debug!(
"Failed to construct App-level ReqData extractor. \
Request path: {:?} (type: {})",
req.path(),
type_name::<T>(),
);
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<ReqData<u32>>| {
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::<u32>().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::<Rc<RefCell<u32>>>().unwrap();
assert_eq!(*data_after.borrow(), 53u32);
}
res
})
})
.default_service(web::to(|data: ReqData<Rc<RefCell<u32>>>| {
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);
}
}