From 29b6c7706774654fe212277b4578f6d3b076e1f2 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 17 Oct 2019 05:34:53 +0800 Subject: [PATCH] Add DataRaw type. --- src/app.rs | 11 ++- src/config.rs | 29 +++++++- src/data.rs | 191 +++++++++++++++++++++++++++++++++++++++++------- src/request.rs | 4 +- src/resource.rs | 46 +++++++++++- src/scope.rs | 56 +++++++++++++- src/test.rs | 6 +- src/web.rs | 2 +- 8 files changed, 302 insertions(+), 43 deletions(-) diff --git a/src/app.rs b/src/app.rs index f93859c7e..4b54d3eb9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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(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` extractor where `T` is data type. - pub fn register_data(mut self, data: Data) -> Self { + /// by using `Data`or `DataRaw` extractor where `T` is data type. + pub fn register_data(mut self, data: U) -> Self { self.data.push(Box::new(data)); self } diff --git a/src/config.rs b/src/config.rs index 63fd31d27..ed6a29a82 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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` 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(&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>>| 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| { diff --git a/src/data.rs b/src/data.rs index 14e293bc2..725575a2c 100644 --- a/src/data.rs +++ b/src/data.rs @@ -13,6 +13,21 @@ pub(crate) trait DataFactory { fn create(&self, extensions: &mut Extensions) -> bool; } +/// AppData is a trait generic over `Data` and `DataRaw`. +/// 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(Arc); -impl Data { - /// 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 { - 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 - pub fn into_inner(self) -> Arc { - self.0 - } -} - impl Deref for Data { type Target = T; @@ -98,7 +92,99 @@ impl Clone for Data { } } -impl FromRequest for Data { +impl AppData for Data { + type Data = T; + type Inner = Arc; + 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> +/// } +/// +/// impl Clone for ForeignType { +/// fn clone(&self) -> Self { +/// ForeignType { +/// inner: self.inner.clone() +/// } +/// } +/// } +/// +/// /// Use `DataRaw` extractor to access data in handler. +/// fn index(data: web::DataRaw) { +/// 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); + +impl Clone for DataRaw { + fn clone(&self) -> DataRaw { + DataRaw(self.0.clone()) + } +} + +impl Deref for DataRaw { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl AppData for DataRaw + 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 FromRequest for T { type Config = (); type Error = Error; type Future = Result; @@ -120,10 +206,10 @@ impl FromRequest for Data { } } -impl DataFactory for Data { +impl DataFactory for T { fn create(&self, extensions: &mut Extensions) -> bool { - if !extensions.contains::>() { - extensions.insert(Data(self.0.clone())); + if !extensions.contains::() { + extensions.insert(self.clone()); true } else { false @@ -133,6 +219,9 @@ impl DataFactory for Data { #[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>>| 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>| 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>| 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>| 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>| HttpResponse::Ok()), + )); + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } + } diff --git a/src/request.rs b/src/request.rs index ea27e303c..bdc04c3da 100644 --- a/src/request.rs +++ b/src/request.rs @@ -218,8 +218,8 @@ impl HttpRequest { /// Get an application data stored with `App::data()` method during /// application configuration. - pub fn get_app_data(&self) -> Option> { - if let Some(st) = self.0.app_data.get::>() { + pub fn get_app_data(&self) -> Option { + if let Some(st) = self.0.app_data.get::() { Some(st.clone()) } else { None diff --git a/src/resource.rs b/src/resource.rs index 3ee0167a0..7dc441f5d 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -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(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`. Use a `Data` extractor to retrieve its - /// value. - pub fn register_data(mut self, data: Data) -> Self { + /// value of type `Data` or `DataRaw`. Use a `Data` or `DataRaw` + /// extractor to retrieve its value. + pub fn register_data(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>>, + data2: web::DataRaw>>, + data3: web::DataRaw>>| { + 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); + } } diff --git a/src/scope.rs b/src/scope.rs index c152bc334..a2a3953f1 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -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(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`. Use a `Data` extractor to retrieve its value. - pub fn register_data(mut self, data: Data) -> Self { + /// type `Data` or `DataRaw`. Use a `Data` or `DataRaw` extractor + /// to retrieve its value. + pub fn register_data(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>>| { + 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>>| { + 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 = diff --git a/src/test.rs b/src/test.rs index 6563253cc..32b3b3304 100644 --- a/src/test.rs +++ b/src/test.rs @@ -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::().unwrap(); - assert!(req.get_app_data::().is_none()); + let data = req.get_app_data::>().unwrap(); + assert!(req.get_app_data::>().is_none()); assert_eq!(*data, 10); assert_eq!(*data.get_ref(), 10); diff --git a/src/web.rs b/src/web.rs index 5669a1e86..80b2902af 100644 --- a/src/web.rs +++ b/src/web.rs @@ -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::*;