From 60386f1791e6bd005889d566eba1ba0b76699401 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 16 Mar 2019 21:09:11 -0700 Subject: [PATCH] introduce RouteData extractor --- examples/basic.rs | 4 +- src/app.rs | 20 ----- src/data.rs | 182 ++++++++++++++++++++++++++++++++++++++++- src/extract/form.rs | 2 +- src/extract/json.rs | 2 +- src/extract/payload.rs | 4 +- src/lib.rs | 2 +- src/route.rs | 3 +- src/service.rs | 16 ++-- 9 files changed, 198 insertions(+), 37 deletions(-) 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 { 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| 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| 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` +/// 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, +/// } +/// +/// /// Use `Data` extractor to access data in handler. +/// fn index(data: web::Data) { +/// 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(Arc); impl Data { @@ -25,7 +63,7 @@ impl Data { 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 FromRequest

for Data { 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` extractor. +/// +/// ```rust +/// # use std::cell::Cell; +/// use actix_web::{web, App}; +/// +/// struct MyData { +/// counter: Cell, +/// } +/// +/// /// Use `RouteData` extractor to access data in handler. +/// fn index(data: web::RouteData) { +/// 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(Arc); + +impl RouteData { + pub(crate) fn new(state: T) -> RouteData { + RouteData(Arc::new(state)) + } + + /// Get referecnce to inner data object. + pub fn get_ref(&self) -> &T { + self.0.as_ref() + } +} + +impl Deref for RouteData { + type Target = T; + + fn deref(&self) -> &T { + self.0.as_ref() + } +} + +impl Clone for RouteData { + fn clone(&self) -> RouteData { + RouteData(self.0.clone()) + } +} + +impl FromRequest

for RouteData { + type Error = Error; + type Future = Result; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + if let Some(st) = req.route_data::() { + 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| 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| 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| { + 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| 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

) -> Self::Future { let req2 = req.clone(); let (limit, err) = req - .load_config::() + .route_data::() .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

) -> Self::Future { let req2 = req.clone(); let (limit, err) = req - .load_config::() + .route_data::() .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

) -> Self::Future { let mut tmp; - let cfg = if let Some(cfg) = req.load_config::() { + let cfg = if let Some(cfg) = req.route_data::() { cfg } else { tmp = PayloadConfig::default(); @@ -193,7 +193,7 @@ where #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { let mut tmp; - let cfg = if let Some(cfg) = req.load_config::() { + let cfg = if let Some(cfg) = req.route_data::() { 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 Route

{ 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

fmt::Debug for ServiceRequest

{ pub struct ServiceFromRequest

{ req: HttpRequest, payload: Payload

, - config: Option>, + data: Option>, } impl

ServiceFromRequest

{ - pub(crate) fn new(req: ServiceRequest

, config: Option>) -> Self { + pub(crate) fn new(req: ServiceRequest

, data: Option>) -> Self { Self { req: req.req, payload: req.payload, - config, + data, } } @@ -269,10 +270,11 @@ impl

ServiceFromRequest

{ ServiceResponse::new(self.req, err.into().into()) } - /// Load extractor configuration - pub fn load_config(&self) -> Option<&T> { - if let Some(ref ext) = self.config { - ext.get::() + /// Load route data. Route data could be set during + /// route configuration with `Route::data()` method. + pub fn route_data(&self) -> Option<&RouteData> { + if let Some(ref ext) = self.data { + ext.get::>() } else { None }