From d292c5023fc4b0391d10ec758d2df47191cbd376 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 2 Apr 2018 16:19:18 -0700 Subject: [PATCH] add String and Bytes extractor --- guide/src/qs_7.md | 1 - src/error.rs | 7 +++ src/extractor.rs | 117 +++++++++++++++++++++++++++++++++++++--------- src/handler.rs | 16 +++++++ src/json.rs | 94 ++++++++++++++++++------------------- src/lib.rs | 4 +- 6 files changed, 167 insertions(+), 72 deletions(-) diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index 04e6d426..fab21a34 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -265,7 +265,6 @@ a error in several cases: * content-length is greater than 256k * payload terminates with error. - ```rust # extern crate actix_web; # extern crate futures; diff --git a/src/error.rs b/src/error.rs index d709ce59..dc4ae78e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -115,6 +115,13 @@ impl ResponseError for DeError { } } +/// Return `BAD_REQUEST` for `Utf8Error` +impl ResponseError for Utf8Error { + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST) + } +} + /// Return `InternalServerError` for `HttpError`, /// Response generation can return `HttpError`, so it is internal error impl ResponseError for HttpError {} diff --git a/src/extractor.rs b/src/extractor.rs index 0b264589..2346365b 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -1,14 +1,17 @@ +use std::str; use std::ops::{Deref, DerefMut}; +use bytes::Bytes; use serde_urlencoded; use serde::de::{self, DeserializeOwned}; use futures::future::{Future, FutureResult, result}; +use encoding::all::UTF_8; +use encoding::types::{Encoding, DecoderTrap}; -use body::Binary; -use error::Error; -use handler::FromRequest; +use error::{Error, ErrorBadRequest}; +use handler::{Either, FromRequest}; use httprequest::HttpRequest; -use httpmessage::{MessageBody, UrlEncoded}; +use httpmessage::{HttpMessage, MessageBody, UrlEncoded}; use de::PathDeserializer; /// Extract typed information from the request's path. @@ -173,20 +176,6 @@ impl FromRequest for Query } } -/// Request payload extractor. -/// -/// Loads request's payload and construct Binary instance. -impl FromRequest for Binary -{ - type Result = Box>; - - #[inline] - fn from_request(req: &HttpRequest) -> Self::Result { - Box::new( - MessageBody::new(req.clone()).from_err().map(|b| b.into())) - } -} - /// Extract typed information from the request's body. /// /// To extract typed information from request's body, the type `T` must implement the @@ -242,6 +231,79 @@ impl FromRequest for Form } } +/// Request payload extractor. +/// +/// Loads request's payload and construct Bytes instance. +/// +/// ## Example +/// +/// ```rust +/// extern crate bytes; +/// # extern crate actix_web; +/// use actix_web::{App, Result}; +/// +/// /// extract text data from request +/// fn index(body: bytes::Bytes) -> Result { +/// Ok(format!("Body {:?}!", body)) +/// } +/// # fn main() {} +/// ``` +impl FromRequest for Bytes +{ + type Result = Box>; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + Box::new(MessageBody::new(req.clone()).from_err()) + } +} + +/// Extract text information from the request's body. +/// +/// Text extractor automatically decode body according to the request's charset. +/// +/// ## Example +/// +/// ```rust +/// # extern crate actix_web; +/// use actix_web::{App, Result}; +/// +/// /// extract text data from request +/// fn index(body: String) -> Result { +/// Ok(format!("Body {}!", body)) +/// } +/// # fn main() {} +/// ``` +impl FromRequest for String +{ + type Result = Either, + Box>>; + + #[inline] + fn from_request(req: &HttpRequest) -> Self::Result { + let encoding = match req.encoding() { + Err(_) => return Either::A( + result(Err(ErrorBadRequest("Unknown request charset")))), + Ok(encoding) => encoding, + }; + + Either::B(Box::new( + MessageBody::new(req.clone()) + .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"))?) + } + }))) + } +} + #[cfg(test)] mod tests { use super::*; @@ -259,13 +321,26 @@ mod tests { } #[test] - fn test_binary() { + fn test_bytes() { let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11").finish(); req.payload_mut().unread_data(Bytes::from_static(b"hello=world")); - match Binary::from_request(&req).poll().unwrap() { + match Bytes::from_request(&req).poll().unwrap() { Async::Ready(s) => { - assert_eq!(s, Binary::from(Bytes::from_static(b"hello=world"))); + assert_eq!(s, Bytes::from_static(b"hello=world")); + }, + _ => unreachable!(), + } + } + + #[test] + fn test_string() { + 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() { + Async::Ready(s) => { + assert_eq!(s, "hello=world"); }, _ => unreachable!(), } diff --git a/src/handler.rs b/src/handler.rs index 08e49797..6041dc28 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,5 +1,6 @@ use std::ops::Deref; use std::marker::PhantomData; +use futures::Poll; use futures::future::{Future, FutureResult, ok, err}; use error::Error; @@ -96,6 +97,21 @@ impl Responder for Either } } +impl Future for Either + where A: Future, + B: Future, +{ + type Item = I; + type Error = E; + + fn poll(&mut self) -> Poll { + match *self { + Either::A(ref mut fut) => fut.poll(), + Either::B(ref mut fut) => fut.poll(), + } + } +} + /// Convenience trait that converts `Future` object to a `Boxed` future /// /// For example loading json from request's body is async operation. diff --git a/src/json.rs b/src/json.rs index 63e58fea..3c8f81e8 100644 --- a/src/json.rs +++ b/src/json.rs @@ -19,54 +19,6 @@ use httpresponse::HttpResponse; /// /// 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. -/// -/// 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 -/// # extern crate actix_web; -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// # -/// #[derive(Serialize)] -/// struct MyObj { -/// name: String, -/// } -/// -/// fn index(req: HttpRequest) -> Result> { -/// Ok(Json(MyObj{name: req.match_info().query("name")?})) -/// } -/// # fn main() {} -/// ``` -/// -/// To extract typed information from request's body, the type `T` must implement the -/// `Deserialize` trait from *serde*. -/// -/// ## Example -/// -/// ```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 -/// 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)); // <- use `with` extractor -/// } -/// ``` pub struct Json(pub T); impl Deref for Json { @@ -95,6 +47,26 @@ impl fmt::Display for Json where T: fmt::Display { } } +/// 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 +/// # extern crate actix_web; +/// # #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// # +/// #[derive(Serialize)] +/// struct MyObj { +/// name: String, +/// } +/// +/// fn index(req: HttpRequest) -> Result> { +/// Ok(Json(MyObj{name: req.match_info().query("name")?})) +/// } +/// # fn main() {} +/// ``` impl Responder for Json { type Item = HttpResponse; type Error = Error; @@ -108,6 +80,32 @@ impl Responder for Json { } } +/// To extract typed information from request's body, the type `T` must implement the +/// `Deserialize` trait from *serde*. +/// +/// ## Example +/// +/// ```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 +/// 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)); // <- use `with` extractor +/// } +/// ``` impl FromRequest for Json where T: DeserializeOwned + 'static, S: 'static { diff --git a/src/lib.rs b/src/lib.rs index 11f1c00a..3e134c44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,7 +143,7 @@ pub use application::App; pub use httpmessage::HttpMessage; pub use httprequest::HttpRequest; pub use httpresponse::HttpResponse; -pub use handler::{Either, Responder, AsyncResponder, FutureResponse, State}; +pub use handler::{Either, Responder, AsyncResponder, FromRequest, FutureResponse, State}; pub use context::HttpContext; pub use server::HttpServer; @@ -179,7 +179,7 @@ pub mod dev { pub use context::Drain; pub use json::JsonBody; pub use info::ConnectionInfo; - pub use handler::{Handler, Reply, FromRequest}; + pub use handler::{Handler, Reply}; pub use route::Route; pub use router::{Router, Resource, ResourceType}; pub use resource::ResourceHandler;