1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2025-04-12 21:04:05 +00:00

Add DataRaw type.

This commit is contained in:
fakeshadow 2019-10-17 05:34:53 +08:00
parent ace98e3a1e
commit 29b6c77067
8 changed files with 302 additions and 43 deletions

View file

@ -12,7 +12,7 @@ use futures::{Future, IntoFuture};
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
use crate::config::{AppConfig, AppConfigInner, ServiceConfig};
use crate::data::{Data, DataFactory};
use crate::data::{AppData, Data, DataFactory, DataRaw};
use crate::dev::ResourceDef;
use crate::error::Error;
use crate::resource::Resource;
@ -104,6 +104,11 @@ where
self
}
pub fn data_raw<U: Send + Sync + Clone + 'static>(mut self, data: U) -> Self {
self.data.push(Box::new(DataRaw::new(data)));
self
}
/// Set application data factory. This function is
/// similar to `.data()` but it accepts data factory. Data object get
/// constructed asynchronously during application initialization.
@ -130,8 +135,8 @@ where
}
/// Set application data. Application data could be accessed
/// by using `Data<T>` extractor where `T` is data type.
pub fn register_data<U: 'static>(mut self, data: Data<U>) -> Self {
/// by using `Data<T>`or `DataRaw<T>` extractor where `T` is data type.
pub fn register_data<U: AppData + Clone + 'static>(mut self, data: U) -> Self {
self.data.push(Box::new(data));
self
}

View file

@ -5,7 +5,7 @@ use actix_http::Extensions;
use actix_router::ResourceDef;
use actix_service::{boxed, IntoNewService, NewService};
use crate::data::{Data, DataFactory};
use crate::data::{AppData, Data, DataFactory, DataRaw};
use crate::error::Error;
use crate::guard::Guard;
use crate::resource::Resource;
@ -197,6 +197,16 @@ impl ServiceConfig {
self
}
/// Set raw application data. Raw application data could be accessed
/// by using `DataRaw<T>` extractor where `T` is data type.
/// T must be `Send + Sync + Clone` in order to be register as `DataRaw`
///
/// This is same as `App::data_raw()` method.
pub fn data_raw<U: Send + Sync + Clone + 'static>(&mut self, data: U) -> &mut Self {
self.data.push(Box::new(DataRaw::new(data)));
self
}
/// Configure route for a specific path.
///
/// This is same as `App::route()` method.
@ -241,6 +251,8 @@ impl ServiceConfig {
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use actix_service::Service;
use bytes::Bytes;
@ -264,6 +276,21 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_data_raw() {
let cfg = |cfg: &mut ServiceConfig| {
cfg.data_raw(Arc::new(Mutex::new(10usize)));
};
let mut srv =
init_service(App::new().configure(cfg).service(
web::resource("/").to(|_: web::DataRaw<Arc<Mutex<usize>>>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
// #[test]
// fn test_data_factory() {
// let cfg = |cfg: &mut ServiceConfig| {

View file

@ -13,6 +13,21 @@ pub(crate) trait DataFactory {
fn create(&self, extensions: &mut Extensions) -> bool;
}
/// AppData is a trait generic over `Data<T>` and `DataRaw<T>`.
/// A type impl this trait can use `FromRequest` and `DataFactory` to extract and push data to extension
pub trait AppData {
type Data;
type Inner;
/// Create new `Data` instance.
fn new(state: Self::Data) -> Self;
/// Get reference to inner app data.
fn get_ref(&self) -> &Self::Data;
/// Convert to the internal Data.
fn into_inner(self) -> Self::Inner;
}
/// Application data.
///
/// Application data is an arbitrary data attached to the app.
@ -37,7 +52,7 @@ pub(crate) trait DataFactory {
///
/// ```rust
/// use std::sync::Mutex;
/// use actix_web::{web, App};
/// use actix_web::{web::{self, AppData}, App};
///
/// struct MyData {
/// counter: usize,
@ -63,27 +78,6 @@ pub(crate) trait DataFactory {
#[derive(Debug)]
pub struct Data<T>(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))
}
/// Get reference to inner app data.
pub fn get_ref(&self) -> &T {
self.0.as_ref()
}
/// Convert to the internal Arc<T>
pub fn into_inner(self) -> Arc<T> {
self.0
}
}
impl<T> Deref for Data<T> {
type Target = T;
@ -98,7 +92,99 @@ impl<T> Clone for Data<T> {
}
}
impl<T: 'static> FromRequest for Data<T> {
impl<T> AppData for Data<T> {
type Data = T;
type Inner = Arc<T>;
fn new(state: Self::Data) -> Self {
Data(Arc::new(state))
}
fn get_ref(&self) -> &Self::Data {
self.0.as_ref()
}
fn into_inner(self) -> Self::Inner {
self.0
}
}
/// Raw Application data.
///
/// Raw Application data shares the same principle of Application data.
/// The difference is Raw Application data explicitly require the data type to be `Send + Sync + Clone`.
///
/// This is useful when you introduce a foreign type(from other crates for example) that is already thread safe.
/// By using Raw Application data you can avoid the additional layer of `Arc` provided by `web::Data`
/// ```rust
/// use std::sync::{Arc, Mutex};
/// use actix_web::{web::{self, AppData}, App};
///
/// struct ForeignType {
/// inner: Arc<Mutex<usize>>
/// }
///
/// impl Clone for ForeignType {
/// fn clone(&self) -> Self {
/// ForeignType {
/// inner: self.inner.clone()
/// }
/// }
/// }
///
/// /// Use `DataRaw<T>` extractor to access data in handler.
/// fn index(data: web::DataRaw<ForeignType>) {
/// let mut data = data.inner.lock().unwrap();
/// *data += 1;
/// }
///
/// fn main() {
/// let data = ForeignType {
/// inner: Arc::new(Mutex::new(1usize))
/// };
///
/// let app = App::new()
/// // Store `ForeignTypeType` in application storage.
/// .data_raw(data.clone())
/// .service(
/// web::resource("/index.html").route(
/// web::get().to(index)));
/// }
/// ```
#[derive(Debug)]
pub struct DataRaw<T: Send + Sync + Clone>(T);
impl<T: Send + Sync + Clone> Clone for DataRaw<T> {
fn clone(&self) -> DataRaw<T> {
DataRaw(self.0.clone())
}
}
impl<T: Send + Sync + Clone> Deref for DataRaw<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> AppData for DataRaw<T>
where T: Send + Sync + Clone {
type Data = T;
type Inner = T;
fn new(state: Self::Data) -> Self {
DataRaw(state)
}
fn get_ref(&self) -> &Self::Data {
&*self
}
fn into_inner(self) -> Self::Inner {
self.0
}
}
impl<T: AppData + Clone + 'static> FromRequest for T {
type Config = ();
type Error = Error;
type Future = Result<Self, Error>;
@ -120,10 +206,10 @@ impl<T: 'static> FromRequest for Data<T> {
}
}
impl<T: 'static> DataFactory for Data<T> {
impl<T: AppData + Clone + 'static> DataFactory for T {
fn create(&self, extensions: &mut Extensions) -> bool {
if !extensions.contains::<Data<T>>() {
extensions.insert(Data(self.0.clone()));
if !extensions.contains::<T>() {
extensions.insert(self.clone());
true
} else {
false
@ -133,6 +219,9 @@ impl<T: 'static> DataFactory for Data<T> {
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, AtomicU32};
use actix_service::Service;
use super::*;
@ -223,4 +312,54 @@ mod tests {
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_data_raw_extractor() {
let mut srv =
init_service(App::new().data_raw(Arc::new(Mutex::new(1usize))).service(
web::resource("/").to(|_: web::DataRaw<Arc<Mutex<usize>>>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let mut srv =
init_service(App::new().data_raw(Arc::new(AtomicUsize::new(1))).service(
web::resource("/").to(|_: web::DataRaw<Arc<AtomicUsize>>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let mut srv =
init_service(App::new().data_raw(Arc::new(AtomicUsize::new(1))).service(
web::resource("/").to(|_: web::DataRaw<Arc<AtomicU32>>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[test]
fn test_register_data_raw_extractor() {
let mut srv =
init_service(App::new().register_data(DataRaw::new(Arc::new(AtomicUsize::new(1)))).service(
web::resource("/").to(|_: web::DataRaw<Arc<AtomicUsize>>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let mut srv =
init_service(App::new().register_data(DataRaw::new(Arc::new(AtomicUsize::new(1)))).service(
web::resource("/").to(|_: web::DataRaw<Arc<AtomicU32>>| HttpResponse::Ok()),
));
let req = TestRequest::default().to_request();
let resp = block_on(srv.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}

View file

@ -218,8 +218,8 @@ impl HttpRequest {
/// Get an application data stored with `App::data()` method during
/// application configuration.
pub fn get_app_data<T: 'static>(&self) -> Option<Data<T>> {
if let Some(st) = self.0.app_data.get::<Data<T>>() {
pub fn get_app_data<T: Clone + 'static>(&self) -> Option<T> {
if let Some(st) = self.0.app_data.get::<T>() {
Some(st.clone())
} else {
None

View file

@ -10,7 +10,7 @@ use actix_service::{
use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll};
use crate::data::Data;
use crate::data::{AppData, Data, DataRaw};
use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef};
use crate::extract::FromRequest;
use crate::guard::Guard;
@ -193,13 +193,19 @@ where
self.register_data(Data::new(data))
}
/// The same usage as `Resource::data` method.
/// The difference is the `U` must be `Send + Sync + Clone` in order to register as `DataRaw`
pub fn data_raw<U: Send + Sync + Clone + 'static>(self, data: U) -> Self {
self.register_data(DataRaw::new(data))
}
/// Set or override application data.
///
/// This method has the same effect as [`Resource::data`](#method.data),
/// except that instead of taking a value of some type `T`, it expects a
/// value of type `Data<T>`. Use a `Data<T>` extractor to retrieve its
/// value.
pub fn register_data<U: 'static>(mut self, data: Data<U>) -> Self {
/// value of type `Data<T>` or `DataRaw<T>`. Use a `Data<T>` or `DataRaw<T>`
/// extractor to retrieve its value.
pub fn register_data<U: AppData + 'static>(mut self, data: U) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());
}
@ -608,11 +614,13 @@ impl NewService for ResourceEndpoint {
#[cfg(test)]
mod tests {
use std::time::Duration;
use std::sync::{Arc, Mutex};
use actix_service::Service;
use futures::{Future, IntoFuture};
use tokio_timer::sleep;
use crate::data::AppData;
use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::service::{ServiceRequest, ServiceResponse};
use crate::test::{call_service, init_service, TestRequest};
@ -803,4 +811,34 @@ mod tests {
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_data_raw() {
let mut srv = init_service(
App::new()
.data_raw(Arc::new(Mutex::new(1.0f64)))
.data_raw(Arc::new(Mutex::new(1usize)))
.register_data(web::DataRaw::new(Arc::new(Mutex::new('-'))))
.service(
web::resource("/test")
.data_raw(Arc::new(Mutex::new(10usize)))
.register_data(web::DataRaw::new(Arc::new(Mutex::new('*'))))
.guard(guard::Get())
.to(
|data1: web::DataRaw<Arc<Mutex<usize>>>,
data2: web::DataRaw<Arc<Mutex<char>>>,
data3: web::DataRaw<Arc<Mutex<f64>>>| {
assert_eq!(*(data1.lock().unwrap()), 10);
assert_eq!(*(data2.lock().unwrap()), '*');
assert_eq!(*(data3.lock().unwrap()), 1.0);
HttpResponse::Ok()
},
),
),
);
let req = TestRequest::get().uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
}

View file

@ -12,7 +12,7 @@ use futures::future::{ok, Either, Future, FutureResult};
use futures::{Async, IntoFuture, Poll};
use crate::config::ServiceConfig;
use crate::data::Data;
use crate::data::{AppData, Data, DataRaw};
use crate::dev::{AppService, HttpServiceFactory};
use crate::error::Error;
use crate::guard::Guard;
@ -152,12 +152,19 @@ where
self.register_data(Data::new(data))
}
/// The same usage as `Resource::data` method.
/// The difference is the `U` must be `Send + Sync + Clone` in order to register as `DataRaw`
pub fn data_raw<U: Send + Sync + Clone + 'static>(self, data: U) -> Self {
self.register_data(DataRaw::new(data))
}
/// Set or override application data.
///
/// This method has the same effect as [`Scope::data`](#method.data), except
/// that instead of taking a value of some type `T`, it expects a value of
/// type `Data<T>`. Use a `Data<T>` extractor to retrieve its value.
pub fn register_data<U: 'static>(mut self, data: Data<U>) -> Self {
/// type `Data<T>` or `DataRaw<T>`. Use a `Data<T>` or `DataRaw<T>` extractor
/// to retrieve its value.
pub fn register_data<U: AppData + 'static>(mut self, data: U) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());
}
@ -655,10 +662,13 @@ impl NewService for ScopeEndpoint {
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use actix_service::Service;
use bytes::Bytes;
use futures::{Future, IntoFuture};
use crate::data::AppData;
use crate::dev::{Body, ResponseBody};
use crate::http::{header, HeaderValue, Method, StatusCode};
use crate::service::{ServiceRequest, ServiceResponse};
@ -1113,6 +1123,46 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_override_data_raw() {
let mut srv = init_service(App::new().data_raw(Arc::new(Mutex::new(1usize))).service(
web::scope("app").data_raw(Arc::new(Mutex::new(10usize))).route(
"/t",
web::get().to(|data: web::DataRaw<Arc<Mutex<usize>>>| {
assert_eq!(*(data.lock().unwrap()), 10);
let _ = data.clone();
HttpResponse::Ok()
}),
),
));
let req = TestRequest::with_uri("/app/t").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_override_register_data_raw() {
let mut srv = init_service(
App::new().register_data(web::DataRaw::new(Arc::new(Mutex::new(1usize)))).service(
web::scope("app")
.register_data(web::DataRaw::new(Arc::new(Mutex::new(10usize))))
.route(
"/t",
web::get().to(|data: web::DataRaw<Arc<Mutex<usize>>>| {
assert_eq!(*(data.lock().unwrap()), 10);
let _ = data.clone();
HttpResponse::Ok()
}),
),
),
);
let req = TestRequest::with_uri("/app/t").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_scope_config() {
let mut srv =

View file

@ -19,7 +19,7 @@ pub use actix_http::test::TestBuffer;
pub use actix_testing::{block_fn, block_on, run_on};
use crate::config::{AppConfig, AppConfigInner};
use crate::data::Data;
use crate::data::{AppData, Data};
use crate::dev::{Body, MessageBody, Payload};
use crate::request::HttpRequestPool;
use crate::rmap::ResourceMap;
@ -521,8 +521,8 @@ mod tests {
assert!(req.headers().contains_key(header::DATE));
assert_eq!(&req.match_info()["test"], "123");
assert_eq!(req.version(), Version::HTTP_2);
let data = req.get_app_data::<u32>().unwrap();
assert!(req.get_app_data::<u64>().is_none());
let data = req.get_app_data::<web::Data<u32>>().unwrap();
assert!(req.get_app_data::<web::Data<u64>>().is_none());
assert_eq!(*data, 10);
assert_eq!(*data.get_ref(), 10);

View file

@ -15,7 +15,7 @@ use crate::scope::Scope;
use crate::service::WebService;
pub use crate::config::ServiceConfig;
pub use crate::data::Data;
pub use crate::data::{AppData, Data, DataRaw};
pub use crate::request::HttpRequest;
pub use crate::types::*;