diff --git a/examples/basic.rs b/examples/basic.rs index e8591f77..756f1b79 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -1,6 +1,8 @@ use futures::IntoFuture; -use actix_web::{get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; +use actix_web::{ + get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, +}; #[get("/resource1/{name}/index.html")] fn index(req: HttpRequest, name: web::Path<String>) -> String { diff --git a/src/app.rs b/src/app.rs index b146fb4c..8c416808 100644 --- a/src/app.rs +++ b/src/app.rs @@ -487,26 +487,6 @@ mod tests { assert_eq!(resp.status(), StatusCode::CREATED); } - #[test] - fn test_data() { - let mut srv = - init_service(App::new().data(10usize).service( - web::resource("/").to(|_: web::Data<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(10u32).service( - web::resource("/").to(|_: web::Data<usize>| 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_data_factory() { let mut srv = diff --git a/src/data.rs b/src/data.rs index a172cb35..6fb8e0b9 100644 --- a/src/data.rs +++ b/src/data.rs @@ -17,7 +17,45 @@ pub(crate) trait DataFactoryResult { fn poll_result(&mut self, extensions: &mut Extensions) -> Poll<(), ()>; } -/// Application state +/// 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. +/// +/// Applicatin data could 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 shared object should be used, e.g. `Arc`. Application +/// data does not need to be `Send` or `Sync`. +/// +/// ```rust +/// use std::cell::Cell; +/// use actix_web::{web, App}; +/// +/// struct MyData { +/// counter: Cell<usize>, +/// } +/// +/// /// Use `Data<T>` extractor to access data in handler. +/// fn index(data: web::Data<MyData>) { +/// data.counter.set(data.counter.get() + 1); +/// } +/// +/// fn main() { +/// let app = App::new() +/// // Store `MyData` in application storage. +/// .data(MyData{ counter: Cell::new(0) }) +/// .service( +/// web::resource("/index.html").route( +/// web::get().to(index))); +/// } +/// ``` pub struct Data<T>(Arc<T>); impl<T> Data<T> { @@ -25,7 +63,7 @@ impl<T> Data<T> { Data(Arc::new(state)) } - /// Get referecnce to inner state type. + /// Get referecnce to inner app data. pub fn get_ref(&self) -> &T { self.0.as_ref() } @@ -55,7 +93,7 @@ impl<T: 'static, P> FromRequest<P> for Data<T> { Ok(st.clone()) } else { Err(ErrorInternalServerError( - "State is not configured, to configure use App::state()", + "App data is not configured, to configure use App::data()", )) } } @@ -118,3 +156,141 @@ where } } } + +/// Route data. +/// +/// Route data is an arbitrary data attached to specific route. +/// Route data could be added to route during route configuration process +/// with `Route::data()` method. Route data is also used as an extractor +/// configuration storage. Route data could be accessed in handler +/// via `RouteData<T>` extractor. +/// +/// ```rust +/// # use std::cell::Cell; +/// use actix_web::{web, App}; +/// +/// struct MyData { +/// counter: Cell<usize>, +/// } +/// +/// /// Use `RouteData<T>` extractor to access data in handler. +/// fn index(data: web::RouteData<MyData>) { +/// data.counter.set(data.counter.get() + 1); +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get() +/// // Store `MyData` in route storage +/// .data(MyData{ counter: Cell::new(0) }) +/// // Route data could be used as extractor configuration storage, +/// // limit size of the payload +/// .data(web::PayloadConfig::new(4096)) +/// // register handler +/// .to(index) +/// )); +/// } +/// ``` +/// +/// If route data is not set for a handler, using `RouteData` extractor +/// would cause `Internal Server error` response. +pub struct RouteData<T>(Arc<T>); + +impl<T> RouteData<T> { + pub(crate) fn new(state: T) -> RouteData<T> { + RouteData(Arc::new(state)) + } + + /// Get referecnce to inner data object. + pub fn get_ref(&self) -> &T { + self.0.as_ref() + } +} + +impl<T> Deref for RouteData<T> { + type Target = T; + + fn deref(&self) -> &T { + self.0.as_ref() + } +} + +impl<T> Clone for RouteData<T> { + fn clone(&self) -> RouteData<T> { + RouteData(self.0.clone()) + } +} + +impl<T: 'static, P> FromRequest<P> for RouteData<T> { + type Error = Error; + type Future = Result<Self, Error>; + + #[inline] + fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future { + if let Some(st) = req.route_data::<T>() { + Ok(st.clone()) + } else { + Err(ErrorInternalServerError( + "Route data is not configured, to configure use Route::data()", + )) + } + } +} + +#[cfg(test)] +mod tests { + use actix_service::Service; + + use crate::http::StatusCode; + use crate::test::{block_on, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[test] + fn test_data_extractor() { + let mut srv = + init_service(App::new().data(10usize).service( + web::resource("/").to(|_: web::Data<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(10u32).service( + web::resource("/").to(|_: web::Data<usize>| 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_route_data_extractor() { + let mut srv = init_service(App::new().service(web::resource("/").route( + web::get().data(10usize).to(|data: web::RouteData<usize>| { + let _ = data.clone(); + HttpResponse::Ok() + }), + ))); + + let req = TestRequest::default().to_request(); + let resp = block_on(srv.call(req)).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + // different type + let mut srv = init_service( + App::new().service( + web::resource("/").route( + web::get() + .data(10u32) + .to(|_: web::RouteData<usize>| 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/extract/form.rs b/src/extract/form.rs index 6b13c5f8..4a5e9729 100644 --- a/src/extract/form.rs +++ b/src/extract/form.rs @@ -77,7 +77,7 @@ where fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future { let req2 = req.clone(); let (limit, err) = req - .load_config::<FormConfig>() + .route_data::<FormConfig>() .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((16384, None)); diff --git a/src/extract/json.rs b/src/extract/json.rs index 3847e71a..92b7f20f 100644 --- a/src/extract/json.rs +++ b/src/extract/json.rs @@ -177,7 +177,7 @@ where fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future { let req2 = req.clone(); let (limit, err) = req - .load_config::<JsonConfig>() + .route_data::<JsonConfig>() .map(|c| (c.limit, c.ehandler.clone())) .unwrap_or((32768, None)); diff --git a/src/extract/payload.rs b/src/extract/payload.rs index 3fc0c964..7164a544 100644 --- a/src/extract/payload.rs +++ b/src/extract/payload.rs @@ -140,7 +140,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future { let mut tmp; - let cfg = if let Some(cfg) = req.load_config::<PayloadConfig>() { + let cfg = if let Some(cfg) = req.route_data::<PayloadConfig>() { cfg } else { tmp = PayloadConfig::default(); @@ -193,7 +193,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future { let mut tmp; - let cfg = if let Some(cfg) = req.load_config::<PayloadConfig>() { + let cfg = if let Some(cfg) = req.route_data::<PayloadConfig>() { cfg } else { tmp = PayloadConfig::default(); diff --git a/src/lib.rs b/src/lib.rs index 843ad103..b22e05da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,7 +91,7 @@ pub mod web { use crate::route::Route; use crate::scope::Scope; - pub use crate::data::Data; + pub use crate::data::{Data, RouteData}; pub use crate::error::{BlockingError, Error}; pub use crate::extract::{Form, Json, Path, Payload, Query}; pub use crate::extract::{FormConfig, JsonConfig, PayloadConfig}; diff --git a/src/route.rs b/src/route.rs index 30905d40..c44ed713 100644 --- a/src/route.rs +++ b/src/route.rs @@ -6,6 +6,7 @@ use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; use futures::{Async, Future, IntoFuture, Poll}; +use crate::data::RouteData; use crate::extract::FromRequest; use crate::guard::{self, Guard}; use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; @@ -309,7 +310,7 @@ impl<P: 'static> Route<P> { if self.data.is_none() { self.data = Some(Extensions::new()); } - self.data.as_mut().unwrap().insert(data); + self.data.as_mut().unwrap().insert(RouteData::new(data)); self } } diff --git a/src/service.rs b/src/service.rs index e907a1ab..b8c3a158 100644 --- a/src/service.rs +++ b/src/service.rs @@ -13,6 +13,7 @@ use actix_router::{Path, Resource, Url}; use futures::future::{ok, FutureResult, IntoFuture}; use crate::config::{AppConfig, ServiceConfig}; +use crate::data::RouteData; use crate::request::HttpRequest; use crate::rmap::ResourceMap; @@ -241,15 +242,15 @@ impl<P> fmt::Debug for ServiceRequest<P> { pub struct ServiceFromRequest<P> { req: HttpRequest, payload: Payload<P>, - config: Option<Rc<Extensions>>, + data: Option<Rc<Extensions>>, } impl<P> ServiceFromRequest<P> { - pub(crate) fn new(req: ServiceRequest<P>, config: Option<Rc<Extensions>>) -> Self { + pub(crate) fn new(req: ServiceRequest<P>, data: Option<Rc<Extensions>>) -> Self { Self { req: req.req, payload: req.payload, - config, + data, } } @@ -269,10 +270,11 @@ impl<P> ServiceFromRequest<P> { ServiceResponse::new(self.req, err.into().into()) } - /// Load extractor configuration - pub fn load_config<T: 'static>(&self) -> Option<&T> { - if let Some(ref ext) = self.config { - ext.get::<T>() + /// Load route data. Route data could be set during + /// route configuration with `Route::data()` method. + pub fn route_data<T: 'static>(&self) -> Option<&RouteData<T>> { + if let Some(ref ext) = self.data { + ext.get::<RouteData<T>>() } else { None }