Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add web::ReqData<T> extractor #1748

Merged
merged 7 commits into from
Oct 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# Changes

## Unreleased - 2020-xx-xx
* Implement Logger middleware regex exclude pattern [#1723]
* Print unconfigured `Data<T>` type when attempting extraction. [#1743]
### Added
* Implement `exclude_regex` for Logger middleware. [#1723]
* Add request-local data extractor `web::ReqData`. [#1748]

### Changed
* Print non-configured `Data<T>` type when attempting extraction. [#1743]

[#1723]: https://github.com/actix/actix-web/pull/1723
[#1743]: https://github.com/actix/actix-web/pull/1743
[#1748]: https://github.com/actix/actix-web/pull/1748


## 3.1.0 - 2020-09-29
### Changed
Expand Down
33 changes: 12 additions & 21 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,20 @@ pub(crate) type FnDataFactory =

/// Application data.
///
/// Application data is an arbitrary data attached to the app.
/// Application data is available to all routes and could be added
/// during application configuration process
/// with `App::data()` method.
/// Application level data is a piece of arbitrary data attached to the app, scope, or resource.
/// Application data is available to all routes and can be added during the application
/// configuration process via `App::data()`.
///
/// Application data could be accessed by using `Data<T>`
/// extractor where `T` is data type.
/// Application data can be accessed by using `Data<T>` extractor where `T` is data type.
///
/// **Note**: http server accepts an application factory rather than
/// an application instance. Http server constructs an application
/// instance for each thread, thus application data must be constructed
/// multiple times. If you want to share data between different
/// threads, a shareable object should be used, e.g. `Send + Sync`. Application
/// data does not need to be `Send` or `Sync`. Internally `Data` type
/// uses `Arc`. if your data implements `Send` + `Sync` traits you can
/// use `web::Data::new()` and avoid double `Arc`.
/// **Note**: http server accepts an application factory rather than an application instance. HTTP
/// server constructs an application instance for each thread, thus application data must be
/// constructed multiple times. If you want to share data between different threads, a shareable
/// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send`
/// or `Sync`. Internally `Data` uses `Arc`.
///
/// If route data is not set for a handler, using `Data<T>` extractor would
/// cause *Internal Server Error* response.
/// If route data is not set for a handler, using `Data<T>` extractor would cause *Internal
/// Server Error* response.
///
/// ```rust
/// use std::sync::Mutex;
Expand All @@ -48,7 +43,7 @@ pub(crate) type FnDataFactory =
/// counter: usize,
/// }
///
/// /// Use `Data<T>` extractor to access data in handler.
/// /// Use the `Data<T>` extractor to access data in a handler.
/// async fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
/// let mut data = data.lock().unwrap();
/// data.counter += 1;
Expand All @@ -71,10 +66,6 @@ pub struct Data<T: ?Sized>(Arc<T>);

impl<T> Data<T> {
/// Create new `Data` instance.
///
/// Internally `Data` type uses `Arc`. if your data implements
/// `Send` + `Sync` traits you can use `web::Data::new()` and
/// avoid double `Arc`.
pub fn new(state: T) -> Data<T> {
Data(Arc::new(state))
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ mod handler;
mod info;
pub mod middleware;
mod request;
mod request_data;
mod resource;
mod responder;
mod rmap;
Expand Down
175 changes: 175 additions & 0 deletions src/request_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use std::{any::type_name, ops::Deref};

use actix_http::error::{Error, ErrorInternalServerError};
use futures_util::future;

use crate::{dev::Payload, FromRequest, 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.
///
/// # Example
/// ```rust,no_run
/// # use actix_web::{web, HttpResponse, HttpRequest, Responder};
///
/// #[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 optional extractor if the middleware is
/// // not guaranteed to add this type of requests 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 it's 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 Config = ();
type Error = Error;
type Future = future::Ready<Result<Self, Error>>;

fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
if let Some(st) = req.extensions().get::<T>() {
future::ok(ReqData(st.clone()))
} else {
log::debug!(
"Failed to construct App-level ReqData extractor. \
Request path: {:?} (type: {})",
req.path(),
type_name::<T>(),
);
future::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 mut 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 mut 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);
}
}
1 change: 1 addition & 0 deletions src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use crate::service::WebService;
pub use crate::config::ServiceConfig;
pub use crate::data::Data;
pub use crate::request::HttpRequest;
pub use crate::request_data::ReqData;
pub use crate::types::*;

/// Create resource for a specific path.
Expand Down