diff --git a/CHANGES.md b/CHANGES.md index 2790c88e2..640c05e93 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,9 +2,7 @@ ## 0.4.11 -* Added `Route::with()` handler, uses request extractor - -* Added `HttpReuqest::extract_xxx()`, type safe path/query information extractor +* Type-safe path/query parameter handling, using serde #70 * Router cannot parse Non-ASCII characters in URL #137 diff --git a/src/extractor.rs b/src/extractor.rs index eb4a2c84e..8d8189f97 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -305,6 +305,8 @@ impl<'de, S: 'de> Deserializer<'de> for PathExtractor<'de, S> #[cfg(test)] mod tests { + use futures::Async; + use super::*; use router::{Router, Pattern}; use resource::Resource; use test::TestRequest; @@ -332,15 +334,27 @@ mod tests { let (router, _) = Router::new("", ServerSettings::default(), routes); assert!(router.recognize(&mut req).is_some()); - let s: MyStruct = req.extract_path().unwrap(); - assert_eq!(s.key, "name"); - assert_eq!(s.value, "user1"); + match Path::::extract(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.key, "name"); + assert_eq!(s.value, "user1"); + }, + _ => unreachable!(), + } - let s: (String, String) = req.extract_path().unwrap(); - assert_eq!(s.0, "name"); - assert_eq!(s.1, "user1"); + match Path::<(String, String), _>::extract(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.0, "name"); + assert_eq!(s.1, "user1"); + }, + _ => unreachable!(), + } - let s: Id = req.extract_query().unwrap(); - assert_eq!(s.id, "test"); + match Query::::extract(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.id, "test"); + }, + _ => unreachable!(), + } } } diff --git a/src/handler.rs b/src/handler.rs index e688a35f9..7b8f2d480 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -155,14 +155,6 @@ impl Reply { _ => None, } } - - #[cfg(test)] - pub(crate) fn into_future(self) -> Box> { - match self.0 { - ReplyItem::Future(fut) => fut, - _ => panic!(), - } - } } impl Responder for Reply { diff --git a/src/httprequest.rs b/src/httprequest.rs index 2954cd5b6..91523321b 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -5,13 +5,12 @@ use std::net::SocketAddr; use std::borrow::Cow; use bytes::Bytes; use cookie::Cookie; -use futures::{Async, Future, Stream, Poll}; +use futures::{Async, Stream, Poll}; use futures_cpupool::CpuPool; use failure; use url::{Url, form_urlencoded}; use http::{header, Uri, Method, Version, HeaderMap, Extensions, StatusCode}; use tokio_io::AsyncRead; -use serde::de; use percent_encoding::percent_decode; use body::Body; @@ -22,8 +21,7 @@ use payload::Payload; use httpmessage::HttpMessage; use httpresponse::{HttpResponse, HttpResponseBuilder}; use helpers::SharedHttpInnerMessage; -use extractor::{Path, Query, HttpRequestExtractor}; -use error::{Error, UrlGenerationError, CookieParseError, PayloadError}; +use error::{UrlGenerationError, CookieParseError, PayloadError}; pub struct HttpInnerMessage { @@ -406,96 +404,6 @@ impl HttpRequest { unsafe{ mem::transmute(&mut self.as_mut().params) } } - /// Extract typed information from request's path. - /// - /// By default, in case of error `BAD_REQUEST` response get returned to peer. - /// If you need to return different response use `map_err()` method. - /// - /// ## Example - /// - /// ```rust - /// # extern crate actix_web; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::*; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// fn index(mut req: HttpRequest) -> Result { - /// let info: Info = req.extract_path()?; // <- extract path info using serde - /// Ok(format!("Welcome {}!", info.username)) - /// } - /// - /// fn main() { - /// let app = Application::new() - /// .resource("/{username}/index.html", // <- define path parameters - /// |r| r.method(Method::GET).f(index)); - /// } - /// ``` - pub fn extract_path(&self) -> Result - where S: 'static, - T: de::DeserializeOwned, - { - match Path::::extract(self).poll()? { - Async::Ready(val) => Ok(val.into().0), - _ => unreachable!() - } - } - - /// Extract typed information from request's query string. - /// - /// ## Example - /// - /// ```rust - /// # extern crate actix_web; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{HttpRequest, Result}; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// fn index(mut req: HttpRequest) -> Result { - /// let info: Info = req.extract_query()?; // <- extract query info, i.e: /?id=username - /// Ok(format!("Welcome {}!", info.username)) - /// } - /// # fn main() {} - /// ``` - /// - /// By default, in case of error, `BAD_REQUEST` response get returned to peer. - /// If you need to return different response use `map_err()` method. - /// - /// ```rust - /// # extern crate actix_web; - /// #[macro_use] extern crate serde_derive; - /// use actix_web::{HttpRequest, Result, error}; - /// - /// #[derive(Deserialize)] - /// struct Info { - /// username: String, - /// } - /// - /// fn index(mut req: HttpRequest) -> Result { - /// let info: Info = req.extract_query() // <- extract query information - /// .map_err(error::ErrorInternalServerError)?; // <- return 500 in case of error - /// Ok(format!("Welcome {}!", info.username)) - /// } - /// # fn main() {} - /// ``` - /// - pub fn extract_query(&self) -> Result - where S: 'static, - T: de::DeserializeOwned, - { - match Query::::extract(self).poll()? { - Async::Ready(val) => Ok(val.into().0), - _ => unreachable!() - } - } - /// Checks if a connection should be kept alive. pub fn keep_alive(&self) -> bool { self.as_ref().keep_alive() diff --git a/src/with.rs b/src/with.rs index cf93fe017..132a252a8 100644 --- a/src/with.rs +++ b/src/with.rs @@ -62,31 +62,17 @@ impl Handler for With type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { - let mut fut = T::extract(&req); + let mut fut = WithHandlerFut{ + req, + started: false, + hnd: Rc::clone(&self.hnd), + fut1: None, + fut2: None, + }; + match fut.poll() { - Ok(Async::Ready(item)) => { - let hnd: &mut H = unsafe{&mut *self.hnd.get()}; - match hnd.handle(item).respond_to(req.without_state()) { - Ok(item) => match item.into().into() { - ReplyItem::Message(resp) => Reply::response(resp), - ReplyItem::Future(fut) => Reply::async( - WithHandlerFut{ - req, - hnd: Rc::clone(&self.hnd), - fut1: None, - fut2: Some(fut), - }) - }, - Err(e) => Reply::response(e.into()), - } - } - Ok(Async::NotReady) => Reply::async( - WithHandlerFut{ - req, - hnd: Rc::clone(&self.hnd), - fut1: Some(Box::new(fut)), - fut2: None, - }), + Ok(Async::Ready(resp)) => Reply::response(resp), + Ok(Async::NotReady) => Reply::async(fut), Err(e) => Reply::response(e), } } @@ -97,6 +83,7 @@ struct WithHandlerFut T: HttpRequestExtractor, T: 'static, S: 'static { + started: bool, hnd: Rc>, req: HttpRequest, fut1: Option>>, @@ -116,15 +103,26 @@ impl Future for WithHandlerFut return fut.poll() } - let item = match self.fut1.as_mut().unwrap().poll()? { - Async::Ready(item) => item, - Async::NotReady => return Ok(Async::NotReady), + let item = if !self.started { + self.started = true; + let mut fut = T::extract(&self.req); + match fut.poll() { + Ok(Async::Ready(item)) => item, + Ok(Async::NotReady) => { + self.fut1 = Some(Box::new(fut)); + return Ok(Async::NotReady) + }, + Err(e) => return Err(e), + } + } else { + match self.fut1.as_mut().unwrap().poll()? { + Async::Ready(item) => item, + Async::NotReady => return Ok(Async::NotReady), + } }; let hnd: &mut H = unsafe{&mut *self.hnd.get()}; - let item = match hnd.handle(item) - .respond_to(self.req.without_state()) - { + let item = match hnd.handle(item).respond_to(self.req.without_state()) { Ok(item) => item.into(), Err(err) => return Err(err.into()), }; @@ -172,50 +170,18 @@ impl Handler for With2 type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { - let mut fut = T1::extract(&req); + let mut fut = WithHandlerFut2{ + req, + started: false, + hnd: Rc::clone(&self.hnd), + item: None, + fut1: None, + fut2: None, + fut3: None, + }; match fut.poll() { - Ok(Async::Ready(item1)) => { - let mut fut = T2::extract(&req); - match fut.poll() { - Ok(Async::Ready(item2)) => { - let hnd: &mut F = unsafe{&mut *self.hnd.get()}; - match (*hnd)(item1, item2).respond_to(req.without_state()) { - Ok(item) => match item.into().into() { - ReplyItem::Message(resp) => Reply::response(resp), - ReplyItem::Future(fut) => Reply::async( - WithHandlerFut2{ - req, - item: None, - hnd: Rc::clone(&self.hnd), - fut1: None, - fut2: None, - fut3: Some(fut), - }) - }, - Err(e) => Reply::response(e.into()), - } - }, - Ok(Async::NotReady) => Reply::async( - WithHandlerFut2{ - req, - hnd: Rc::clone(&self.hnd), - item: Some(item1), - fut1: None, - fut2: Some(Box::new(fut)), - fut3: None, - }), - Err(e) => Reply::response(e), - } - }, - Ok(Async::NotReady) => Reply::async( - WithHandlerFut2{ - req, - hnd: Rc::clone(&self.hnd), - item: None, - fut1: Some(Box::new(fut)), - fut2: None, - fut3: None, - }), + Ok(Async::Ready(resp)) => Reply::response(resp), + Ok(Async::NotReady) => Reply::async(fut), Err(e) => Reply::response(e), } } @@ -228,6 +194,7 @@ struct WithHandlerFut2 T2: HttpRequestExtractor + 'static, S: 'static { + started: bool, hnd: Rc>, req: HttpRequest, item: Option, @@ -251,6 +218,45 @@ impl Future for WithHandlerFut2 return fut.poll() } + if !self.started { + self.started = true; + let mut fut = T1::extract(&self.req); + match fut.poll() { + Ok(Async::Ready(item1)) => { + let mut fut = T2::extract(&self.req); + match fut.poll() { + Ok(Async::Ready(item2)) => { + let hnd: &mut F = unsafe{&mut *self.hnd.get()}; + match (*hnd)(item1, item2) + .respond_to(self.req.without_state()) + { + Ok(item) => match item.into().into() { + ReplyItem::Message(resp) => + return Ok(Async::Ready(resp)), + ReplyItem::Future(fut) => { + self.fut3 = Some(fut); + return self.poll() + } + }, + Err(e) => return Err(e.into()), + } + }, + Ok(Async::NotReady) => { + self.item = Some(item1); + self.fut2 = Some(Box::new(fut)); + return Ok(Async::NotReady); + }, + Err(e) => return Err(e), + } + }, + Ok(Async::NotReady) => { + self.fut1 = Some(Box::new(fut)); + return Ok(Async::NotReady); + } + Err(e) => return Err(e), + } + } + if self.fut1.is_some() { match self.fut1.as_mut().unwrap().poll()? { Async::Ready(item) => { @@ -322,19 +328,22 @@ impl Handler for With3 type Result = Reply; fn handle(&mut self, req: HttpRequest) -> Self::Result { - let fut = Box::new(T1::extract(&req)); - - Reply::async( - WithHandlerFut3{ - req, - hnd: Rc::clone(&self.hnd), - item1: None, - item2: None, - fut1: Some(fut), - fut2: None, - fut3: None, - fut4: None, - }) + let mut fut = WithHandlerFut3{ + req, + hnd: Rc::clone(&self.hnd), + started: false, + item1: None, + item2: None, + fut1: None, + fut2: None, + fut3: None, + fut4: None, + }; + match fut.poll() { + Ok(Async::Ready(resp)) => Reply::response(resp), + Ok(Async::NotReady) => Reply::async(fut), + Err(e) => Reply::response(e), + } } } @@ -348,6 +357,7 @@ struct WithHandlerFut3 { hnd: Rc>, req: HttpRequest, + started: bool, item1: Option, item2: Option, fut1: Option>>, @@ -372,6 +382,57 @@ impl Future for WithHandlerFut3 return fut.poll() } + if !self.started { + self.started = true; + let mut fut = T1::extract(&self.req); + match fut.poll() { + Ok(Async::Ready(item1)) => { + let mut fut = T2::extract(&self.req); + match fut.poll() { + Ok(Async::Ready(item2)) => { + let mut fut = T3::extract(&self.req); + match fut.poll() { + Ok(Async::Ready(item3)) => { + let hnd: &mut F = unsafe{&mut *self.hnd.get()}; + match (*hnd)(item1, item2, item3) + .respond_to(self.req.without_state()) + { + Ok(item) => match item.into().into() { + ReplyItem::Message(resp) => + return Ok(Async::Ready(resp)), + ReplyItem::Future(fut) => { + self.fut4 = Some(fut); + return self.poll() + } + }, + Err(e) => return Err(e.into()), + } + }, + Ok(Async::NotReady) => { + self.item1 = Some(item1); + self.item2 = Some(item2); + self.fut3 = Some(Box::new(fut)); + return Ok(Async::NotReady); + }, + Err(e) => return Err(e), + } + }, + Ok(Async::NotReady) => { + self.item1 = Some(item1); + self.fut2 = Some(Box::new(fut)); + return Ok(Async::NotReady); + }, + Err(e) => return Err(e), + } + }, + Ok(Async::NotReady) => { + self.fut1 = Some(Box::new(fut)); + return Ok(Async::NotReady); + } + Err(e) => return Err(e), + } + } + if self.fut1.is_some() { match self.fut1.as_mut().unwrap().poll()? { Async::Ready(item) => {