responder body type temp

This commit is contained in:
Rob Ede 2021-11-29 23:57:29 +00:00
parent e4e2cef2e1
commit 81d6fb4d65
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
16 changed files with 203 additions and 73 deletions

View File

@ -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;

View File

@ -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<BoxBody> {
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::Body> {
self.into_response(req)
}
}

View File

@ -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(());
}
}

View File

@ -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;

View File

@ -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.
///

View File

@ -109,7 +109,9 @@ impl<T> Responder for InternalError<T>
where
T: fmt::Debug + fmt::Display + 'static,
{
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Body = BoxBody;
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
HttpResponse::from_error(self)
}
}

View File

@ -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<F, T, R>(
handler: F,
) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>
) -> BoxServiceFactory<
(),
ServiceRequest,
ServiceResponse<EitherBody<<R::Output as Responder>::Body>>,
Error,
(),
>
where
F: Handler<T, R>,
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))
}
}))

View File

@ -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,
<R::Output as Responder>::Body: MessageBody + 'static,
<<R::Output as Responder>::Body as MessageBody>::Error:
Into<Box<dyn StdError + 'static>>,
{
self.routes.push(Route::new().to(handler));
self

View File

@ -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<BoxBody>;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>;
/// 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<BoxBody> {
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
self
}
}
impl Responder for actix_http::Response<BoxBody> {
type Body = BoxBody;
#[inline]
fn respond_to(self, _: &HttpRequest) -> HttpResponse<BoxBody> {
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
HttpResponse::from(self)
}
}
impl Responder for HttpResponseBuilder {
type Body = BoxBody;
#[inline]
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse<BoxBody> {
fn respond_to(mut self, _: &HttpRequest) -> HttpResponse<Self::Body> {
self.finish()
}
}
impl Responder for actix_http::ResponseBuilder {
type Body = BoxBody;
#[inline]
fn respond_to(mut self, req: &HttpRequest) -> HttpResponse<BoxBody> {
fn respond_to(mut self, req: &HttpRequest) -> HttpResponse<Self::Body> {
self.finish().map_into_boxed_body().respond_to(req)
}
}
impl<T: Responder> Responder for Option<T> {
fn respond_to(self, req: &HttpRequest) -> HttpResponse<BoxBody> {
impl<T> Responder for Option<T>
where
T: Responder,
<T::Body as MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
{
type Body = EitherBody<T::Body>;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
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<T: Responder> Responder for Option<T> {
impl<T, E> Responder for Result<T, E>
where
T: Responder,
<T::Body as MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
E: Into<Error>,
{
fn respond_to(self, req: &HttpRequest) -> HttpResponse<BoxBody> {
type Body = EitherBody<T::Body>;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
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<T: Responder> Responder for (T, StatusCode) {
fn respond_to(self, req: &HttpRequest) -> HttpResponse<BoxBody> {
type Body = T::Body;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
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<BoxBody> {
HttpResponse::Ok().content_type($ct).body(self)
type Body = $body;
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
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<Self::Body> {
// 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<T> {
responder: T,
@ -202,11 +254,20 @@ impl<T: Responder> CustomResponder<T> {
}
}
impl<T: Responder> Responder for CustomResponder<T> {
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
impl<T> Responder for CustomResponder<T>
where
T: Responder,
<T::Body as MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
{
type Body = EitherBody<T::Body>;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
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<T: Responder> Responder for CustomResponder<T> {
res.headers_mut().insert(k, v);
}
res
res.map_body(|_, body| EitherBody::left(body))
}
}

View File

@ -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<B>(&mut self, body: B) -> HttpResponse<BoxBody>
@ -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<B>(&mut self, body: B) -> Result<HttpResponse<B>, 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]

View File

@ -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<Box<dyn Guard>> {
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,
<R::Output as Responder>::Body: MessageBody + 'static,
<<R::Output as Responder>::Body as MessageBody>::Error:
Into<Box<dyn StdError + 'static>>,
{
self.service = handler_service(handler);
// TODO: remove double boxing
self.service =
boxed::factory(handler_service(handler).map(|res| res.map_into_boxed_body()));
self
}

View File

@ -656,8 +656,8 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result<net::T
Ok(net::TcpListener::from(socket))
}
#[cfg(feature = "openssl")]
/// Configure `SslAcceptorBuilder` with custom server flags.
#[cfg(feature = "openssl")]
fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result<SslAcceptor> {
builder.set_alpn_select_callback(|_, protocols| {
const H2: &[u8] = b"\x02h2";

View File

@ -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<L, R> Either<L, R> {
impl<L, R> Responder for Either<L, R>
where
L: Responder,
<L::Body as dev::MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
R: Responder,
<R::Body as dev::MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
{
fn respond_to(self, req: &HttpRequest) -> HttpResponse {
type Body = body::EitherBody<L::Body, R::Body>;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
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)),
}
}
}

View File

@ -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<T: fmt::Display> fmt::Display for Form<T> {
/// See [here](#responder) for example of usage as a handler return type.
impl<T: Serialize> Responder for Form<T> {
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Body = EitherBody<String>;
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
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)),
}
}
}

View File

@ -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<T: Serialize> Serialize for Json<T> {
///
/// If serialization failed
impl<T: Serialize> Responder for Json<T> {
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
type Body = EitherBody<String>;
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
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)),
}
}
}

View File

@ -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,
<R::Output as Responder>::Body: MessageBody + 'static,
<<R::Output as Responder>::Body as MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
{
Route::new().to(handler)
}