diff --git a/CHANGES.md b/CHANGES.md index 25fd1095..4eb2b6e1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx - +### Changed +* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. + Making it more simple and performant. [#1891] + +[#1891]: https://github.com/actix/actix-web/pull/1891 ## 4.0.0-beta.1 - 2021-01-07 ### Added diff --git a/Cargo.toml b/Cargo.toml index 87183c32..24a8573f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,3 +139,7 @@ harness = false [[bench]] name = "service" harness = false + +[[bench]] +name = "responder" +harness = false \ No newline at end of file diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index a9b95bad..b3c247b1 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -16,10 +16,9 @@ use actix_web::{ }, ContentEncoding, StatusCode, }, - Error, HttpMessage, HttpRequest, HttpResponse, Responder, + HttpMessage, HttpRequest, HttpResponse, Responder, }; use bitflags::bitflags; -use futures_util::future::{ready, Ready}; use mime_guess::from_path; use crate::ChunkedReadFile; @@ -277,7 +276,7 @@ impl NamedFile { } /// Creates an `HttpResponse` with file as a streaming body. - pub fn into_response(self, req: &HttpRequest) -> Result { + pub fn into_response(self, req: &HttpRequest) -> HttpResponse { if self.status_code != StatusCode::OK { let mut res = HttpResponse::build(self.status_code); @@ -307,7 +306,7 @@ impl NamedFile { counter: 0, }; - return Ok(res.streaming(reader)); + return res.streaming(reader); } let etag = if self.flags.contains(Flags::ETAG) { @@ -411,17 +410,17 @@ impl NamedFile { ); } else { resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); - return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); + return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(); }; } else { - return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); + return resp.status(StatusCode::BAD_REQUEST).finish(); }; }; if precondition_failed { - return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); + return resp.status(StatusCode::PRECONDITION_FAILED).finish(); } else if not_modified { - return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); + return resp.status(StatusCode::NOT_MODIFIED).finish(); } let reader = ChunkedReadFile { @@ -436,7 +435,7 @@ impl NamedFile { resp.status(StatusCode::PARTIAL_CONTENT); } - Ok(resp.body(SizedStream::new(length, reader))) + resp.body(SizedStream::new(length, reader)) } } @@ -495,10 +494,7 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { } impl Responder for NamedFile { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - ready(self.into_response(req)) + fn respond_to(self, req: &HttpRequest) -> HttpResponse { + self.into_response(req) } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 1e3d64a0..05431db3 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -120,10 +120,8 @@ impl Service for FilesService { named_file.flags = self.file_flags; let (req, _) = req.into_parts(); - Either::Left(ok(match named_file.into_response(&req) { - Ok(item) => ServiceResponse::new(req, item), - Err(e) => ServiceResponse::from_err(e, req), - })) + let res = named_file.into_response(&req); + Either::Left(ok(ServiceResponse::new(req, res))) } Err(e) => self.handle_err(e, req), } @@ -154,12 +152,8 @@ impl Service for FilesService { named_file.flags = self.file_flags; let (req, _) = req.into_parts(); - match named_file.into_response(&req) { - Ok(item) => { - Either::Left(ok(ServiceResponse::new(req.clone(), item))) - } - Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), - } + let res = named_file.into_response(&req); + Either::Left(ok(ServiceResponse::new(req, res))) } Err(e) => self.handle_err(e, req), } diff --git a/benches/responder.rs b/benches/responder.rs new file mode 100644 index 00000000..61180d57 --- /dev/null +++ b/benches/responder.rs @@ -0,0 +1,113 @@ +use std::future::Future; +use std::time::Instant; + +use actix_http::Response; +use actix_web::http::StatusCode; +use actix_web::test::TestRequest; +use actix_web::{error, Error, HttpRequest, HttpResponse, Responder}; +use criterion::{criterion_group, criterion_main, Criterion}; +use futures_util::future::{ready, Either, Ready}; + +// responder simulate the old responder trait. +trait FutureResponder { + type Error; + type Future: Future>; + + fn future_respond_to(self, req: &HttpRequest) -> Self::Future; +} + +// a simple option responder type. +struct OptionResponder(Option); + +// a simple wrapper type around string +struct StringResponder(String); + +impl FutureResponder for StringResponder { + type Error = Error; + type Future = Ready>; + + fn future_respond_to(self, _: &HttpRequest) -> Self::Future { + // this is default builder for string response in both new and old responder trait. + ready(Ok(Response::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self.0))) + } +} + +impl FutureResponder for OptionResponder +where + T: FutureResponder, + T::Future: Future>, +{ + type Error = Error; + type Future = Either>>; + + fn future_respond_to(self, req: &HttpRequest) -> Self::Future { + match self.0 { + Some(t) => Either::Left(t.future_respond_to(req)), + None => Either::Right(ready(Err(error::ErrorInternalServerError("err")))), + } + } +} + +impl Responder for StringResponder { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + Response::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self.0) + } +} + +impl Responder for OptionResponder { + fn respond_to(self, req: &HttpRequest) -> HttpResponse { + match self.0 { + Some(t) => t.respond_to(req), + None => Response::from_error(error::ErrorInternalServerError("err")), + } + } +} + +fn future_responder(c: &mut Criterion) { + let rt = actix_rt::System::new("test"); + let req = TestRequest::default().to_http_request(); + + c.bench_function("future_responder", move |b| { + b.iter_custom(|_| { + let futs = (0..100_000).map(|_| async { + StringResponder(String::from("Hello World!!")) + .future_respond_to(&req) + .await + }); + + let futs = futures_util::future::join_all(futs); + + let start = Instant::now(); + + let _res = rt.block_on(async { futs.await }); + + start.elapsed() + }) + }); +} + +fn responder(c: &mut Criterion) { + let rt = actix_rt::System::new("test"); + let req = TestRequest::default().to_http_request(); + c.bench_function("responder", move |b| { + b.iter_custom(|_| { + let responders = + (0..100_000).map(|_| StringResponder(String::from("Hello World!!"))); + + let start = Instant::now(); + let _res = rt.block_on(async { + // don't need runtime block on but to be fair. + responders.map(|r| r.respond_to(&req)).collect::>() + }); + + start.elapsed() + }) + }); +} + +criterion_group!(responder_bench, future_responder, responder); +criterion_main!(responder_bench); diff --git a/src/handler.rs b/src/handler.rs index 30cc5984..47656cd8 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -135,7 +135,6 @@ where { Extract(#[pin] T::Future, Option, F), Handle(#[pin] R, Option), - Respond(#[pin] ::Future, Option), } impl Future for HandlerServiceFuture @@ -168,13 +167,8 @@ where } HandlerProj::Handle(fut, req) => { let res = ready!(fut.poll(cx)); - let fut = res.respond_to(req.as_ref().unwrap()); - let state = HandlerServiceFuture::Respond(fut, req.take()); - self.as_mut().set(state); - } - HandlerProj::Respond(fut, req) => { - let res = ready!(fut.poll(cx)).unwrap_or_else(|e| e.into().into()); let req = req.take().unwrap(); + let res = res.respond_to(&req); return Poll::Ready(Ok(ServiceResponse::new(req, res))); } } diff --git a/src/responder.rs b/src/responder.rs index 58e33f39..b61dd101 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,8 +1,4 @@ use std::convert::TryFrom; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; use actix_http::error::InternalError; use actix_http::http::{ @@ -10,9 +6,6 @@ use actix_http::http::{ }; use actix_http::{Error, Response, ResponseBuilder}; use bytes::{Bytes, BytesMut}; -use futures_util::future::{err, ok, Either as EitherFuture, Ready}; -use futures_util::ready; -use pin_project::pin_project; use crate::request::HttpRequest; @@ -20,14 +13,8 @@ use crate::request::HttpRequest; /// /// Types that implement this trait can be used as the return type of a handler. pub trait Responder { - /// The associated error which can be returned. - type Error: Into; - - /// The future response value. - type Future: Future>; - - /// Convert itself to `AsyncResult` or `Error`. - fn respond_to(self, req: &HttpRequest) -> Self::Future; + /// Convert self to `Response`. + fn respond_to(self, req: &HttpRequest) -> Response; /// Override a status code for a Responder. /// @@ -77,28 +64,17 @@ pub trait Responder { } impl Responder for Response { - type Error = Error; - type Future = Ready>; - #[inline] - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(self) + fn respond_to(self, _: &HttpRequest) -> Response { + self } } -impl Responder for Option -where - T: Responder, -{ - type Error = T::Error; - type Future = EitherFuture>>; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { +impl Responder for Option { + fn respond_to(self, req: &HttpRequest) -> Response { match self { - Some(t) => EitherFuture::Left(t.respond_to(req)), - None => { - EitherFuture::Right(ok(Response::build(StatusCode::NOT_FOUND).finish())) - } + Some(t) => t.respond_to(req), + None => Response::build(StatusCode::NOT_FOUND).finish(), } } } @@ -108,109 +84,74 @@ where T: Responder, E: Into, { - type Error = Error; - type Future = EitherFuture< - ResponseFuture, - Ready>, - >; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { + fn respond_to(self, req: &HttpRequest) -> Response { match self { - Ok(val) => EitherFuture::Left(ResponseFuture::new(val.respond_to(req))), - Err(e) => EitherFuture::Right(err(e.into())), + Ok(val) => val.respond_to(req), + Err(e) => Response::from_error(e.into()), } } } impl Responder for ResponseBuilder { - type Error = Error; - type Future = Ready>; - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> Self::Future { - ok(self.finish()) + fn respond_to(mut self, _: &HttpRequest) -> Response { + self.finish() } } -impl Responder for (T, StatusCode) -where - T: Responder, -{ - type Error = T::Error; - type Future = CustomResponderFut; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - CustomResponderFut { - fut: self.0.respond_to(req), - status: Some(self.1), - headers: None, - } +impl Responder for (T, StatusCode) { + fn respond_to(self, req: &HttpRequest) -> Response { + let mut res = self.0.respond_to(req); + *res.status_mut() = self.1; + res } } impl Responder for &'static str { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") - .body(self)) + .body(self) } } impl Responder for &'static [u8] { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("application/octet-stream") - .body(self)) + .body(self) } } impl Responder for String { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") - .body(self)) + .body(self) } } impl<'a> Responder for &'a String { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") - .body(self)) + .body(self) } } impl Responder for Bytes { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("application/octet-stream") - .body(self)) + .body(self) } } impl Responder for BytesMut { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("application/octet-stream") - .body(self)) + .body(self) } } @@ -290,45 +231,20 @@ impl CustomResponder { } impl Responder for CustomResponder { - type Error = T::Error; - type Future = CustomResponderFut; + fn respond_to(self, req: &HttpRequest) -> Response { + let mut res = self.responder.respond_to(req); - fn respond_to(self, req: &HttpRequest) -> Self::Future { - CustomResponderFut { - fut: self.responder.respond_to(req), - status: self.status, - headers: self.headers, - } - } -} - -#[pin_project] -pub struct CustomResponderFut { - #[pin] - fut: T::Future, - status: Option, - headers: Option, -} - -impl Future for CustomResponderFut { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - let mut res = match ready!(this.fut.poll(cx)) { - Ok(res) => res, - Err(e) => return Poll::Ready(Err(e)), - }; - if let Some(status) = this.status.take() { + if let Some(status) = self.status { *res.status_mut() = status; } - if let Some(ref headers) = this.headers { + + if let Some(ref headers) = self.headers { for (k, v) in headers { res.headers_mut().insert(k.clone(), v.clone()); } } - Poll::Ready(Ok(res)) + + res } } @@ -336,40 +252,8 @@ impl Responder for InternalError where T: std::fmt::Debug + std::fmt::Display + 'static, { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let err: Error = self.into(); - ok(err.into()) - } -} - -#[pin_project] -pub struct ResponseFuture { - #[pin] - fut: T, - _phantom: PhantomData, -} - -impl ResponseFuture { - pub fn new(fut: T) -> Self { - ResponseFuture { - fut, - _phantom: PhantomData, - } - } -} - -impl Future for ResponseFuture -where - T: Future>, - E: Into, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready(ready!(self.project().fut.poll(cx)).map_err(|e| e.into())) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::from_error(self.into()) } } @@ -382,7 +266,7 @@ pub(crate) mod tests { use crate::dev::{Body, ResponseBody}; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; use crate::test::{init_service, TestRequest}; - use crate::{error, web, App, HttpResponse}; + use crate::{error, web, App}; #[actix_rt::test] async fn test_option_responder() { @@ -441,7 +325,7 @@ pub(crate) mod tests { async fn test_responder() { let req = TestRequest::default().to_http_request(); - let resp: HttpResponse = "test".respond_to(&req).await.unwrap(); + let resp = "test".respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -449,7 +333,7 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); - let resp: HttpResponse = b"test".respond_to(&req).await.unwrap(); + let resp = b"test".respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -457,7 +341,7 @@ pub(crate) mod tests { HeaderValue::from_static("application/octet-stream") ); - let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap(); + let resp = "test".to_string().respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -465,7 +349,7 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); - let resp: HttpResponse = (&"test".to_string()).respond_to(&req).await.unwrap(); + let resp = (&"test".to_string()).respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -473,8 +357,7 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); - let resp: HttpResponse = - Bytes::from_static(b"test").respond_to(&req).await.unwrap(); + let resp = Bytes::from_static(b"test").respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -482,10 +365,7 @@ pub(crate) mod tests { HeaderValue::from_static("application/octet-stream") ); - let resp: HttpResponse = BytesMut::from(b"test".as_ref()) - .respond_to(&req) - .await - .unwrap(); + let resp = BytesMut::from(b"test".as_ref()).respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -494,11 +374,8 @@ pub(crate) mod tests { ); // InternalError - let resp: HttpResponse = - error::InternalError::new("err", StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); + let resp = + error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } @@ -507,10 +384,7 @@ pub(crate) mod tests { let req = TestRequest::default().to_http_request(); // Result - let resp: HttpResponse = Ok::<_, Error>("test".to_string()) - .respond_to(&req) - .await - .unwrap(); + let resp = Ok::<_, Error>("test".to_string()).respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -520,9 +394,9 @@ pub(crate) mod tests { let res = Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) - .respond_to(&req) - .await; - assert!(res.is_err()); + .respond_to(&req); + + assert_eq!(res.status(), StatusCode::BAD_REQUEST); } #[actix_rt::test] @@ -531,18 +405,15 @@ pub(crate) mod tests { let res = "test" .to_string() .with_status(StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); + .respond_to(&req); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); assert_eq!(res.body().bin_ref(), b"test"); let res = "test" .to_string() .with_header("content-type", "json") - .respond_to(&req) - .await - .unwrap(); + .respond_to(&req); assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.body().bin_ref(), b"test"); @@ -555,19 +426,14 @@ pub(crate) mod tests { #[actix_rt::test] async fn test_tuple_responder_with_status_code() { let req = TestRequest::default().to_http_request(); - let res = ("test".to_string(), StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); + let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req); assert_eq!(res.status(), StatusCode::BAD_REQUEST); assert_eq!(res.body().bin_ref(), b"test"); let req = TestRequest::default().to_http_request(); let res = ("test".to_string(), StatusCode::OK) .with_header("content-type", "json") - .respond_to(&req) - .await - .unwrap(); + .respond_to(&req); assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.body().bin_ref(), b"test"); assert_eq!( diff --git a/src/types/either.rs b/src/types/either.rs index 8a046d29..ca3ab355 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -1,13 +1,6 @@ -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; - use actix_http::{Error, Response}; use bytes::Bytes; -use futures_util::{future::LocalBoxFuture, ready, FutureExt, TryFutureExt}; -use pin_project::pin_project; +use futures_util::{future::LocalBoxFuture, FutureExt, TryFutureExt}; use crate::{dev, request::HttpRequest, FromRequest, Responder}; @@ -68,42 +61,10 @@ where A: Responder, B: Responder, { - type Error = Error; - type Future = EitherResponder; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { + fn respond_to(self, req: &HttpRequest) -> Response { match self { - Either::A(a) => EitherResponder::A(a.respond_to(req)), - Either::B(b) => EitherResponder::B(b.respond_to(req)), - } - } -} - -#[pin_project(project = EitherResponderProj)] -pub enum EitherResponder -where - A: Responder, - B: Responder, -{ - A(#[pin] A::Future), - B(#[pin] B::Future), -} - -impl Future for EitherResponder -where - A: Responder, - B: Responder, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.project() { - EitherResponderProj::A(fut) => { - Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into())) - } - EitherResponderProj::B(fut) => { - Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into()))) - } + Either::A(a) => a.respond_to(req), + Either::B(b) => b.respond_to(req), } } } diff --git a/src/types/form.rs b/src/types/form.rs index 82ea7321..cdd28049 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -9,7 +9,7 @@ use std::{fmt, ops}; use actix_http::{Error, HttpMessage, Payload, Response}; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; -use futures_util::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; +use futures_util::future::{FutureExt, LocalBoxFuture}; use futures_util::StreamExt; use serde::de::DeserializeOwned; use serde::Serialize; @@ -158,18 +158,13 @@ impl fmt::Display for Form { } impl Responder for Form { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let body = match serde_urlencoded::to_string(&self.0) { - Ok(body) => body, - Err(e) => return err(e.into()), - }; - - ok(Response::build(StatusCode::OK) - .set(ContentType::form_url_encoded()) - .body(body)) + fn respond_to(self, _: &HttpRequest) -> Response { + match serde_urlencoded::to_string(&self.0) { + Ok(body) => Response::build(StatusCode::OK) + .set(ContentType::form_url_encoded()) + .body(body), + Err(e) => Response::from_error(e.into()), + } } } @@ -493,7 +488,7 @@ mod tests { hello: "world".to_string(), counter: 123, }); - let resp = form.respond_to(&req).await.unwrap(); + let resp = form.respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), diff --git a/src/types/json.rs b/src/types/json.rs index 74138ca5..452cb58c 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -8,7 +8,6 @@ use std::task::{Context, Poll}; use std::{fmt, ops}; use bytes::BytesMut; -use futures_util::future::{ready, Ready}; use futures_util::ready; use futures_util::stream::Stream; use serde::de::DeserializeOwned; @@ -123,18 +122,13 @@ where } impl Responder for Json { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let body = match serde_json::to_string(&self.0) { - Ok(body) => body, - Err(e) => return ready(Err(e.into())), - }; - - ready(Ok(Response::build(StatusCode::OK) - .content_type("application/json") - .body(body))) + fn respond_to(self, _: &HttpRequest) -> Response { + match serde_json::to_string(&self.0) { + Ok(body) => Response::build(StatusCode::OK) + .content_type("application/json") + .body(body), + Err(e) => Response::from_error(e.into()), + } } } @@ -498,7 +492,7 @@ mod tests { let j = Json(MyObject { name: "test".to_string(), }); - let resp = j.respond_to(&req).await.unwrap(); + let resp = j.respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(),