From 6df85e32dfa2ef7c2c3d587c65c8a3602406e772 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 3 Mar 2019 00:57:48 -0800 Subject: [PATCH] added extractor configuration system --- src/extractor.rs | 83 ++++++++++++++++++++++++++++++++++++++---------- src/handler.rs | 58 ++++++++++++++++++++++++++------- src/lib.rs | 4 +-- src/request.rs | 1 + src/resource.rs | 2 +- src/route.rs | 66 ++++++++++++++++++++++++++++++++++---- src/service.rs | 29 +++++++++++------ src/state.rs | 1 + src/test.rs | 16 ++++++++-- 9 files changed, 210 insertions(+), 50 deletions(-) diff --git a/src/extractor.rs b/src/extractor.rs index ea7681b0e..24e4c8afa 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -23,7 +23,7 @@ use actix_http::http::StatusCode; use actix_http::{HttpMessage, Response}; use actix_router::PathDeserializer; -use crate::handler::FromRequest; +use crate::handler::{ConfigStorage, ExtractorConfig, FromRequest}; use crate::request::HttpRequest; use crate::responder::Responder; use crate::service::ServiceFromRequest; @@ -133,6 +133,7 @@ where { type Error = Error; type Future = FutureResult; + type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { @@ -219,6 +220,7 @@ where { type Error = Error; type Future = FutureResult; + type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { @@ -299,16 +301,18 @@ where { type Error = Error; type Future = Box>; + type Config = FormConfig; #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let cfg = FormConfig::default(); - let req2 = req.clone(); + let cfg = req.load_config::(); + + let limit = cfg.limit; let err = Rc::clone(&cfg.ehandler); Box::new( UrlEncoded::new(req) - .limit(cfg.limit) + .limit(limit) .map_err(move |e| (*err)(e, &req2)) .map(Form), ) @@ -356,6 +360,7 @@ impl fmt::Display for Form { /// ); /// } /// ``` +#[derive(Clone)] pub struct FormConfig { limit: usize, ehandler: Rc Error>, @@ -363,13 +368,13 @@ pub struct FormConfig { impl FormConfig { /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { + pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self } /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self + pub fn error_handler(mut self, f: F) -> Self where F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, { @@ -378,6 +383,8 @@ impl FormConfig { } } +impl ExtractorConfig for FormConfig {} + impl Default for FormConfig { fn default() -> Self { FormConfig { @@ -509,16 +516,18 @@ where { type Error = Error; type Future = Box>; + type Config = JsonConfig; #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let cfg = JsonConfig::default(); - let req2 = req.clone(); + let cfg = req.load_config::(); + + let limit = cfg.limit; let err = Rc::clone(&cfg.ehandler); Box::new( JsonBody::new(req) - .limit(cfg.limit) + .limit(limit) .map_err(move |e| (*err)(e, &req2)) .map(Json), ) @@ -555,6 +564,7 @@ where /// }); /// } /// ``` +#[derive(Clone)] pub struct JsonConfig { limit: usize, ehandler: Rc Error>, @@ -562,13 +572,13 @@ pub struct JsonConfig { impl JsonConfig { /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { + pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self } /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self + pub fn error_handler(mut self, f: F) -> Self where F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, { @@ -577,6 +587,8 @@ impl JsonConfig { } } +impl ExtractorConfig for JsonConfig {} + impl Default for JsonConfig { fn default() -> Self { JsonConfig { @@ -617,16 +629,18 @@ where type Error = Error; type Future = Either>, FutureResult>; + type Config = PayloadConfig; #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let cfg = PayloadConfig::default(); + let cfg = req.load_config::(); if let Err(e) = cfg.check_mimetype(req) { return Either::B(err(e)); } - Either::A(Box::new(MessageBody::new(req).limit(cfg.limit).from_err())) + let limit = cfg.limit; + Either::A(Box::new(MessageBody::new(req).limit(limit).from_err())) } } @@ -664,10 +678,11 @@ where type Error = Error; type Future = Either>, FutureResult>; + type Config = PayloadConfig; #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { - let cfg = PayloadConfig::default(); + let cfg = req.load_config::(); // check content-type if let Err(e) = cfg.check_mimetype(req) { @@ -679,10 +694,11 @@ where Ok(enc) => enc, Err(e) => return Either::B(err(e.into())), }; + let limit = cfg.limit; Either::A(Box::new( MessageBody::new(req) - .limit(cfg.limit) + .limit(limit) .from_err() .and_then(move |body| { let enc: *const Encoding = encoding as *const Encoding; @@ -753,6 +769,7 @@ where { type Error = Error; type Future = Box, Error = Error>>; + type Config = T::Config; #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { @@ -816,6 +833,7 @@ where { type Error = Error; type Future = Box, Error = Error>>; + type Config = T::Config; #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { @@ -827,21 +845,27 @@ where } /// Payload configuration for request's payload. +#[derive(Clone)] pub struct PayloadConfig { limit: usize, mimetype: Option, } impl PayloadConfig { + /// Create `PayloadConfig` instance and set max size of payload. + pub fn new(limit: usize) -> Self { + Self::default().limit(limit) + } + /// Change max size of payload. By default max size is 256Kb - pub fn limit(&mut self, limit: usize) -> &mut Self { + pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self } /// Set required mime-type of the request. By default mime type is not /// enforced. - pub fn mimetype(&mut self, mt: Mime) -> &mut Self { + pub fn mimetype(mut self, mt: Mime) -> Self { self.mimetype = Some(mt); self } @@ -867,6 +891,8 @@ impl PayloadConfig { } } +impl ExtractorConfig for PayloadConfig {} + impl Default for PayloadConfig { fn default() -> Self { PayloadConfig { @@ -876,6 +902,16 @@ impl Default for PayloadConfig { } } +macro_rules! tuple_config ({ $($T:ident),+} => { + impl<$($T,)+> ExtractorConfig for ($($T,)+) + where $($T: ExtractorConfig + Clone,)+ + { + fn store_default(ext: &mut ConfigStorage) { + $($T::store_default(ext);)+ + } + } +}); + macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { /// FromRequest implementation for tuple @@ -883,6 +919,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { { type Error = Error; type Future = $fut_type; + type Config = ($($T::Config,)+); fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { $fut_type { @@ -932,6 +969,7 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { impl

FromRequest

for () { type Error = Error; type Future = FutureResult<(), Error>; + type Config = (); fn from_request(_req: &mut ServiceFromRequest

) -> Self::Future { ok(()) @@ -942,6 +980,17 @@ impl

FromRequest

for () { mod m { use super::*; +tuple_config!(A); +tuple_config!(A, B); +tuple_config!(A, B, C); +tuple_config!(A, B, C, D); +tuple_config!(A, B, C, D, E); +tuple_config!(A, B, C, D, E, F); +tuple_config!(A, B, C, D, E, F, G); +tuple_config!(A, B, C, D, E, F, G, H); +tuple_config!(A, B, C, D, E, F, G, H, I); +tuple_config!(A, B, C, D, E, F, G, H, I, J); + tuple_from_req!(TupleFromRequest1, (0, A)); tuple_from_req!(TupleFromRequest2, (0, A), (1, B)); tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C)); diff --git a/src/handler.rs b/src/handler.rs index 31ae796b2..313422ed5 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,6 +1,8 @@ +use std::cell::RefCell; use std::marker::PhantomData; +use std::rc::Rc; -use actix_http::{Error, Response}; +use actix_http::{Error, Extensions, Response}; use actix_service::{NewService, Service, Void}; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; @@ -19,10 +21,41 @@ pub trait FromRequest

: Sized { /// Future that resolves to a Self type Future: Future; + /// Configuration for the extractor + type Config: ExtractorConfig; + /// Convert request to a Self fn from_request(req: &mut ServiceFromRequest

) -> Self::Future; } +/// Storage for extractor configs +#[derive(Default)] +pub struct ConfigStorage { + pub(crate) storage: Option>, +} + +impl ConfigStorage { + pub fn store(&mut self, config: C) { + if self.storage.is_none() { + self.storage = Some(Rc::new(Extensions::new())); + } + if let Some(ref mut ext) = self.storage { + Rc::get_mut(ext).unwrap().insert(config); + } + } +} + +pub trait ExtractorConfig: Default + Clone + 'static { + /// Set default configuration to config storage + fn store_default(ext: &mut ConfigStorage) { + ext.store(Self::default()) + } +} + +impl ExtractorConfig for () { + fn store_default(_: &mut ConfigStorage) {} +} + /// Handler converter factory pub trait Factory: Clone where @@ -288,18 +321,16 @@ where /// Extract arguments from request pub struct Extract> { + config: Rc>>>, _t: PhantomData<(P, T)>, } impl> Extract { - pub fn new() -> Self { - Extract { _t: PhantomData } - } -} - -impl> Default for Extract { - fn default() -> Self { - Self::new() + pub fn new(config: Rc>>>) -> Self { + Extract { + config, + _t: PhantomData, + } } } @@ -312,11 +343,15 @@ impl> NewService for Extract { type Future = FutureResult; fn new_service(&self, _: &()) -> Self::Future { - ok(ExtractService { _t: PhantomData }) + ok(ExtractService { + _t: PhantomData, + config: self.config.borrow().clone(), + }) } } pub struct ExtractService> { + config: Option>, _t: PhantomData<(P, T)>, } @@ -331,7 +366,7 @@ impl> Service for ExtractService { } fn call(&mut self, req: ServiceRequest

) -> Self::Future { - let mut req = req.into(); + let mut req = ServiceFromRequest::new(req, self.config.clone()); ExtractResponse { fut: T::from_request(&mut req), req: Some(req), @@ -365,7 +400,6 @@ impl> Future for ExtractResponse { macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { impl Factory<($($T,)+), Res> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, - //$($T,)+ Res: Responder + 'static, { fn call(&self, param: ($($T,)+)) -> Res { diff --git a/src/lib.rs b/src/lib.rs index 0e81b65ab..8ad689aa9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![allow(clippy::type_complexity)] mod app; -mod extractor; +pub mod extractor; pub mod handler; // mod info; pub mod blocking; @@ -20,7 +20,7 @@ pub use actix_http::Response as HttpResponse; pub use actix_http::{http, Error, HttpMessage, ResponseError}; pub use crate::app::App; -pub use crate::extractor::{Form, Json, Path, Query}; +pub use crate::extractor::{Form, Json, Path, PayloadConfig, Query}; pub use crate::handler::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; diff --git a/src/request.rs b/src/request.rs index 538064f19..a7c84b534 100644 --- a/src/request.rs +++ b/src/request.rs @@ -143,6 +143,7 @@ impl HttpMessage for HttpRequest { impl

FromRequest

for HttpRequest { type Error = Error; type Future = FutureResult; + type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { diff --git a/src/resource.rs b/src/resource.rs index ab6096c52..0bee0ecd4 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -75,7 +75,7 @@ where /// } /// ``` pub fn route(mut self, route: Route

) -> Self { - self.routes.push(route); + self.routes.push(route.finish()); self } diff --git a/src/route.rs b/src/route.rs index 99117afed..578ba79ef 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,11 +1,15 @@ +use std::cell::RefCell; use std::rc::Rc; -use actix_http::{http::Method, Error, Response}; +use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; use futures::{Async, Future, IntoFuture, Poll}; use crate::filter::{self, Filter}; -use crate::handler::{AsyncFactory, AsyncHandle, Extract, Factory, FromRequest, Handle}; +use crate::handler::{ + AsyncFactory, AsyncHandle, ConfigStorage, Extract, ExtractorConfig, Factory, + FromRequest, Handle, +}; use crate::responder::Responder; use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -37,33 +41,50 @@ type BoxedRouteNewService = Box< pub struct Route

{ service: BoxedRouteNewService, ServiceResponse>, filters: Rc>>, + config: ConfigStorage, + config_ref: Rc>>>, } impl Route

{ + /// Create new route which matches any request. pub fn new() -> Route

{ + let config_ref = Rc::new(RefCell::new(None)); Route { - service: Box::new(RouteNewService::new(Extract::new().and_then( - Handle::new(|| HttpResponse::NotFound()).map_err(|_| panic!()), - ))), + service: Box::new(RouteNewService::new( + Extract::new(config_ref.clone()).and_then( + Handle::new(|| HttpResponse::NotFound()).map_err(|_| panic!()), + ), + )), filters: Rc::new(Vec::new()), + config: ConfigStorage::default(), + config_ref, } } + /// Create new `GET` route. pub fn get() -> Route

{ Route::new().method(Method::GET) } + /// Create new `POST` route. pub fn post() -> Route

{ Route::new().method(Method::POST) } + /// Create new `PUT` route. pub fn put() -> Route

{ Route::new().method(Method::PUT) } + /// Create new `DELETE` route. pub fn delete() -> Route

{ Route::new().method(Method::DELETE) } + + pub(crate) fn finish(self) -> Self { + *self.config_ref.borrow_mut() = self.config.storage.clone(); + self + } } impl

NewService for Route

{ @@ -260,8 +281,10 @@ impl Route

{ T: FromRequest

+ 'static, R: Responder + 'static, { + T::Config::store_default(&mut self.config); self.service = Box::new(RouteNewService::new( - Extract::new().and_then(Handle::new(handler).map_err(|_| panic!())), + Extract::new(self.config_ref.clone()) + .and_then(Handle::new(handler).map_err(|_| panic!())), )); self } @@ -305,10 +328,39 @@ impl Route

{ R::Error: Into, { self.service = Box::new(RouteNewService::new( - Extract::new().and_then(AsyncHandle::new(handler).map_err(|_| panic!())), + Extract::new(self.config_ref.clone()) + .and_then(AsyncHandle::new(handler).map_err(|_| panic!())), )); self } + + /// This method allows to add extractor configuration + /// for specific route. + /// + /// ```rust + /// use actix_web::{web, extractor, App}; + /// + /// /// extract text data from request + /// fn index(body: String) -> String { + /// format!("Body {}!", body) + /// } + /// + /// fn main() { + /// let app = App::new().resource("/index.html", |r| { + /// r.route( + /// web::get() + /// // limit size of the payload + /// .config(extractor::PayloadConfig::new(4096)) + /// // register handler + /// .to(index) + /// ) + /// }); + /// } + /// ``` + pub fn config(mut self, config: C) -> Self { + self.config.store(config); + self + } } // pub struct RouteServiceBuilder { diff --git a/src/service.rs b/src/service.rs index 99af73c16..5602a6133 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::cell::{Ref, RefMut}; use std::rc::Rc; @@ -167,9 +168,18 @@ impl

std::ops::DerefMut for ServiceRequest

{ pub struct ServiceFromRequest

{ req: HttpRequest, payload: Payload

, + config: Option>, } impl

ServiceFromRequest

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

, config: Option>) -> Self { + Self { + req: req.req, + payload: req.payload, + config, + } + } + #[inline] pub fn into_request(self) -> HttpRequest { self.req @@ -180,6 +190,16 @@ impl

ServiceFromRequest

{ pub fn error_response>(self, err: E) -> ServiceResponse { ServiceResponse::new(self.req, err.into().into()) } + + /// Load extractor configuration + pub fn load_config(&self) -> Cow { + if let Some(ref ext) = self.config { + if let Some(cfg) = ext.get::() { + return Cow::Borrowed(cfg); + } + } + Cow::Owned(T::default()) + } } impl

std::ops::Deref for ServiceFromRequest

{ @@ -204,15 +224,6 @@ impl

HttpMessage for ServiceFromRequest

{ } } -impl

From> for ServiceFromRequest

{ - fn from(req: ServiceRequest

) -> Self { - Self { - req: req.req, - payload: req.payload, - } - } -} - pub struct ServiceResponse { request: HttpRequest, response: Response, diff --git a/src/state.rs b/src/state.rs index db2637775..4a450245a 100644 --- a/src/state.rs +++ b/src/state.rs @@ -48,6 +48,7 @@ impl Clone for State { impl FromRequest

for State { type Error = Error; type Future = FutureResult; + type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { diff --git a/src/test.rs b/src/test.rs index d6caf8973..4899cfe41 100644 --- a/src/test.rs +++ b/src/test.rs @@ -9,7 +9,7 @@ use actix_router::{Path, Url}; use bytes::Bytes; use crate::request::HttpRequest; -use crate::service::ServiceRequest; +use crate::service::{ServiceFromRequest, ServiceRequest}; /// Test `Request` builder /// @@ -133,7 +133,7 @@ impl TestRequest { } /// Complete request creation and generate `HttpRequest` instance - pub fn request(mut self) -> HttpRequest { + pub fn to_request(mut self) -> HttpRequest { let req = self.req.finish(); ServiceRequest::new( @@ -143,4 +143,16 @@ impl TestRequest { ) .into_request() } + + /// Complete request creation and generate `ServiceFromRequest` instance + pub fn to_from(mut self) -> ServiceFromRequest { + let req = self.req.finish(); + + let req = ServiceRequest::new( + Path::new(Url::new(req.uri().clone())), + req, + Rc::new(self.extensions), + ); + ServiceFromRequest::new(req, None) + } }