mirror of
https://github.com/actix/actix-web.git
synced 2024-11-29 21:11:17 +00:00
add handler with exatractor
This commit is contained in:
parent
b03c7051ff
commit
2f60a4b89d
11 changed files with 348 additions and 43 deletions
|
@ -2,7 +2,9 @@
|
||||||
|
|
||||||
## 0.4.11
|
## 0.4.11
|
||||||
|
|
||||||
* Added `HttpReuqest::extract_xxx()`, type safe path/query information extractor.
|
* Added `Route::with()` handler, uses request extractor
|
||||||
|
|
||||||
|
* Added `HttpReuqest::extract_xxx()`, type safe path/query information extractor
|
||||||
|
|
||||||
* Fix long client urls #129
|
* Fix long client urls #129
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,11 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
.responder()
|
.responder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This handler uses `With` helper for loading serde json object.
|
||||||
|
fn extract_item(_: &HttpRequest, item: Json<MyObj>) -> Result<HttpResponse> {
|
||||||
|
println!("model: {:?}", &item);
|
||||||
|
httpcodes::HTTPOk.build().json(item.0) // <- send response
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_SIZE: usize = 262_144; // max payload size is 256k
|
const MAX_SIZE: usize = 262_144; // max payload size is 256k
|
||||||
|
|
||||||
|
@ -73,7 +78,6 @@ fn index_mjsonrust(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Erro
|
||||||
Ok(HttpResponse::build(StatusCode::OK)
|
Ok(HttpResponse::build(StatusCode::OK)
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(injson.dump()).unwrap())
|
.body(injson.dump()).unwrap())
|
||||||
|
|
||||||
})
|
})
|
||||||
.responder()
|
.responder()
|
||||||
}
|
}
|
||||||
|
@ -87,6 +91,8 @@ fn main() {
|
||||||
Application::new()
|
Application::new()
|
||||||
// enable logger
|
// enable logger
|
||||||
.middleware(middleware::Logger::default())
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/extractor/{name}/{number}/",
|
||||||
|
|r| r.method(Method::GET).with(extract_item))
|
||||||
.resource("/manual", |r| r.method(Method::POST).f(index_manual))
|
.resource("/manual", |r| r.method(Method::POST).f(index_manual))
|
||||||
.resource("/mjsonrust", |r| r.method(Method::POST).f(index_mjsonrust))
|
.resource("/mjsonrust", |r| r.method(Method::POST).f(index_mjsonrust))
|
||||||
.resource("/", |r| r.method(Method::POST).f(index))})
|
.resource("/", |r| r.method(Method::POST).f(index))})
|
||||||
|
|
104
src/extractor.rs
104
src/extractor.rs
|
@ -1,11 +1,18 @@
|
||||||
use serde_urlencoded;
|
use std::ops::{Deref, DerefMut};
|
||||||
use serde::de::{self, Deserializer, Visitor, Error as DeError};
|
|
||||||
|
|
||||||
|
use serde_urlencoded;
|
||||||
|
use serde::de::{self, Deserializer, DeserializeOwned, Visitor, Error as DeError};
|
||||||
|
use futures::future::{Future, FutureResult, result};
|
||||||
|
|
||||||
|
use error::Error;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
|
|
||||||
pub trait HttpRequestExtractor<'de> {
|
|
||||||
fn extract<T, S>(&self, req: &'de HttpRequest<S>) -> Result<T, de::value::Error>
|
pub trait HttpRequestExtractor<T>: Sized where T: DeserializeOwned
|
||||||
where T: de::Deserialize<'de>, S: 'static;
|
{
|
||||||
|
type Result: Future<Item=Self, Error=Error>;
|
||||||
|
|
||||||
|
fn extract<S: 'static>(req: &HttpRequest<S>) -> Self::Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract typed information from the request's path.
|
/// Extract typed information from the request's path.
|
||||||
|
@ -18,32 +25,49 @@ pub trait HttpRequestExtractor<'de> {
|
||||||
/// # extern crate futures;
|
/// # extern crate futures;
|
||||||
/// #[macro_use] extern crate serde_derive;
|
/// #[macro_use] extern crate serde_derive;
|
||||||
/// use actix_web::*;
|
/// use actix_web::*;
|
||||||
/// use actix_web::dev::{Path, HttpRequestExtractor};
|
/// use actix_web::Path;
|
||||||
///
|
///
|
||||||
/// #[derive(Deserialize)]
|
/// #[derive(Deserialize)]
|
||||||
/// struct Info {
|
/// struct Info {
|
||||||
/// username: String,
|
/// username: String,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn index(mut req: HttpRequest) -> Result<String> {
|
/// /// extract path info using serde
|
||||||
/// let info: Info = Path.extract(&req)?; // <- extract path info using serde
|
/// fn index(req: &HttpRequest, info: Path<Info>) -> Result<String> {
|
||||||
/// Ok(format!("Welcome {}!", info.username))
|
/// Ok(format!("Welcome {}!", info.username))
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = Application::new()
|
/// let app = Application::new().resource(
|
||||||
/// .resource("/{username}/index.html", // <- define path parameters
|
/// "/{username}/index.html", // <- define path parameters
|
||||||
/// |r| r.method(Method::GET).f(index));
|
/// |r| r.method(Method::GET).with(index)); // <- use `with` extractor
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Path;
|
pub struct Path<T>(pub T);
|
||||||
|
|
||||||
impl<'de> HttpRequestExtractor<'de> for Path {
|
impl<T> Deref for Path<T> {
|
||||||
#[inline]
|
type Target = T;
|
||||||
fn extract<T, S>(&self, req: &'de HttpRequest<S>) -> Result<T, de::value::Error>
|
|
||||||
where T: de::Deserialize<'de>, S: 'static,
|
fn deref(&self) -> &T {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for Path<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HttpRequestExtractor<T> for Path<T> where T: DeserializeOwned
|
||||||
{
|
{
|
||||||
de::Deserialize::deserialize(PathExtractor{req: req})
|
type Result = FutureResult<Self, Error>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn extract<S: 'static>(req: &HttpRequest<S>) -> Self::Result {
|
||||||
|
result(de::Deserialize::deserialize(PathExtractor{req})
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.map(Path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,28 +81,50 @@ impl<'de> HttpRequestExtractor<'de> for Path {
|
||||||
/// # extern crate futures;
|
/// # extern crate futures;
|
||||||
/// #[macro_use] extern crate serde_derive;
|
/// #[macro_use] extern crate serde_derive;
|
||||||
/// use actix_web::*;
|
/// use actix_web::*;
|
||||||
/// use actix_web::dev::{Query, HttpRequestExtractor};
|
/// use actix_web::Query;
|
||||||
///
|
///
|
||||||
/// #[derive(Deserialize)]
|
/// #[derive(Deserialize)]
|
||||||
/// struct Info {
|
/// struct Info {
|
||||||
/// username: String,
|
/// username: String,
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn index(mut req: HttpRequest) -> Result<String> {
|
/// // use `with` extractor for query info
|
||||||
/// let info: Info = Query.extract(&req)?; // <- extract query info using serde
|
/// // this handler get called only if request's query contains `username` field
|
||||||
|
/// fn index(req: &HttpRequest, info: Query<Info>) -> Result<String> {
|
||||||
/// Ok(format!("Welcome {}!", info.username))
|
/// Ok(format!("Welcome {}!", info.username))
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// # fn main() {}
|
/// fn main() {
|
||||||
|
/// let app = Application::new().resource(
|
||||||
|
/// "/index.html",
|
||||||
|
/// |r| r.method(Method::GET).with(index)); // <- use `with` extractor
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Query;
|
pub struct Query<T>(pub T);
|
||||||
|
|
||||||
impl<'de> HttpRequestExtractor<'de> for Query {
|
impl<T> Deref for Query<T> {
|
||||||
#[inline]
|
type Target = T;
|
||||||
fn extract<T, S>(&self, req: &'de HttpRequest<S>) -> Result<T, de::value::Error>
|
|
||||||
where T: de::Deserialize<'de>, S: 'static,
|
fn deref(&self) -> &T {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for Query<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HttpRequestExtractor<T> for Query<T> where T: de::DeserializeOwned
|
||||||
{
|
{
|
||||||
serde_urlencoded::from_str::<T>(req.query_string())
|
type Result = FutureResult<Self, Error>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn extract<S: 'static>(req: &HttpRequest<S>) -> Self::Result {
|
||||||
|
result(serde_urlencoded::from_str::<T>(req.query_string())
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.map(Query))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,11 +138,11 @@ macro_rules! unsupported_type {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PathExtractor<'de, S: 'static> {
|
pub struct PathExtractor<'de, S: 'de> {
|
||||||
req: &'de HttpRequest<S>
|
req: &'de HttpRequest<S>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de, S: 'static> Deserializer<'de> for PathExtractor<'de, S>
|
impl<'de, S: 'de> Deserializer<'de> for PathExtractor<'de, S>
|
||||||
{
|
{
|
||||||
type Error = de::value::Error;
|
type Error = de::value::Error;
|
||||||
|
|
||||||
|
|
|
@ -155,6 +155,14 @@ impl Reply {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn into_future(self) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
match self.0 {
|
||||||
|
ReplyItem::Future(fut) => fut,
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Responder for Reply {
|
impl Responder for Reply {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::rc::Rc;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
use futures::{Async, Stream, Poll};
|
use futures::{Async, Future, Stream, Poll};
|
||||||
use futures_cpupool::CpuPool;
|
use futures_cpupool::CpuPool;
|
||||||
use failure;
|
use failure;
|
||||||
use url::{Url, form_urlencoded};
|
use url::{Url, form_urlencoded};
|
||||||
|
@ -426,11 +426,14 @@ impl<S> HttpRequest<S> {
|
||||||
/// |r| r.method(Method::GET).f(index));
|
/// |r| r.method(Method::GET).f(index));
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn extract_path<'a, T>(&'a self) -> Result<T, Error>
|
pub fn extract_path<T>(&self) -> Result<T, Error>
|
||||||
where S: 'static,
|
where S: 'static,
|
||||||
T: de::Deserialize<'a>,
|
T: de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
Ok(Path.extract(self)?)
|
match Path::<T>::extract(self).poll()? {
|
||||||
|
Async::Ready(val) => Ok(val.0),
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract typed information from request's query string.
|
/// Extract typed information from request's query string.
|
||||||
|
@ -475,11 +478,14 @@ impl<S> HttpRequest<S> {
|
||||||
/// # fn main() {}
|
/// # fn main() {}
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
pub fn extract_query<'a, T>(&'a self) -> Result<T, Error>
|
pub fn extract_query<T>(&self) -> Result<T, Error>
|
||||||
where S: 'static,
|
where S: 'static,
|
||||||
T: de::Deserialize<'a>,
|
T: de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
Ok(Query.extract(self)?)
|
match Query::<T>::extract(self).poll()? {
|
||||||
|
Async::Ready(val) => Ok(val.0),
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a connection should be kept alive.
|
/// Checks if a connection should be kept alive.
|
||||||
|
|
53
src/json.rs
53
src/json.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use std::fmt;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures::{Poll, Future, Stream};
|
use futures::{Poll, Future, Stream};
|
||||||
use http::header::CONTENT_LENGTH;
|
use http::header::CONTENT_LENGTH;
|
||||||
|
@ -12,6 +13,7 @@ use handler::Responder;
|
||||||
use httpmessage::HttpMessage;
|
use httpmessage::HttpMessage;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
use extractor::HttpRequestExtractor;
|
||||||
|
|
||||||
/// Json response helper
|
/// Json response helper
|
||||||
///
|
///
|
||||||
|
@ -34,7 +36,19 @@ use httpresponse::HttpResponse;
|
||||||
/// }
|
/// }
|
||||||
/// # fn main() {}
|
/// # fn main() {}
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Json<T: Serialize> (pub T);
|
pub struct Json<T>(pub T);
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for Json<T> where T: fmt::Debug {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Json: {:?}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Display for Json<T> where T: fmt::Display {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Serialize> Responder for Json<T> {
|
impl<T: Serialize> Responder for Json<T> {
|
||||||
type Item = HttpResponse;
|
type Item = HttpResponse;
|
||||||
|
@ -49,6 +63,19 @@ impl<T: Serialize> Responder for Json<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> HttpRequestExtractor<T> for Json<T> where T: DeserializeOwned + 'static
|
||||||
|
{
|
||||||
|
type Result = Box<Future<Item=Self, Error=Error>>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn extract<S: 'static>(req: &HttpRequest<S>) -> Self::Result {
|
||||||
|
Box::new(
|
||||||
|
JsonBody::new(req.clone())
|
||||||
|
.from_err()
|
||||||
|
.map(Json))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Request payload json parser that resolves to a deserialized `T` value.
|
/// Request payload json parser that resolves to a deserialized `T` value.
|
||||||
///
|
///
|
||||||
/// Returns error:
|
/// Returns error:
|
||||||
|
@ -160,6 +187,9 @@ mod tests {
|
||||||
use http::header;
|
use http::header;
|
||||||
use futures::Async;
|
use futures::Async;
|
||||||
|
|
||||||
|
use with::with;
|
||||||
|
use handler::Handler;
|
||||||
|
|
||||||
impl PartialEq for JsonPayloadError {
|
impl PartialEq for JsonPayloadError {
|
||||||
fn eq(&self, other: &JsonPayloadError) -> bool {
|
fn eq(&self, other: &JsonPayloadError) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -215,6 +245,25 @@ mod tests {
|
||||||
header::HeaderValue::from_static("16"));
|
header::HeaderValue::from_static("16"));
|
||||||
req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
|
req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
|
||||||
let mut json = req.json::<MyObject>();
|
let mut json = req.json::<MyObject>();
|
||||||
assert_eq!(json.poll().ok().unwrap(), Async::Ready(MyObject{name: "test".to_owned()}));
|
assert_eq!(json.poll().ok().unwrap(),
|
||||||
|
Async::Ready(MyObject{name: "test".to_owned()}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_json() {
|
||||||
|
let mut handler = with(|_: &_, data: Json<MyObject>| data);
|
||||||
|
|
||||||
|
let req = HttpRequest::default();
|
||||||
|
let mut json = handler.handle(req).into_future();
|
||||||
|
assert!(json.poll().is_err());
|
||||||
|
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
req.headers_mut().insert(header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("application/json"));
|
||||||
|
req.headers_mut().insert(header::CONTENT_LENGTH,
|
||||||
|
header::HeaderValue::from_static("16"));
|
||||||
|
req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
|
||||||
|
let mut json = handler.handle(req).into_future();
|
||||||
|
assert!(json.poll().is_ok())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,6 +119,7 @@ mod resource;
|
||||||
mod param;
|
mod param;
|
||||||
mod payload;
|
mod payload;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
mod with;
|
||||||
mod extractor;
|
mod extractor;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
@ -144,6 +145,7 @@ pub use route::Route;
|
||||||
pub use resource::Resource;
|
pub use resource::Resource;
|
||||||
pub use context::HttpContext;
|
pub use context::HttpContext;
|
||||||
pub use server::HttpServer;
|
pub use server::HttpServer;
|
||||||
|
pub use extractor::{Path, Query, HttpRequestExtractor};
|
||||||
|
|
||||||
// re-exports
|
// re-exports
|
||||||
pub use http::{Method, StatusCode, Version};
|
pub use http::{Method, StatusCode, Version};
|
||||||
|
@ -184,10 +186,10 @@ pub mod dev {
|
||||||
pub use context::Drain;
|
pub use context::Drain;
|
||||||
pub use info::ConnectionInfo;
|
pub use info::ConnectionInfo;
|
||||||
pub use handler::Handler;
|
pub use handler::Handler;
|
||||||
|
pub use with::With;
|
||||||
pub use json::JsonBody;
|
pub use json::JsonBody;
|
||||||
pub use router::{Router, Pattern};
|
pub use router::{Router, Pattern};
|
||||||
pub use param::{FromParam, Params};
|
pub use param::{FromParam, Params};
|
||||||
pub use httpmessage::{UrlEncoded, MessageBody};
|
pub use httpmessage::{UrlEncoded, MessageBody};
|
||||||
pub use httpresponse::HttpResponseBuilder;
|
pub use httpresponse::HttpResponseBuilder;
|
||||||
pub use extractor::{Path, Query, HttpRequestExtractor};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,14 +233,14 @@ impl FormatText {
|
||||||
FormatText::Time => {
|
FormatText::Time => {
|
||||||
let response_time = time::now() - entry_time;
|
let response_time = time::now() - entry_time;
|
||||||
let response_time = response_time.num_seconds() as f64 +
|
let response_time = response_time.num_seconds() as f64 +
|
||||||
(response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000000.0;
|
(response_time.num_nanoseconds().unwrap_or(0) as f64)/1_000_000_000.0;
|
||||||
|
|
||||||
fmt.write_fmt(format_args!("{:.6}", response_time))
|
fmt.write_fmt(format_args!("{:.6}", response_time))
|
||||||
},
|
},
|
||||||
FormatText::TimeMillis => {
|
FormatText::TimeMillis => {
|
||||||
let response_time = time::now() - entry_time;
|
let response_time = time::now() - entry_time;
|
||||||
let response_time_ms = (response_time.num_seconds() * 1000) as f64 +
|
let response_time_ms = (response_time.num_seconds() * 1000) as f64 +
|
||||||
(response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000.0;
|
(response_time.num_nanoseconds().unwrap_or(0) as f64)/1_000_000.0;
|
||||||
|
|
||||||
fmt.write_fmt(format_args!("{:.6}", response_time_ms))
|
fmt.write_fmt(format_args!("{:.6}", response_time_ms))
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::marker::PhantomData;
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use http::{Method, StatusCode};
|
use http::{Method, StatusCode};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
use pred;
|
use pred;
|
||||||
use body::Body;
|
use body::Body;
|
||||||
|
@ -11,6 +12,8 @@ use handler::{Reply, Handler, Responder};
|
||||||
use middleware::Middleware;
|
use middleware::Middleware;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
use with::WithHandler;
|
||||||
|
use extractor::HttpRequestExtractor;
|
||||||
|
|
||||||
/// *Resource* is an entry in route table which corresponds to requested URL.
|
/// *Resource* is an entry in route table which corresponds to requested URL.
|
||||||
///
|
///
|
||||||
|
@ -132,6 +135,22 @@ impl<S: 'static> Resource<S> {
|
||||||
self.routes.last_mut().unwrap().f(handler)
|
self.routes.last_mut().unwrap().f(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a new route and add handler.
|
||||||
|
///
|
||||||
|
/// This is shortcut for:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// Resource::resource("/", |r| r.route().with(index)
|
||||||
|
/// ```
|
||||||
|
pub fn with<T, D, H>(&mut self, handler: H)
|
||||||
|
where H: WithHandler<T, D, S>,
|
||||||
|
D: HttpRequestExtractor<T> + 'static,
|
||||||
|
T: DeserializeOwned + 'static,
|
||||||
|
{
|
||||||
|
self.routes.push(Route::default());
|
||||||
|
self.routes.last_mut().unwrap().with(handler)
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a middleware
|
/// Register a middleware
|
||||||
///
|
///
|
||||||
/// This is similar to `Application's` middlewares, but
|
/// This is similar to `Application's` middlewares, but
|
||||||
|
|
37
src/route.rs
37
src/route.rs
|
@ -1,6 +1,7 @@
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
@ -11,6 +12,8 @@ use middleware::{Middleware, Response as MiddlewareResponse, Started as Middlewa
|
||||||
use httpcodes::HttpNotFound;
|
use httpcodes::HttpNotFound;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
use with::{with, WithHandler};
|
||||||
|
use extractor::HttpRequestExtractor;
|
||||||
|
|
||||||
/// Resource route definition
|
/// Resource route definition
|
||||||
///
|
///
|
||||||
|
@ -107,6 +110,40 @@ impl<S: 'static> Route<S> {
|
||||||
{
|
{
|
||||||
self.handler = InnerHandler::async(handler);
|
self.handler = InnerHandler::async(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set handler function with http request extractor.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate bytes;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate futures;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::*;
|
||||||
|
/// use actix_web::{with, Path, HttpRequestExtractor};
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Info {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// extract path info using serde
|
||||||
|
/// fn index(req: &HttpRequest, info: Path<Info>) -> Result<String> {
|
||||||
|
/// Ok(format!("Welcome {}!", info.username))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = Application::new().resource(
|
||||||
|
/// "/{username}/index.html", // <- define path parameters
|
||||||
|
/// |r| r.method(Method::GET).with(index)); // <- use `with` extractor
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn with<T, D, H>(&mut self, handler: H)
|
||||||
|
where H: WithHandler<T, D, S>,
|
||||||
|
D: HttpRequestExtractor<T> + 'static,
|
||||||
|
T: DeserializeOwned + 'static,
|
||||||
|
{
|
||||||
|
self.h(with(handler))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `RouteHandler` wrapper. This struct is required because it needs to be shared
|
/// `RouteHandler` wrapper. This struct is required because it needs to be shared
|
||||||
|
|
130
src/with.rs
Normal file
130
src/with.rs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::UnsafeCell;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use futures::{Async, Future, Poll};
|
||||||
|
|
||||||
|
use error::Error;
|
||||||
|
use handler::{Handler, Reply, ReplyItem, Responder};
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
use extractor::HttpRequestExtractor;
|
||||||
|
|
||||||
|
|
||||||
|
/// Trait defines object that could be registered as route handler
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub trait WithHandler<T, D, S>: 'static
|
||||||
|
where D: HttpRequestExtractor<T>, T: DeserializeOwned
|
||||||
|
{
|
||||||
|
/// The type of value that handler will return.
|
||||||
|
type Result: Responder;
|
||||||
|
|
||||||
|
/// Handle request
|
||||||
|
fn handle(&mut self, req: &HttpRequest<S>, data: D) -> Self::Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// WithHandler<D, T, S> for Fn()
|
||||||
|
impl<T, D, S, F, R> WithHandler<T, D, S> for F
|
||||||
|
where F: Fn(&HttpRequest<S>, D) -> R + 'static,
|
||||||
|
R: Responder + 'static,
|
||||||
|
D: HttpRequestExtractor<T>,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
type Result = R;
|
||||||
|
|
||||||
|
fn handle(&mut self, req: &HttpRequest<S>, item: D) -> R {
|
||||||
|
(self)(req, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with<T, D, S, H>(h: H) -> With<T, D, S, H>
|
||||||
|
where H: WithHandler<T, D, S>,
|
||||||
|
D: HttpRequestExtractor<T>,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
With{hnd: Rc::new(UnsafeCell::new(h)),
|
||||||
|
_t: PhantomData, _d: PhantomData, _s: PhantomData}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct With<T, D, S, H>
|
||||||
|
where H: WithHandler<T, D, S>,
|
||||||
|
D: HttpRequestExtractor<T>,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
hnd: Rc<UnsafeCell<H>>,
|
||||||
|
_t: PhantomData<T>,
|
||||||
|
_d: PhantomData<D>,
|
||||||
|
_s: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, D, S, H> Handler<S> for With<T, D, S, H>
|
||||||
|
where H: WithHandler<T, D, S>,
|
||||||
|
D: HttpRequestExtractor<T>,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
T: 'static, D: 'static, S: 'static
|
||||||
|
{
|
||||||
|
type Result = Reply;
|
||||||
|
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||||
|
let fut = Box::new(D::extract(&req));
|
||||||
|
|
||||||
|
Reply::async(
|
||||||
|
WithHandlerFut{
|
||||||
|
req,
|
||||||
|
hnd: Rc::clone(&self.hnd),
|
||||||
|
fut1: Some(fut),
|
||||||
|
fut2: None,
|
||||||
|
_t: PhantomData,
|
||||||
|
_d: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WithHandlerFut<T, D, S, H>
|
||||||
|
where H: WithHandler<T, D, S>,
|
||||||
|
D: HttpRequestExtractor<T>,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
T: 'static, D: 'static, S: 'static
|
||||||
|
{
|
||||||
|
hnd: Rc<UnsafeCell<H>>,
|
||||||
|
req: HttpRequest<S>,
|
||||||
|
fut1: Option<Box<Future<Item=D, Error=Error>>>,
|
||||||
|
fut2: Option<Box<Future<Item=HttpResponse, Error=Error>>>,
|
||||||
|
_t: PhantomData<T>,
|
||||||
|
_d: PhantomData<D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, D, S, H> Future for WithHandlerFut<T, D, S, H>
|
||||||
|
where H: WithHandler<T, D, S>,
|
||||||
|
D: HttpRequestExtractor<T>,
|
||||||
|
T: DeserializeOwned,
|
||||||
|
T: 'static, D: 'static, S: 'static
|
||||||
|
{
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
if let Some(ref mut fut) = self.fut2 {
|
||||||
|
return fut.poll()
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = 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(&self.req, item).respond_to(self.req.without_state())
|
||||||
|
{
|
||||||
|
Ok(item) => item.into(),
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match item.into() {
|
||||||
|
ReplyItem::Message(resp) => return Ok(Async::Ready(resp)),
|
||||||
|
ReplyItem::Future(fut) => self.fut2 = Some(fut),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.poll()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue