diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index fbb46e417..68221ccc3 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -6,8 +6,7 @@ use std::{ task::{Context, Poll}, }; -use actix_web::error::Error; -use bytes::Bytes; +use actix_web::{error::Error, web::Bytes}; use futures_core::{ready, Stream}; use pin_project_lite::pin_project; diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 2026868ec..e938f8c05 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -13,6 +13,7 @@ use std::os::unix::fs::MetadataExt; use actix_http::body::AnyBody; use actix_service::{Service, ServiceFactory}; use actix_web::{ + body::BoxBody, dev::{ AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse, SizedStream, @@ -394,7 +395,7 @@ impl NamedFile { } /// Creates an `HttpResponse` with file as a streaming body. - pub fn into_response(self, req: &HttpRequest) -> HttpResponse { + pub fn into_response(self, req: &HttpRequest) -> HttpResponse { if self.status_code != StatusCode::OK { let mut res = HttpResponse::build(self.status_code); @@ -598,7 +599,10 @@ impl DerefMut for NamedFile { } impl Responder for NamedFile { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { + // TODO: can be improved + type Body = BoxBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { self.into_response(req) } } diff --git a/actix-http/src/body/either.rs b/actix-http/src/body/either.rs index 418d3a696..38f164762 100644 --- a/actix-http/src/body/either.rs +++ b/actix-http/src/body/either.rs @@ -77,7 +77,10 @@ mod tests { use super::*; #[test] - fn either_body_works() { - let _body = EitherBody::new(()); + fn type_parameter_inference() { + let _body: EitherBody<(), _> = EitherBody::new(()); + + let _body: EitherBody<_, ()> = EitherBody::left(()); + let _body: EitherBody<(), _> = EitherBody::right(()); } } diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index e1d928bc9..5021ff832 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -13,7 +13,8 @@ use pin_project_lite::pin_project; use super::BodySize; -/// An interface for response bodies. +/// An interface types that can converted to bytes and used as response bodies. +// TODO: examples pub trait MessageBody { type Error; diff --git a/actix-http/src/body/size.rs b/actix-http/src/body/size.rs index e238eadac..d64af9d44 100644 --- a/actix-http/src/body/size.rs +++ b/actix-http/src/body/size.rs @@ -18,7 +18,7 @@ pub enum BodySize { } impl BodySize { - /// Returns true if size hint indicates no or empty body. + /// Returns true if size hint indicates omitted or empty body. /// /// Streams will return false because it cannot be known without reading the stream. /// diff --git a/src/error/internal.rs b/src/error/internal.rs index 00b77586e..c766ba83e 100644 --- a/src/error/internal.rs +++ b/src/error/internal.rs @@ -109,7 +109,9 @@ impl Responder for InternalError where T: fmt::Debug + fmt::Display + 'static, { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + type Body = BoxBody; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { HttpResponse::from_error(self) } } diff --git a/src/handler.rs b/src/handler.rs index ddefe8d53..375f8abee 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,6 +6,7 @@ use actix_service::{ }; use crate::{ + body::EitherBody, service::{ServiceRequest, ServiceResponse}, Error, FromRequest, HttpResponse, Responder, }; @@ -26,7 +27,13 @@ where pub fn handler_service( handler: F, -) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()> +) -> BoxServiceFactory< + (), + ServiceRequest, + ServiceResponse::Body>>, + Error, + (), +> where F: Handler, T: FromRequest, @@ -35,12 +42,21 @@ where { boxed::factory(fn_service(move |req: ServiceRequest| { let handler = handler.clone(); + async move { let (req, mut payload) = req.into_parts(); let res = match T::from_request(&req, &mut payload).await { - Err(err) => HttpResponse::from_error(err), - Ok(data) => handler.call(data).await.respond_to(&req), + Err(err) => { + HttpResponse::from_error(err).map_body(|_, body| EitherBody::right(body)) + } + + Ok(data) => handler + .call(data) + .await + .respond_to(&req) + .map_body(|_, body| EitherBody::left(body)), }; + Ok(ServiceResponse::new(req, res)) } })) diff --git a/src/resource.rs b/src/resource.rs index a025a8943..bd9518bd0 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,7 +1,4 @@ -use std::cell::RefCell; -use std::fmt; -use std::future::Future; -use std::rc::Rc; +use std::{cell::RefCell, error::Error as StdError, fmt, future::Future, rc::Rc}; use actix_http::Extensions; use actix_router::{IntoPatterns, Patterns}; @@ -13,6 +10,7 @@ use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; use crate::{ + body::MessageBody, data::Data, dev::{ensure_leading_slash, AppService, ResourceDef}, guard::Guard, @@ -241,6 +239,9 @@ where I: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, + ::Body: MessageBody + 'static, + <::Body as MessageBody>::Error: + Into>, { self.routes.push(Route::new().to(handler)); self diff --git a/src/responder.rs b/src/responder.rs index e78d0c1c7..2878c1992 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,5 +1,7 @@ +use std::error::Error as StdError; + use actix_http::{ - body::BoxBody, + body::{BoxBody, EitherBody, MessageBody}, http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, }; use bytes::{Bytes, BytesMut}; @@ -10,8 +12,10 @@ use crate::{Error, HttpRequest, HttpResponse, HttpResponseBuilder}; /// /// Any types that implement this trait can be used in the return type of a handler. pub trait Responder { + type Body: MessageBody + 'static; + /// Convert self to `HttpResponse`. - fn respond_to(self, req: &HttpRequest) -> HttpResponse; + fn respond_to(self, req: &HttpRequest) -> HttpResponse; /// Override a status code for a Responder. /// @@ -57,38 +61,56 @@ pub trait Responder { } impl Responder for HttpResponse { + type Body = BoxBody; + #[inline] - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { self } } impl Responder for actix_http::Response { + type Body = BoxBody; + #[inline] - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { HttpResponse::from(self) } } impl Responder for HttpResponseBuilder { + type Body = BoxBody; + #[inline] - fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { self.finish() } } impl Responder for actix_http::ResponseBuilder { + type Body = BoxBody; + #[inline] - fn respond_to(mut self, req: &HttpRequest) -> HttpResponse { + fn respond_to(mut self, req: &HttpRequest) -> HttpResponse { self.finish().map_into_boxed_body().respond_to(req) } } -impl Responder for Option { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { +impl Responder for Option +where + T: Responder, + ::Error: Into>, +{ + type Body = EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Some(val) => val.respond_to(req), - None => HttpResponse::new(StatusCode::NOT_FOUND), + Some(val) => val + .respond_to(req) + .map_body(|_, body| EitherBody::left(body)), + + None => HttpResponse::new(StatusCode::NOT_FOUND) + .map_body(|_, body| EitherBody::right(body)), } } } @@ -96,48 +118,78 @@ impl Responder for Option { impl Responder for Result where T: Responder, + ::Error: Into>, E: Into, { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { + type Body = EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Ok(val) => val.respond_to(req), - Err(e) => HttpResponse::from_error(e.into()), + Ok(val) => val + .respond_to(req) + .map_body(|_, body| EitherBody::left(body)), + + Err(e) => { + HttpResponse::from_error(e.into()).map_body(|_, body| EitherBody::right(body)) + } } } } impl Responder for (T, StatusCode) { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { + type Body = T::Body; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { let mut res = self.0.respond_to(req); *res.status_mut() = self.1; res } } -macro_rules! impl_responder { - ($res: ty, $ct: path) => { +macro_rules! impl_responder_by_forward_into_base_response { + ($res:ty, $body:ty) => { impl Responder for $res { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { - HttpResponse::Ok().content_type($ct).body(self) + type Body = $body; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + let res: actix_http::Response<_> = self.into(); + res.into() } } }; + + ($res:ty) => { + impl_responder_by_forward_into_base_response!($res, $res); + }; } -impl_responder!(&'static str, mime::TEXT_PLAIN_UTF_8); +impl_responder_by_forward_into_base_response!(&'static [u8]); +impl_responder_by_forward_into_base_response!(Bytes); +impl_responder_by_forward_into_base_response!(BytesMut); -impl_responder!(String, mime::TEXT_PLAIN_UTF_8); +impl_responder_by_forward_into_base_response!(&'static str); +impl_responder_by_forward_into_base_response!(String); + +// macro_rules! impl_responder { +// ($res:ty, $body:ty, $ct:path) => { +// impl Responder for $res { +// type Body = $body; + +// fn respond_to(self, _: &HttpRequest) -> HttpResponse { +// HttpResponse::Ok().content_type($ct).body(self) +// } +// } +// }; + +// ($res:ty, $ct:path) => { +// impl_responder!($res, $res, $ct); +// }; +// } // impl_responder!(&'_ String, mime::TEXT_PLAIN_UTF_8); // impl_responder!(Cow<'_, str>, mime::TEXT_PLAIN_UTF_8); -impl_responder!(&'static [u8], mime::APPLICATION_OCTET_STREAM); - -impl_responder!(Bytes, mime::APPLICATION_OCTET_STREAM); - -impl_responder!(BytesMut, mime::APPLICATION_OCTET_STREAM); - /// Allows overriding status code and headers for a responder. pub struct CustomResponder { responder: T, @@ -202,11 +254,20 @@ impl CustomResponder { } } -impl Responder for CustomResponder { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { +impl Responder for CustomResponder +where + T: Responder, + ::Error: Into>, +{ + type Body = EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { let headers = match self.headers { Ok(headers) => headers, - Err(err) => return HttpResponse::from_error(Error::from(err)), + Err(err) => { + return HttpResponse::from_error(Error::from(err)) + .map_body(|_, body| EitherBody::right(body)) + } }; let mut res = self.responder.respond_to(req); @@ -220,7 +281,7 @@ impl Responder for CustomResponder { res.headers_mut().insert(k, v); } - res + res.map_body(|_, body| EitherBody::left(body)) } } diff --git a/src/response/builder.rs b/src/response/builder.rs index 21364444c..db493b69f 100644 --- a/src/response/builder.rs +++ b/src/response/builder.rs @@ -306,7 +306,7 @@ impl HttpResponseBuilder { .extensions_mut() } - /// Set a body and generate `Response`. + /// Set a body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. pub fn body(&mut self, body: B) -> HttpResponse @@ -320,7 +320,7 @@ impl HttpResponseBuilder { } } - /// Set a body and generate `Response`. + /// Set a body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. pub fn message_body(&mut self, body: B) -> Result, Error> { @@ -350,7 +350,7 @@ impl HttpResponseBuilder { Ok(res) } - /// Set a streaming body and generate `Response`. + /// Set a streaming body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. #[inline] @@ -362,7 +362,7 @@ impl HttpResponseBuilder { self.body(BoxBody::new(BodyStream::new(stream))) } - /// Set a json body and generate `Response` + /// Set a JSON body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. pub fn json(&mut self, value: impl Serialize) -> HttpResponse { @@ -384,7 +384,7 @@ impl HttpResponseBuilder { } } - /// Set an empty body and generate `Response` + /// Set an empty body and build the `HttpResponse`. /// /// `HttpResponseBuilder` can not be used after this call. #[inline] diff --git a/src/route.rs b/src/route.rs index 0c0699430..66fda5068 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,6 +1,6 @@ #![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`) -use std::{future::Future, rc::Rc}; +use std::{error::Error as StdError, future::Future, mem, rc::Rc}; use actix_http::http::Method; use actix_service::{ @@ -10,6 +10,7 @@ use actix_service::{ use futures_core::future::LocalBoxFuture; use crate::{ + body::MessageBody, guard::{self, Guard}, handler::{handler_service, Handler}, service::{ServiceRequest, ServiceResponse}, @@ -30,13 +31,16 @@ impl Route { #[allow(clippy::new_without_default)] pub fn new() -> Route { Route { - service: handler_service(HttpResponse::NotFound), + // TODO: remove double boxing + service: boxed::factory( + handler_service(HttpResponse::NotFound).map(|res| res.map_into_boxed_body()), + ), guards: Rc::new(Vec::new()), } } pub(crate) fn take_guards(&mut self) -> Vec> { - std::mem::take(Rc::get_mut(&mut self.guards).unwrap()) + mem::take(Rc::get_mut(&mut self.guards).unwrap()) } } @@ -181,8 +185,13 @@ impl Route { T: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, + ::Body: MessageBody + 'static, + <::Body as MessageBody>::Error: + Into>, { - self.service = handler_service(handler); + // TODO: remove double boxing + self.service = + boxed::factory(handler_service(handler).map(|res| res.map_into_boxed_body())); self } diff --git a/src/server.rs b/src/server.rs index 1bf56655b..2510dbba2 100644 --- a/src/server.rs +++ b/src/server.rs @@ -656,8 +656,8 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result io::Result { builder.set_alpn_select_callback(|_, protocols| { const H2: &[u8] = b"\x02h2"; diff --git a/src/types/either.rs b/src/types/either.rs index 0a8a90133..df6d2f9c7 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -1,6 +1,7 @@ //! For either helper, see [`Either`]. use std::{ + error::Error as StdError, future::Future, mem, pin::Pin, @@ -12,7 +13,7 @@ use futures_core::ready; use pin_project_lite::pin_project; use crate::{ - dev, + body, dev, web::{Form, Json}, Error, FromRequest, HttpRequest, HttpResponse, Responder, }; @@ -144,12 +145,21 @@ impl Either { impl Responder for Either where L: Responder, + ::Error: Into>, R: Responder, + ::Error: Into>, { - fn respond_to(self, req: &HttpRequest) -> HttpResponse { + type Body = body::EitherBody; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Either::Left(a) => a.respond_to(req), - Either::Right(b) => b.respond_to(req), + Either::Left(a) => a + .respond_to(req) + .map_body(|_, body| body::EitherBody::left(body)), + + Either::Right(b) => b + .respond_to(req) + .map_body(|_, body| body::EitherBody::right(body)), } } } diff --git a/src/types/form.rs b/src/types/form.rs index f4414dec5..3603ca2dd 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -20,8 +20,9 @@ use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "__compress")] use crate::dev::Decompress; use crate::{ - error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, Error, - HttpMessage, HttpRequest, HttpResponse, Responder, + body::EitherBody, error::UrlencodedError, extract::FromRequest, + http::header::CONTENT_LENGTH, web, Error, HttpMessage, HttpRequest, HttpResponse, + Responder, }; /// URL encoded payload extractor and responder. @@ -180,12 +181,22 @@ impl fmt::Display for Form { /// See [here](#responder) for example of usage as a handler return type. impl Responder for Form { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + type Body = EitherBody; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { match serde_urlencoded::to_string(&self.0) { - Ok(body) => HttpResponse::Ok() + Ok(body) => match HttpResponse::Ok() .content_type(mime::APPLICATION_WWW_FORM_URLENCODED) - .body(body), - Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err)), + .message_body(body) + { + Ok(res) => res.map_body(|_, body| EitherBody::left(body)), + Err(err) => { + HttpResponse::from_error(err).map_body(|_, body| EitherBody::right(body)) + } + }, + + Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err)) + .map_body(|_, body| EitherBody::right(body)), } } } diff --git a/src/types/json.rs b/src/types/json.rs index 92f53deab..568cf70fb 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -19,6 +19,7 @@ use actix_http::Payload; #[cfg(feature = "__compress")] use crate::dev::Decompress; use crate::{ + body::EitherBody, error::{Error, JsonPayloadError}, extract::FromRequest, http::header::CONTENT_LENGTH, @@ -116,12 +117,22 @@ impl Serialize for Json { /// /// If serialization failed impl Responder for Json { - fn respond_to(self, _: &HttpRequest) -> HttpResponse { + type Body = EitherBody; + + fn respond_to(self, _: &HttpRequest) -> HttpResponse { match serde_json::to_string(&self.0) { - Ok(body) => HttpResponse::Ok() + Ok(body) => match HttpResponse::Ok() .content_type(mime::APPLICATION_JSON) - .body(body), - Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)), + .message_body(body) + { + Ok(res) => res.map_body(|_, body| EitherBody::left(body)), + Err(err) => { + HttpResponse::from_error(err).map_body(|_, body| EitherBody::right(body)) + } + }, + + Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)) + .map_body(|_, body| EitherBody::right(body)), } } } diff --git a/src/web.rs b/src/web.rs index e9f5c8518..b58adc2f8 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,14 +1,14 @@ //! Essentials helper functions and types for application registration. -use std::future::Future; +use std::{error::Error as StdError, future::Future}; use actix_http::http::Method; use actix_router::IntoPatterns; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; use crate::{ - error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource, - responder::Responder, route::Route, scope::Scope, service::WebService, + body::MessageBody, error::BlockingError, extract::FromRequest, handler::Handler, + resource::Resource, responder::Responder, route::Route, scope::Scope, service::WebService, }; pub use crate::config::ServiceConfig; @@ -145,6 +145,8 @@ where I: FromRequest + 'static, R: Future + 'static, R::Output: Responder + 'static, + ::Body: MessageBody + 'static, + <::Body as MessageBody>::Error: Into>, { Route::new().to(handler) }