1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-27 17:22:57 +01:00

.to_async() handler can return Responder type #792

This commit is contained in:
Nikolay Kim 2019-04-22 14:22:08 -07:00
parent d00c9bb844
commit 48bee55087
6 changed files with 118 additions and 24 deletions

View File

@ -1,5 +1,14 @@
# Changes # Changes
### Added
* Add helper functions for reading response body `test::read_body()`
### Changed
* `.to_async()` handler can return `Responder` type #792
## [1.0.0-beta.1] - 2019-04-20 ## [1.0.0-beta.1] - 2019-04-20
### Added ### Added

View File

@ -124,7 +124,7 @@ where
pub trait AsyncFactory<T, R>: Clone + 'static pub trait AsyncFactory<T, R>: Clone + 'static
where where
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
fn call(&self, param: T) -> R; fn call(&self, param: T) -> R;
@ -134,7 +134,7 @@ impl<F, R> AsyncFactory<(), R> for F
where where
F: Fn() -> R + Clone + 'static, F: Fn() -> R + Clone + 'static,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
fn call(&self, _: ()) -> R { fn call(&self, _: ()) -> R {
@ -147,7 +147,7 @@ pub struct AsyncHandler<F, T, R>
where where
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
hnd: F, hnd: F,
@ -158,7 +158,7 @@ impl<F, T, R> AsyncHandler<F, T, R>
where where
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
pub fn new(hnd: F) -> Self { pub fn new(hnd: F) -> Self {
@ -173,7 +173,7 @@ impl<F, T, R> Clone for AsyncHandler<F, T, R>
where where
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@ -188,7 +188,7 @@ impl<F, T, R> Service for AsyncHandler<F, T, R>
where where
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
R: IntoFuture, R: IntoFuture,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
type Request = (T, HttpRequest); type Request = (T, HttpRequest);
@ -203,32 +203,56 @@ where
fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future {
AsyncHandlerServiceResponse { AsyncHandlerServiceResponse {
fut: self.hnd.call(param).into_future(), fut: self.hnd.call(param).into_future(),
fut2: None,
req: Some(req), req: Some(req),
} }
} }
} }
#[doc(hidden)] #[doc(hidden)]
pub struct AsyncHandlerServiceResponse<T> { pub struct AsyncHandlerServiceResponse<T>
where
T: Future,
T::Item: Responder,
{
fut: T, fut: T,
fut2: Option<<<T::Item as Responder>::Future as IntoFuture>::Future>,
req: Option<HttpRequest>, req: Option<HttpRequest>,
} }
impl<T> Future for AsyncHandlerServiceResponse<T> impl<T> Future for AsyncHandlerServiceResponse<T>
where where
T: Future, T: Future,
T::Item: Into<Response>, T::Item: Responder,
T::Error: Into<Error>, T::Error: Into<Error>,
{ {
type Item = ServiceResponse; type Item = ServiceResponse;
type Error = Void; type Error = Void;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref mut fut) = self.fut2 {
return match fut.poll() {
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new(
self.req.take().unwrap(),
res,
))),
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => {
let res: Response = e.into().into();
Ok(Async::Ready(ServiceResponse::new(
self.req.take().unwrap(),
res,
)))
}
};
}
match self.fut.poll() { match self.fut.poll() {
Ok(Async::Ready(res)) => Ok(Async::Ready(ServiceResponse::new( Ok(Async::Ready(res)) => {
self.req.take().unwrap(), self.fut2 =
res.into(), Some(res.respond_to(self.req.as_ref().unwrap()).into_future());
))), return self.poll();
}
Ok(Async::NotReady) => Ok(Async::NotReady), Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => { Err(e) => {
let res: Response = e.into().into(); let res: Response = e.into().into();
@ -357,7 +381,7 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func
where Func: Fn($($T,)+) -> Res + Clone + 'static, where Func: Fn($($T,)+) -> Res + Clone + 'static,
Res: IntoFuture, Res: IntoFuture,
Res::Item: Into<Response>, Res::Item: Responder,
Res::Error: Into<Error>, Res::Error: Into<Error>,
{ {
fn call(&self, param: ($($T,)+)) -> Res { fn call(&self, param: ($($T,)+)) -> Res {

View File

@ -217,7 +217,7 @@ where
F: AsyncFactory<I, R>, F: AsyncFactory<I, R>,
I: FromRequest + 'static, I: FromRequest + 'static,
R: IntoFuture + 'static, R: IntoFuture + 'static,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
self.routes.push(Route::new().to_async(handler)); self.routes.push(Route::new().to_async(handler));

View File

@ -1,7 +1,7 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use actix_http::{http::Method, Error, Extensions, Response}; use actix_http::{http::Method, Error, Extensions};
use actix_service::{NewService, Service}; use actix_service::{NewService, Service};
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, Either, FutureResult};
use futures::{Async, Future, IntoFuture, Poll}; use futures::{Async, Future, IntoFuture, Poll};
@ -278,7 +278,7 @@ impl Route {
F: AsyncFactory<T, R>, F: AsyncFactory<T, R>,
T: FromRequest + 'static, T: FromRequest + 'static,
R: IntoFuture + 'static, R: IntoFuture + 'static,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
self.service = Box::new(RouteNewService::new(Extract::new( self.service = Box::new(RouteNewService::new(Extract::new(
@ -418,18 +418,25 @@ where
mod tests { mod tests {
use std::time::Duration; use std::time::Duration;
use bytes::Bytes;
use futures::Future; use futures::Future;
use serde_derive::Serialize;
use tokio_timer::sleep; use tokio_timer::sleep;
use crate::http::{Method, StatusCode}; use crate::http::{Method, StatusCode};
use crate::test::{call_service, init_service, TestRequest}; use crate::test::{call_service, init_service, read_body, TestRequest};
use crate::{error, web, App, HttpResponse}; use crate::{error, web, App, HttpResponse};
#[derive(Serialize, PartialEq, Debug)]
struct MyObject {
name: String,
}
#[test] #[test]
fn test_route() { fn test_route() {
let mut srv = let mut srv = init_service(
init_service( App::new()
App::new().service( .service(
web::resource("/test") web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok())) .route(web::get().to(|| HttpResponse::Ok()))
.route(web::put().to(|| { .route(web::put().to(|| {
@ -444,8 +451,15 @@ mod tests {
Err::<HttpResponse, _>(error::ErrorBadRequest("err")) Err::<HttpResponse, _>(error::ErrorBadRequest("err"))
}) })
})), })),
), )
); .service(web::resource("/json").route(web::get().to_async(|| {
sleep(Duration::from_millis(25)).then(|_| {
Ok::<_, crate::Error>(web::Json(MyObject {
name: "test".to_string(),
}))
})
}))),
);
let req = TestRequest::with_uri("/test") let req = TestRequest::with_uri("/test")
.method(Method::GET) .method(Method::GET)
@ -476,5 +490,12 @@ mod tests {
.to_request(); .to_request();
let resp = call_service(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let req = TestRequest::with_uri("/json").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
let body = read_body(resp);
assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}"));
} }
} }

View File

@ -193,6 +193,46 @@ where
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap")) .unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
} }
/// Helper function that returns a response body of a ServiceResponse.
/// This function blocks the current thread until futures complete.
///
/// ```rust
/// use actix_web::{test, web, App, HttpResponse, http::header};
/// use bytes::Bytes;
///
/// #[test]
/// fn test_index() {
/// let mut app = test::init_service(
/// App::new().service(
/// web::resource("/index.html")
/// .route(web::post().to(
/// || HttpResponse::Ok().body("welcome!")))));
///
/// let req = test::TestRequest::post()
/// .uri("/index.html")
/// .header(header::CONTENT_TYPE, "application/json")
/// .to_request();
///
/// let resp = call_service(&mut srv, req);
/// let result = test::read_body(resp);
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
/// }
/// ```
pub fn read_body<B>(mut res: ServiceResponse<B>) -> Bytes
where
B: MessageBody,
{
block_on(run_on(move || {
res.take_body()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
})
.map(|body: BytesMut| body.freeze())
}))
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
}
/// Helper function that returns a deserialized response body of a TestRequest /// Helper function that returns a deserialized response body of a TestRequest
/// This function blocks the current thread until futures complete. /// This function blocks the current thread until futures complete.
/// ///

View File

@ -1,5 +1,5 @@
//! Essentials helper functions and types for application registration. //! Essentials helper functions and types for application registration.
use actix_http::{http::Method, Response}; use actix_http::http::Method;
use futures::{Future, IntoFuture}; use futures::{Future, IntoFuture};
pub use actix_http::Response as HttpResponse; pub use actix_http::Response as HttpResponse;
@ -268,7 +268,7 @@ where
F: AsyncFactory<I, R>, F: AsyncFactory<I, R>,
I: FromRequest + 'static, I: FromRequest + 'static,
R: IntoFuture + 'static, R: IntoFuture + 'static,
R::Item: Into<Response>, R::Item: Responder,
R::Error: Into<Error>, R::Error: Into<Error>,
{ {
Route::new().to_async(handler) Route::new().to_async(handler)