From df21892b5b6f25066507430eb90682746c0d4e5f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 3 Apr 2018 22:06:18 -0700 Subject: [PATCH] added extractor configuration --- src/application.rs | 8 +-- src/extractor.rs | 87 ++++++++++++++++++++++++++------ src/handler.rs | 10 +++- src/httprequest.rs | 3 +- src/json.rs | 55 +++++++++++++++++++-- src/lib.rs | 3 +- src/resource.rs | 2 +- src/route.rs | 22 +++++++-- src/test.rs | 4 +- src/with.rs | 121 +++++++++++++++++++++++++++++++++++---------- 10 files changed, 253 insertions(+), 62 deletions(-) diff --git a/src/application.rs b/src/application.rs index 38886efc5..96a932c9b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -255,8 +255,8 @@ impl App where S: 'static { /// }); /// } /// ``` - pub fn resource(mut self, path: &str, f: F) -> App - where F: FnOnce(&mut ResourceHandler) + 'static + pub fn resource(mut self, path: &str, f: F) -> App + where F: FnOnce(&mut ResourceHandler) -> R + 'static { { let parts = self.parts.as_mut().expect("Use after finish"); @@ -272,8 +272,8 @@ impl App where S: 'static { } /// Default resource is used if no matched route could be found. - pub fn default_resource(mut self, f: F) -> App - where F: FnOnce(&mut ResourceHandler) + 'static + pub fn default_resource(mut self, f: F) -> App + where F: FnOnce(&mut ResourceHandler) -> R + 'static { { let parts = self.parts.as_mut().expect("Use after finish"); diff --git a/src/extractor.rs b/src/extractor.rs index 2346365bc..6f0e5b334 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -100,10 +100,11 @@ impl Path { impl FromRequest for Path where T: DeserializeOwned, S: 'static { + type Config = (); type Result = FutureResult; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); result(de::Deserialize::deserialize(PathDeserializer::new(&req)) .map_err(|e| e.into()) @@ -165,10 +166,11 @@ impl Query { impl FromRequest for Query where T: de::DeserializeOwned, S: 'static { + type Config = (); type Result = FutureResult; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let req = req.clone(); result(serde_urlencoded::from_str::(req.query_string()) .map_err(|e| e.into()) @@ -223,11 +225,60 @@ impl DerefMut for Form { impl FromRequest for Form where T: DeserializeOwned + 'static, S: 'static { + type Config = FormConfig; type Result = Box>; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { - Box::new(UrlEncoded::new(req.clone()).from_err().map(Form)) + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { + Box::new(UrlEncoded::new(req.clone()) + .limit(cfg.limit) + .from_err() + .map(Form)) + } +} + +/// Form extractor configuration +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Form, Result, http}; +/// +/// #[derive(Deserialize)] +/// struct FormData { +/// username: String, +/// } +/// +/// /// extract form data using serde, max payload size is 4k +/// fn index(form: Form) -> Result { +/// Ok(format!("Welcome {}!", form.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", |r| { +/// r.method(http::Method::GET) +/// .with(index) +/// .limit(4096);} // <- change form extractor configuration +/// ); +/// } +/// ``` +pub struct FormConfig { + limit: usize, +} + +impl FormConfig { + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(&mut self, limit: usize) -> &mut Self { + self.limit = limit; + self + } +} + +impl Default for FormConfig { + fn default() -> Self { + FormConfig{limit: 262_144} } } @@ -250,10 +301,11 @@ impl FromRequest for Form /// ``` impl FromRequest for Bytes { + type Config = (); type Result = Box>; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { Box::new(MessageBody::new(req.clone()).from_err()) } } @@ -276,11 +328,12 @@ impl FromRequest for Bytes /// ``` impl FromRequest for String { + type Config = (); type Result = Either, Box>>; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { let encoding = match req.encoding() { Err(_) => return Either::A( result(Err(ErrorBadRequest("Unknown request charset")))), @@ -325,7 +378,7 @@ mod tests { let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - match Bytes::from_request(&req).poll().unwrap() { + match Bytes::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s, Bytes::from_static(b"hello=world")); }, @@ -338,7 +391,7 @@ mod tests { let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - match String::from_request(&req).poll().unwrap() { + match String::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s, "hello=world"); }, @@ -354,7 +407,9 @@ mod tests { .finish(); req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - match Form::::from_request(&req).poll().unwrap() { + let mut cfg = FormConfig::default(); + cfg.limit(4096); + match Form::::from_request(&req, &cfg).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.hello, "world"); }, @@ -390,7 +445,7 @@ mod tests { let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req).poll().unwrap() { + match Path::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.key, "name"); assert_eq!(s.value, "user1"); @@ -398,7 +453,7 @@ mod tests { _ => unreachable!(), } - match Path::<(String, String)>::from_request(&req).poll().unwrap() { + match Path::<(String, String)>::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, "user1"); @@ -406,7 +461,7 @@ mod tests { _ => unreachable!(), } - match Query::::from_request(&req).poll().unwrap() { + match Query::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.id, "test"); }, @@ -416,7 +471,7 @@ mod tests { let mut req = TestRequest::with_uri("/name/32/").finish(); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req).poll().unwrap() { + match Path::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.as_ref().key, "name"); assert_eq!(s.value, 32); @@ -424,7 +479,7 @@ mod tests { _ => unreachable!(), } - match Path::<(String, u8)>::from_request(&req).poll().unwrap() { + match Path::<(String, u8)>::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.0, "name"); assert_eq!(s.1, 32); @@ -432,7 +487,7 @@ mod tests { _ => unreachable!(), } - match Path::>::from_request(&req).poll().unwrap() { + match Path::>::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.into_inner(), vec!["name".to_owned(), "32".to_owned()]); }, @@ -451,7 +506,7 @@ mod tests { let mut req = TestRequest::with_uri("/32/").finish(); assert!(router.recognize(&mut req).is_some()); - match Path::::from_request(&req).poll().unwrap() { + match Path::::from_request(&req, &()).poll().unwrap() { Async::Ready(s) => { assert_eq!(s.into_inner(), 32); }, diff --git a/src/handler.rs b/src/handler.rs index 6041dc288..edfd1edbe 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -37,9 +37,14 @@ pub trait Responder { /// Types that implement this trait can be used with `Route::with()` method. pub trait FromRequest: Sized where S: 'static { + /// Configuration for conversion process + type Config: Default; + + /// Future that resolves to a Self type Result: Future; - fn from_request(req: &HttpRequest) -> Self::Result; + /// Convert request to a Self + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result; } /// Combines two different responder types into a single type @@ -433,10 +438,11 @@ impl Deref for State { impl FromRequest for State { + type Config = (); type Result = FutureResult; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { ok(State(req.clone())) } } diff --git a/src/httprequest.rs b/src/httprequest.rs index 00aacb810..8077ab230 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -494,10 +494,11 @@ impl Clone for HttpRequest { impl FromRequest for HttpRequest { + type Config = (); type Result = FutureResult; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, _: &Self::Config) -> Self::Result { result(Ok(req.clone())) } } diff --git a/src/json.rs b/src/json.rs index bef34960f..722b35ce7 100644 --- a/src/json.rs +++ b/src/json.rs @@ -110,17 +110,64 @@ impl Responder for Json { impl FromRequest for Json where T: DeserializeOwned + 'static, S: 'static { + type Config = JsonConfig; type Result = Box>; #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { + fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { Box::new( JsonBody::new(req.clone()) + .limit(cfg.limit) .from_err() .map(Json)) } } +/// Json extractor configuration +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{App, Json, Result, http}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body, max payload size is 4kb +/// fn index(info: Json) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().resource( +/// "/index.html", |r| { +/// r.method(http::Method::POST) +/// .with(index) +/// .limit(4096);} // <- change json extractor configuration +/// ); +/// } +/// ``` +pub struct JsonConfig { + limit: usize, +} + +impl JsonConfig { + + /// Change max size of payload. By default max size is 256Kb + pub fn limit(&mut self, limit: usize) -> &mut Self { + self.limit = limit; + self + } +} + +impl Default for JsonConfig { + fn default() -> Self { + JsonConfig{limit: 262_144} + } +} + /// Request payload json parser that resolves to a deserialized `T` value. /// /// Returns error: @@ -231,7 +278,7 @@ mod tests { use http::header; use futures::Async; - use with::With; + use with::{With, ExtractorConfig}; use handler::Handler; impl PartialEq for JsonPayloadError { @@ -295,7 +342,9 @@ mod tests { #[test] fn test_with_json() { - let mut handler = With::new(|data: Json| data); + let mut cfg = ExtractorConfig::<_, Json>::default(); + cfg.limit(4096); + let mut handler = With::new(|data: Json| {data}, cfg); let req = HttpRequest::default(); let err = handler.handle(req).as_response().unwrap().error().is_some(); diff --git a/src/lib.rs b/src/lib.rs index 3e134c441..bf536e2d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,9 +177,10 @@ pub mod dev { pub use body::BodyStream; pub use context::Drain; - pub use json::JsonBody; + pub use json::{JsonBody, JsonConfig}; pub use info::ConnectionInfo; pub use handler::{Handler, Reply}; + pub use extractor::{FormConfig}; pub use route::Route; pub use router::{Router, Resource, ResourceType}; pub use resource::ResourceHandler; diff --git a/src/resource.rs b/src/resource.rs index f28363e28..0ffc64122 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -144,7 +144,7 @@ impl ResourceHandler { T: FromRequest + 'static, { self.routes.push(Route::default()); - self.routes.last_mut().unwrap().with(handler) + self.routes.last_mut().unwrap().with(handler); } /// Register a resource middleware diff --git a/src/route.rs b/src/route.rs index 1eebaa3ea..a2c1947e4 100644 --- a/src/route.rs +++ b/src/route.rs @@ -11,7 +11,7 @@ use handler::{Reply, ReplyItem, Handler, FromRequest, use middleware::{Middleware, Response as MiddlewareResponse, Started as MiddlewareStarted}; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use with::{With, With2, With3}; +use with::{With, With2, With3, ExtractorConfig}; /// Resource route definition /// @@ -127,12 +127,14 @@ impl Route { /// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor /// } /// ``` - pub fn with(&mut self, handler: F) + pub fn with(&mut self, handler: F) -> ExtractorConfig where F: Fn(T) -> R + 'static, R: Responder + 'static, T: FromRequest + 'static, { - self.h(With::new(handler)) + let cfg = ExtractorConfig::default(); + self.h(With::new(handler, Clone::clone(&cfg))); + cfg } /// Set handler function, function has to accept two request extractors. @@ -166,23 +168,33 @@ impl Route { /// } /// ``` pub fn with2(&mut self, handler: F) + -> (ExtractorConfig, ExtractorConfig) where F: Fn(T1, T2) -> R + 'static, R: Responder + 'static, T1: FromRequest + 'static, T2: FromRequest + 'static, { - self.h(With2::new(handler)) + let cfg1 = ExtractorConfig::default(); + let cfg2 = ExtractorConfig::default(); + self.h(With2::new(handler, Clone::clone(&cfg1), Clone::clone(&cfg2))); + (cfg1, cfg2) } /// Set handler function, function has to accept three request extractors. pub fn with3(&mut self, handler: F) + -> (ExtractorConfig, ExtractorConfig, ExtractorConfig) where F: Fn(T1, T2, T3) -> R + 'static, R: Responder + 'static, T1: FromRequest + 'static, T2: FromRequest + 'static, T3: FromRequest + 'static, { - self.h(With3::new(handler)) + let cfg1 = ExtractorConfig::default(); + let cfg2 = ExtractorConfig::default(); + let cfg3 = ExtractorConfig::default(); + self.h(With3::new( + handler, Clone::clone(&cfg1), Clone::clone(&cfg2), Clone::clone(&cfg3))); + (cfg1, cfg2, cfg3) } } diff --git a/src/test.rs b/src/test.rs index b6fd22d2c..4e5ed9bd0 100644 --- a/src/test.rs +++ b/src/test.rs @@ -351,8 +351,8 @@ impl TestApp { /// Register resource. This method is similar /// to `App::resource()` method. - pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp - where F: FnOnce(&mut ResourceHandler) + 'static + pub fn resource(&mut self, path: &str, f: F) -> &mut TestApp + where F: FnOnce(&mut ResourceHandler) -> R + 'static { self.app = Some(self.app.take().unwrap().resource(path, f)); self diff --git a/src/with.rs b/src/with.rs index 2a4420392..5f70db259 100644 --- a/src/with.rs +++ b/src/with.rs @@ -1,6 +1,7 @@ use std::rc::Rc; use std::cell::UnsafeCell; use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; use futures::{Async, Future, Poll}; use error::Error; @@ -8,19 +9,55 @@ use handler::{Handler, FromRequest, Reply, ReplyItem, Responder}; use httprequest::HttpRequest; use httpresponse::HttpResponse; +pub struct ExtractorConfig> { + cfg: Rc> +} + +impl> Default for ExtractorConfig { + fn default() -> Self { + ExtractorConfig { cfg: Rc::new(UnsafeCell::new(T::Config::default())) } + } +} + +impl> Clone for ExtractorConfig { + fn clone(&self) -> Self { + ExtractorConfig { cfg: Rc::clone(&self.cfg) } + } +} + +impl> AsRef for ExtractorConfig { + fn as_ref(&self) -> &T::Config { + unsafe{&*self.cfg.get()} + } +} + +impl> Deref for ExtractorConfig { + type Target = T::Config; + + fn deref(&self) -> &T::Config { + unsafe{&*self.cfg.get()} + } +} + +impl> DerefMut for ExtractorConfig { + fn deref_mut(&mut self) -> &mut T::Config { + unsafe{&mut *self.cfg.get()} + } +} + pub struct With - where F: Fn(T) -> R + where F: Fn(T) -> R, T: FromRequest, S: 'static, { hnd: Rc>, - _t: PhantomData, + cfg: ExtractorConfig, _s: PhantomData, } impl With - where F: Fn(T) -> R, + where F: Fn(T) -> R, T: FromRequest, S: 'static, { - pub fn new(f: F) -> Self { - With{hnd: Rc::new(UnsafeCell::new(f)), _t: PhantomData, _s: PhantomData} + pub fn new(f: F, cfg: ExtractorConfig) -> Self { + With{cfg, hnd: Rc::new(UnsafeCell::new(f)), _s: PhantomData} } } @@ -37,6 +74,7 @@ impl Handler for With req, started: false, hnd: Rc::clone(&self.hnd), + cfg: self.cfg.clone(), fut1: None, fut2: None, }; @@ -57,6 +95,7 @@ struct WithHandlerFut { started: bool, hnd: Rc>, + cfg: ExtractorConfig, req: HttpRequest, fut1: Option>>, fut2: Option>>, @@ -78,7 +117,7 @@ impl Future for WithHandlerFut let item = if !self.started { self.started = true; - let mut fut = T::from_request(&self.req); + let mut fut = T::from_request(&self.req, self.cfg.as_ref()); match fut.poll() { Ok(Async::Ready(item)) => item, Ok(Async::NotReady) => { @@ -110,19 +149,23 @@ impl Future for WithHandlerFut } } -pub struct With2 where F: Fn(T1, T2) -> R +pub struct With2 + where F: Fn(T1, T2) -> R, + T1: FromRequest + 'static, T2: FromRequest + 'static, S: 'static { hnd: Rc>, - _t1: PhantomData, - _t2: PhantomData, + cfg1: ExtractorConfig, + cfg2: ExtractorConfig, _s: PhantomData, } -impl With2 where F: Fn(T1, T2) -> R +impl With2 + where F: Fn(T1, T2) -> R, + T1: FromRequest + 'static, T2: FromRequest + 'static, S: 'static { - pub fn new(f: F) -> Self { + pub fn new(f: F, cfg1: ExtractorConfig, cfg2: ExtractorConfig) -> Self { With2{hnd: Rc::new(UnsafeCell::new(f)), - _t1: PhantomData, _t2: PhantomData, _s: PhantomData} + cfg1, cfg2, _s: PhantomData} } } @@ -140,6 +183,8 @@ impl Handler for With2 req, started: false, hnd: Rc::clone(&self.hnd), + cfg1: self.cfg1.clone(), + cfg2: self.cfg2.clone(), item: None, fut1: None, fut2: None, @@ -162,6 +207,8 @@ struct WithHandlerFut2 { started: bool, hnd: Rc>, + cfg1: ExtractorConfig, + cfg2: ExtractorConfig, req: HttpRequest, item: Option, fut1: Option>>, @@ -186,10 +233,10 @@ impl Future for WithHandlerFut2 if !self.started { self.started = true; - let mut fut = T1::from_request(&self.req); + let mut fut = T1::from_request(&self.req, self.cfg1.as_ref()); match fut.poll() { Ok(Async::Ready(item1)) => { - let mut fut = T2::from_request(&self.req); + let mut fut = T2::from_request(&self.req,self.cfg2.as_ref()); match fut.poll() { Ok(Async::Ready(item2)) => { let hnd: &mut F = unsafe{&mut *self.hnd.get()}; @@ -228,7 +275,8 @@ impl Future for WithHandlerFut2 Async::Ready(item) => { self.item = Some(item); self.fut1.take(); - self.fut2 = Some(Box::new(T2::from_request(&self.req))); + self.fut2 = Some(Box::new( + T2::from_request(&self.req, self.cfg2.as_ref()))); }, Async::NotReady => return Ok(Async::NotReady), } @@ -256,21 +304,32 @@ impl Future for WithHandlerFut2 } } -pub struct With3 where F: Fn(T1, T2, T3) -> R { +pub struct With3 + where F: Fn(T1, T2, T3) -> R, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + T3: FromRequest + 'static, + S: 'static +{ hnd: Rc>, - _t1: PhantomData, - _t2: PhantomData, - _t3: PhantomData, + cfg1: ExtractorConfig, + cfg2: ExtractorConfig, + cfg3: ExtractorConfig, _s: PhantomData, } impl With3 where F: Fn(T1, T2, T3) -> R, + T1: FromRequest + 'static, + T2: FromRequest + 'static, + T3: FromRequest + 'static, + S: 'static { - pub fn new(f: F) -> Self { - With3{hnd: Rc::new(UnsafeCell::new(f)), - _s: PhantomData, _t1: PhantomData, _t2: PhantomData, _t3: PhantomData} + pub fn new(f: F, cfg1: ExtractorConfig, + cfg2: ExtractorConfig, cfg3: ExtractorConfig) -> Self + { + With3{hnd: Rc::new(UnsafeCell::new(f)), cfg1, cfg2, cfg3, _s: PhantomData} } } @@ -288,6 +347,9 @@ impl Handler for With3 let mut fut = WithHandlerFut3{ req, hnd: Rc::clone(&self.hnd), + cfg1: self.cfg1.clone(), + cfg2: self.cfg2.clone(), + cfg3: self.cfg3.clone(), started: false, item1: None, item2: None, @@ -314,6 +376,9 @@ struct WithHandlerFut3 { hnd: Rc>, req: HttpRequest, + cfg1: ExtractorConfig, + cfg2: ExtractorConfig, + cfg3: ExtractorConfig, started: bool, item1: Option, item2: Option, @@ -341,13 +406,13 @@ impl Future for WithHandlerFut3 if !self.started { self.started = true; - let mut fut = T1::from_request(&self.req); + let mut fut = T1::from_request(&self.req, self.cfg1.as_ref()); match fut.poll() { Ok(Async::Ready(item1)) => { - let mut fut = T2::from_request(&self.req); + let mut fut = T2::from_request(&self.req, self.cfg2.as_ref()); match fut.poll() { Ok(Async::Ready(item2)) => { - let mut fut = T3::from_request(&self.req); + let mut fut = T3::from_request(&self.req, self.cfg3.as_ref()); match fut.poll() { Ok(Async::Ready(item3)) => { let hnd: &mut F = unsafe{&mut *self.hnd.get()}; @@ -395,7 +460,8 @@ impl Future for WithHandlerFut3 Async::Ready(item) => { self.item1 = Some(item); self.fut1.take(); - self.fut2 = Some(Box::new(T2::from_request(&self.req))); + self.fut2 = Some(Box::new( + T2::from_request(&self.req, self.cfg2.as_ref()))); }, Async::NotReady => return Ok(Async::NotReady), } @@ -406,7 +472,8 @@ impl Future for WithHandlerFut3 Async::Ready(item) => { self.item2 = Some(item); self.fut2.take(); - self.fut3 = Some(Box::new(T3::from_request(&self.req))); + self.fut3 = Some(Box::new( + T3::from_request(&self.req, self.cfg3.as_ref()))); }, Async::NotReady => return Ok(Async::NotReady), }