diff --git a/src/extract/form.rs b/src/extract/form.rs new file mode 100644 index 000000000..19849ac8b --- /dev/null +++ b/src/extract/form.rs @@ -0,0 +1,197 @@ +//! Form extractor + +use std::rc::Rc; +use std::{fmt, ops}; + +use actix_http::dev::UrlEncoded; +use actix_http::error::{Error, UrlencodedError}; +use bytes::Bytes; +use futures::{Future, Stream}; +use serde::de::DeserializeOwned; + +use crate::extract::FromRequest; +use crate::request::HttpRequest; +use crate::service::ServiceFromRequest; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +/// Extract typed information from the request's body. +/// +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. +/// +/// [**FormConfig**](struct.FormConfig.html) allows to configure extraction +/// process. +/// +/// ## Example +/// +/// ```rust +/// # extern crate actix_web; +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Deserialize)] +/// struct FormData { +/// username: String, +/// } +/// +/// /// Extract form data using serde. +/// /// This handler get called only if content type is *x-www-form-urlencoded* +/// /// and content of the request could be deserialized to a `FormData` struct +/// fn index(form: web::Form) -> String { +/// format!("Welcome {}!", form.username) +/// } +/// # fn main() {} +/// ``` +pub struct Form(pub T); + +impl Form { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl ops::Deref for Form { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl ops::DerefMut for Form { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl FromRequest

for Form +where + T: DeserializeOwned + 'static, + P: Stream + 'static, +{ + type Error = Error; + type Future = Box>; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + let req2 = req.clone(); + let (limit, err) = req + .load_config::() + .map(|c| (c.limit, c.ehandler.clone())) + .unwrap_or((16384, None)); + + Box::new( + UrlEncoded::new(req) + .limit(limit) + .map_err(move |e| { + if let Some(err) = err { + (*err)(e, &req2) + } else { + e.into() + } + }) + .map(Form), + ) + } +} + +impl fmt::Debug for Form { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for Form { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Form extractor configuration +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, Result}; +/// +/// #[derive(Deserialize)] +/// struct FormData { +/// username: String, +/// } +/// +/// /// Extract form data using serde. +/// /// Custom configuration is used for this handler, max payload size is 4k +/// fn index(form: web::Form) -> Result { +/// Ok(format!("Welcome {}!", form.username)) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html") +/// .route(web::get() +/// // change `Form` extractor configuration +/// .config(web::FormConfig::default().limit(4097)) +/// .to(index)) +/// ); +/// } +/// ``` +#[derive(Clone)] +pub struct FormConfig { + limit: usize, + ehandler: Option Error>>, +} + +impl FormConfig { + /// Change max size of payload. By default max size is 16Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set custom error handler + pub fn error_handler(mut self, f: F) -> Self + where + F: Fn(UrlencodedError, &HttpRequest) -> Error + 'static, + { + self.ehandler = Some(Rc::new(f)); + self + } +} + +impl Default for FormConfig { + fn default() -> Self { + FormConfig { + limit: 16384, + ehandler: None, + } + } +} + +#[cfg(test)] +mod tests { + use actix_http::http::header; + use bytes::Bytes; + use serde_derive::Deserialize; + + use super::*; + use crate::test::{block_on, TestRequest}; + + #[derive(Deserialize, Debug, PartialEq)] + struct Info { + hello: String, + } + + #[test] + fn test_form() { + let mut req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .header(header::CONTENT_LENGTH, "11") + .set_payload(Bytes::from_static(b"hello=world")) + .to_from(); + + let s = block_on(Form::::from_request(&mut req)).unwrap(); + assert_eq!(s.hello, "world"); + } +} diff --git a/src/extract/json.rs b/src/extract/json.rs new file mode 100644 index 000000000..f74b082d1 --- /dev/null +++ b/src/extract/json.rs @@ -0,0 +1,259 @@ +//! Json extractor/responder + +use std::rc::Rc; +use std::{fmt, ops}; + +use bytes::Bytes; +use futures::{Future, Stream}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use serde_json; + +use actix_http::dev::JsonBody; +use actix_http::error::{Error, JsonPayloadError}; +use actix_http::http::StatusCode; +use actix_http::Response; + +use crate::extract::FromRequest; +use crate::request::HttpRequest; +use crate::responder::Responder; +use crate::service::ServiceFromRequest; + +/// Json helper +/// +/// Json can be used for two different purpose. First is for json response +/// generation and second is for extracting typed information from request's +/// payload. +/// +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. +/// +/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction +/// process. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body +/// fn index(info: web::Json) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().to(index)) +/// ); +/// } +/// ``` +/// +/// The `Json` type allows you to respond with well-formed JSON data: simply +/// return a value of type Json where T is the type of a structure +/// to serialize into *JSON*. The type `T` must implement the `Serialize` +/// trait from *serde*. +/// +/// ```rust +/// # #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// # +/// #[derive(Serialize)] +/// struct MyObj { +/// name: String, +/// } +/// +/// fn index(req: HttpRequest) -> Result> { +/// Ok(web::Json(MyObj { +/// name: req.match_info().get("name").unwrap().to_string(), +/// })) +/// } +/// # fn main() {} +/// ``` +pub struct Json(pub T); + +impl Json { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl ops::Deref for Json { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl ops::DerefMut for Json { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for Json +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Json: {:?}", self.0) + } +} + +impl fmt::Display for Json +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl Responder for Json { + type Error = Error; + type Future = Result; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + let body = match serde_json::to_string(&self.0) { + Ok(body) => body, + Err(e) => return Err(e.into()), + }; + + Ok(Response::build(StatusCode::OK) + .content_type("application/json") + .body(body)) + } +} + +/// Json extractor. Allow to extract typed information from request's +/// payload. +/// +/// To extract typed information from request's body, the type `T` must +/// implement the `Deserialize` trait from *serde*. +/// +/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction +/// process. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body +/// fn index(info: web::Json) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().to(index)) +/// ); +/// } +/// ``` +impl FromRequest

for Json +where + T: DeserializeOwned + 'static, + P: Stream + 'static, +{ + type Error = Error; + type Future = Box>; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + let req2 = req.clone(); + let (limit, err) = req + .load_config::() + .map(|c| (c.limit, c.ehandler.clone())) + .unwrap_or((32768, None)); + + Box::new( + JsonBody::new(req) + .limit(limit) + .map_err(move |e| { + if let Some(err) = err { + (*err)(e, &req2) + } else { + e.into() + } + }) + .map(Json), + ) + } +} + +/// Json extractor configuration +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{error, web, App, HttpResponse}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// deserialize `Info` from request's body, max payload size is 4kb +/// fn index(info: web::Json) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::post().config( +/// // change json extractor configuration +/// web::JsonConfig::default().limit(4096) +/// .error_handler(|err, req| { // <- create custom error response +/// error::InternalError::from_response( +/// err, HttpResponse::Conflict().finish()).into() +/// })) +/// .to(index)) +/// ); +/// } +/// ``` +#[derive(Clone)] +pub struct JsonConfig { + limit: usize, + ehandler: Option Error>>, +} + +impl JsonConfig { + /// Change max size of payload. By default max size is 32Kb + pub fn limit(mut self, limit: usize) -> Self { + self.limit = limit; + self + } + + /// Set custom error handler + pub fn error_handler(mut self, f: F) -> Self + where + F: Fn(JsonPayloadError, &HttpRequest) -> Error + 'static, + { + self.ehandler = Some(Rc::new(f)); + self + } +} + +impl Default for JsonConfig { + fn default() -> Self { + JsonConfig { + limit: 32768, + ehandler: None, + } + } +} diff --git a/src/extract/mod.rs b/src/extract/mod.rs index 5d08dc079..d8958b2d9 100644 --- a/src/extract/mod.rs +++ b/src/extract/mod.rs @@ -1,7 +1,6 @@ -use std::rc::Rc; +//! Request extractors use actix_http::error::Error; -use actix_http::Extensions; use futures::future::ok; use futures::{future, Async, Future, IntoFuture, Poll}; @@ -29,41 +28,10 @@ pub trait FromRequest

: Sized { /// Future that resolves to a Self type Future: IntoFuture; - /// 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) {} -} - /// Optionally extract a field from the request /// /// If the FromRequest for T fails, return None rather than returning an error response @@ -84,7 +52,6 @@ impl ExtractorConfig for () { /// impl

FromRequest

for Thing { /// type Error = Error; /// type Future = Result; -/// type Config = (); /// /// fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { /// if rand::random() { @@ -119,7 +86,6 @@ where { type Error = Error; type Future = Box, Error = Error>>; - type Config = T::Config; #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { @@ -153,7 +119,6 @@ where /// impl

FromRequest

for Thing { /// type Error = Error; /// type Future = Result; -/// type Config = (); /// /// fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { /// if rand::random() { @@ -186,7 +151,6 @@ where { type Error = Error; type Future = Box, Error = Error>>; - type Config = T::Config; #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { @@ -201,23 +165,12 @@ where impl

FromRequest

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

) -> Self::Future { Ok(()) } } -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 @@ -226,7 +179,6 @@ 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 { @@ -277,17 +229,6 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => { 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)); @@ -335,20 +276,6 @@ mod tests { assert_eq!(s, "hello=world"); } - #[test] - fn test_form() { - let mut req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_from(); - - let s = block_on(Form::::from_request(&mut req)).unwrap(); - assert_eq!(s.hello, "world"); - } - #[test] fn test_option() { let mut req = TestRequest::with_header( diff --git a/src/extract/path.rs b/src/extract/path.rs new file mode 100644 index 000000000..d9461263a --- /dev/null +++ b/src/extract/path.rs @@ -0,0 +1,176 @@ +//! Path extractor + +use std::{fmt, ops}; + +use actix_http::error::{Error, ErrorNotFound}; +use actix_router::PathDeserializer; +use serde::de; + +use crate::request::HttpRequest; +use crate::service::ServiceFromRequest; + +use super::FromRequest; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +/// Extract typed information from the request's path. +/// +/// ## Example +/// +/// ```rust +/// use actix_web::{web, App}; +/// +/// /// extract path info from "/{username}/{count}/index.html" url +/// /// {username} - deserializes to a String +/// /// {count} - - deserializes to a u32 +/// fn index(info: web::Path<(String, u32)>) -> String { +/// format!("Welcome {}! {}", info.0, info.1) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{username}/{count}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- register handler with `Path` extractor +/// ); +/// } +/// ``` +/// +/// It is possible to extract path information to a specific type that +/// implements `Deserialize` trait from *serde*. +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, Error}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// extract `Info` from a path using serde +/// fn index(info: web::Path) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{username}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- use handler with Path` extractor +/// ); +/// } +/// ``` +pub struct Path { + inner: T, +} + +impl Path { + /// Deconstruct to an inner value + pub fn into_inner(self) -> T { + self.inner + } + + /// Extract path information from a request + pub fn extract(req: &HttpRequest) -> Result, de::value::Error> + where + T: de::DeserializeOwned, + { + de::Deserialize::deserialize(PathDeserializer::new(req.match_info())) + .map(|inner| Path { inner }) + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &T { + &self.inner + } +} + +impl ops::Deref for Path { + type Target = T; + + fn deref(&self) -> &T { + &self.inner + } +} + +impl ops::DerefMut for Path { + fn deref_mut(&mut self) -> &mut T { + &mut self.inner + } +} + +impl From for Path { + fn from(inner: T) -> Path { + Path { inner } + } +} + +impl fmt::Debug for Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl fmt::Display for Path { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.inner.fmt(f) + } +} + +/// Extract typed information from the request's path. +/// +/// ## Example +/// +/// ```rust +/// use actix_web::{web, App}; +/// +/// /// extract path info from "/{username}/{count}/index.html" url +/// /// {username} - deserializes to a String +/// /// {count} - - deserializes to a u32 +/// fn index(info: web::Path<(String, u32)>) -> String { +/// format!("Welcome {}! {}", info.0, info.1) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{username}/{count}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- register handler with `Path` extractor +/// ); +/// } +/// ``` +/// +/// It is possible to extract path information to a specific type that +/// implements `Deserialize` trait from *serde*. +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App, Error}; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// username: String, +/// } +/// +/// /// extract `Info` from a path using serde +/// fn index(info: web::Path) -> Result { +/// Ok(format!("Welcome {}!", info.username)) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/{username}/index.html") // <- define path parameters +/// .route(web::get().to(index)) // <- use handler with Path` extractor +/// ); +/// } +/// ``` +impl FromRequest

for Path +where + T: de::DeserializeOwned, +{ + type Error = Error; + type Future = Result; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + Self::extract(req).map_err(ErrorNotFound) + } +} diff --git a/src/extract/payload.rs b/src/extract/payload.rs new file mode 100644 index 000000000..82024c0d8 --- /dev/null +++ b/src/extract/payload.rs @@ -0,0 +1,313 @@ +//! Payload/Bytes/String extractors +use std::str; + +use actix_http::dev::MessageBody; +use actix_http::error::{Error, ErrorBadRequest, PayloadError}; +use actix_http::HttpMessage; +use bytes::Bytes; +use encoding::all::UTF_8; +use encoding::types::{DecoderTrap, Encoding}; +use futures::future::{err, Either, FutureResult}; +use futures::{Future, Poll, Stream}; +use mime::Mime; + +use crate::extract::FromRequest; +use crate::service::ServiceFromRequest; + +/// Payload extractor returns request 's payload stream. +/// +/// ## Example +/// +/// ```rust +/// use futures::{Future, Stream}; +/// use actix_web::{web, error, App, Error, HttpResponse}; +/// +/// /// extract binary data from request +/// fn index

(body: web::Payload

) -> impl Future +/// where +/// P: Stream +/// { +/// body.map_err(Error::from) +/// .fold(web::BytesMut::new(), move |mut body, chunk| { +/// body.extend_from_slice(&chunk); +/// Ok::<_, Error>(body) +/// }) +/// .and_then(|body| { +/// format!("Body {:?}!", body); +/// Ok(HttpResponse::Ok().finish()) +/// }) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to_async(index)) +/// ); +/// } +/// ``` +pub struct Payload(crate::dev::Payload); + +impl Stream for Payload +where + T: Stream, +{ + type Item = Bytes; + type Error = PayloadError; + + #[inline] + fn poll(&mut self) -> Poll, PayloadError> { + self.0.poll() + } +} + +/// Get request's payload stream +/// +/// ## Example +/// +/// ```rust +/// use futures::{Future, Stream}; +/// use actix_web::{web, error, App, Error, HttpResponse}; +/// +/// /// extract binary data from request +/// fn index

(body: web::Payload

) -> impl Future +/// where +/// P: Stream +/// { +/// body.map_err(Error::from) +/// .fold(web::BytesMut::new(), move |mut body, chunk| { +/// body.extend_from_slice(&chunk); +/// Ok::<_, Error>(body) +/// }) +/// .and_then(|body| { +/// format!("Body {:?}!", body); +/// Ok(HttpResponse::Ok().finish()) +/// }) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to_async(index)) +/// ); +/// } +/// ``` +impl

FromRequest

for Payload

+where + P: Stream, +{ + type Error = Error; + type Future = Result, Error>; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + Ok(Payload(req.take_payload())) + } +} + +/// Request binary data from a request's payload. +/// +/// Loads request's payload and construct Bytes instance. +/// +/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure +/// extraction process. +/// +/// ## Example +/// +/// ```rust +/// use bytes::Bytes; +/// use actix_web::{web, App}; +/// +/// /// extract binary data from request +/// fn index(body: Bytes) -> String { +/// format!("Body {:?}!", body) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get().to(index)) +/// ); +/// } +/// ``` +impl

FromRequest

for Bytes +where + P: Stream + 'static, +{ + type Error = Error; + type Future = + Either>, FutureResult>; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + let mut tmp; + let cfg = if let Some(cfg) = req.load_config::() { + cfg + } else { + tmp = PayloadConfig::default(); + &tmp + }; + + if let Err(e) = cfg.check_mimetype(req) { + return Either::B(err(e)); + } + + let limit = cfg.limit; + Either::A(Box::new(MessageBody::new(req).limit(limit).from_err())) + } +} + +/// Extract text information from a request's body. +/// +/// Text extractor automatically decode body according to the request's charset. +/// +/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure +/// extraction process. +/// +/// ## Example +/// +/// ```rust +/// use actix_web::{web, App}; +/// +/// /// extract text data from request +/// fn index(text: String) -> String { +/// format!("Body {}!", text) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route( +/// web::get() +/// .config(web::PayloadConfig::new(4096)) // <- limit size of the payload +/// .to(index)) // <- register handler with extractor params +/// ); +/// } +/// ``` +impl

FromRequest

for String +where + P: Stream + 'static, +{ + type Error = Error; + type Future = + Either>, FutureResult>; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + let mut tmp; + let cfg = if let Some(cfg) = req.load_config::() { + cfg + } else { + tmp = PayloadConfig::default(); + &tmp + }; + + // check content-type + if let Err(e) = cfg.check_mimetype(req) { + return Either::B(err(e)); + } + + // check charset + let encoding = match req.encoding() { + Ok(enc) => enc, + Err(e) => return Either::B(err(e.into())), + }; + let limit = cfg.limit; + + Either::A(Box::new( + MessageBody::new(req) + .limit(limit) + .from_err() + .and_then(move |body| { + let enc: *const Encoding = encoding as *const Encoding; + if enc == UTF_8 { + Ok(str::from_utf8(body.as_ref()) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + .to_owned()) + } else { + Ok(encoding + .decode(&body, DecoderTrap::Strict) + .map_err(|_| ErrorBadRequest("Can not decode body"))?) + } + }), + )) + } +} +/// 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) -> 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) -> Self { + self.mimetype = Some(mt); + self + } + + fn check_mimetype

(&self, req: &ServiceFromRequest

) -> Result<(), Error> { + // check content-type + if let Some(ref mt) = self.mimetype { + match req.mime_type() { + Ok(Some(ref req_mt)) => { + if mt != req_mt { + return Err(ErrorBadRequest("Unexpected Content-Type")); + } + } + Ok(None) => { + return Err(ErrorBadRequest("Content-Type is expected")); + } + Err(err) => { + return Err(err.into()); + } + } + } + Ok(()) + } +} + +impl Default for PayloadConfig { + fn default() -> Self { + PayloadConfig { + limit: 262_144, + mimetype: None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::http::header; + use crate::test::TestRequest; + + #[test] + fn test_payload_config() { + let req = TestRequest::default().to_from(); + let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON); + assert!(cfg.check_mimetype(&req).is_err()); + + let req = TestRequest::with_header( + header::CONTENT_TYPE, + "application/x-www-form-urlencoded", + ) + .to_from(); + assert!(cfg.check_mimetype(&req).is_err()); + + let req = + TestRequest::with_header(header::CONTENT_TYPE, "application/json").to_from(); + assert!(cfg.check_mimetype(&req).is_ok()); + } +} diff --git a/src/extract/query.rs b/src/extract/query.rs new file mode 100644 index 000000000..f0eb6a7ae --- /dev/null +++ b/src/extract/query.rs @@ -0,0 +1,126 @@ +//! Query extractor + +use std::{fmt, ops}; + +use actix_http::error::Error; +use serde::de; +use serde_urlencoded; + +use crate::extract::FromRequest; +use crate::service::ServiceFromRequest; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +/// Extract typed information from from the request's query. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Debug, Deserialize)] +/// pub enum ResponseType { +/// Token, +/// Code +/// } +/// +/// #[derive(Deserialize)] +/// pub struct AuthRequest { +/// id: u64, +/// response_type: ResponseType, +/// } +/// +/// // Use `Query` extractor for query information. +/// // This handler get called only if request's query contains `username` field +/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` +/// fn index(info: web::Query) -> String { +/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor +/// } +/// ``` +pub struct Query(T); + +impl Query { + /// Deconstruct to a inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl ops::Deref for Query { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl ops::DerefMut for Query { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl fmt::Debug for Query { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl fmt::Display for Query { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// Extract typed information from from the request's query. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// use actix_web::{web, App}; +/// +/// #[derive(Debug, Deserialize)] +/// pub enum ResponseType { +/// Token, +/// Code +/// } +/// +/// #[derive(Deserialize)] +/// pub struct AuthRequest { +/// id: u64, +/// response_type: ResponseType, +/// } +/// +/// // Use `Query` extractor for query information. +/// // This handler get called only if request's query contains `username` field +/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` +/// fn index(info: web::Query) -> String { +/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html") +/// .route(web::get().to(index))); // <- use `Query` extractor +/// } +/// ``` +impl FromRequest

for Query +where + T: de::DeserializeOwned, +{ + type Error = Error; + type Future = Result; + + #[inline] + fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { + serde_urlencoded::from_str::(req.query_string()) + .map(|val| Ok(Query(val))) + .unwrap_or_else(|e| Err(e.into())) + } +} diff --git a/src/middleware/identity.rs b/src/middleware/identity.rs index 7cf739bc4..f3ccca932 100644 --- a/src/middleware/identity.rs +++ b/src/middleware/identity.rs @@ -145,7 +145,6 @@ struct IdentityItem { impl

FromRequest

for Identity { type Error = Error; type Future = Result; - type Config = (); #[inline] fn from_request(req: &mut ServiceFromRequest

) -> Self::Future { diff --git a/src/request.rs b/src/request.rs index 717514838..5517302f0 100644 --- a/src/request.rs +++ b/src/request.rs @@ -205,7 +205,6 @@ impl HttpMessage for HttpRequest { impl

FromRequest

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

) -> Self::Future { diff --git a/src/route.rs b/src/route.rs index c189c61b1..1955a81ad 100644 --- a/src/route.rs +++ b/src/route.rs @@ -6,7 +6,7 @@ use actix_http::{http::Method, Error, Extensions, Response}; use actix_service::{NewService, Service}; use futures::{Async, Future, IntoFuture, Poll}; -use crate::extract::{ConfigStorage, ExtractorConfig, FromRequest}; +use crate::extract::FromRequest; use crate::guard::{self, Guard}; use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler}; use crate::responder::Responder; @@ -40,7 +40,7 @@ type BoxedRouteNewService = Box< pub struct Route

{ service: BoxedRouteNewService, ServiceResponse>, guards: Rc>>, - config: ConfigStorage, + config: Option, config_ref: Rc>>>, } @@ -55,13 +55,13 @@ impl Route

{ ), )), guards: Rc::new(Vec::new()), - config: ConfigStorage::default(), + config: None, config_ref, } } - pub(crate) fn finish(self) -> Self { - *self.config_ref.borrow_mut() = self.config.storage.clone(); + pub(crate) fn finish(mut self) -> Self { + *self.config_ref.borrow_mut() = self.config.take().map(|e| Rc::new(e)); self } @@ -252,7 +252,6 @@ impl Route

{ T: FromRequest

+ 'static, R: Responder + 'static, { - T::Config::store_default(&mut self.config); self.service = Box::new(RouteNewService::new( Extract::new(self.config_ref.clone()) .and_then(Handler::new(handler).map_err(|_| panic!())), @@ -324,8 +323,11 @@ impl Route

{ /// )); /// } /// ``` - pub fn config(mut self, config: C) -> Self { - self.config.store(config); + pub fn config(mut self, config: C) -> Self { + if self.config.is_none() { + self.config = Some(Extensions::new()); + } + self.config.as_mut().unwrap().insert(config); self } } diff --git a/src/service.rs b/src/service.rs index 612fe4e89..08330282d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; use std::cell::{Ref, RefMut}; use std::fmt; use std::marker::PhantomData; @@ -271,13 +270,12 @@ impl

ServiceFromRequest

{ } /// Load extractor configuration - pub fn load_config(&self) -> Cow { + pub fn load_config(&self) -> Option<&T> { if let Some(ref ext) = self.config { - if let Some(cfg) = ext.get::() { - return Cow::Borrowed(cfg); - } + ext.get::() + } else { + None } - Cow::Owned(T::default()) } } diff --git a/src/state.rs b/src/state.rs index 2c623c70d..b70540c07 100644 --- a/src/state.rs +++ b/src/state.rs @@ -48,7 +48,6 @@ impl Clone for State { impl FromRequest

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

) -> Self::Future {