mirror of https://github.com/fafhrd91/actix-web
force message body error types to be errors
This commit is contained in:
parent
aec0e46909
commit
c3c59e40d1
|
@ -75,6 +75,7 @@ mod tests {
|
|||
use derive_more::{Display, Error};
|
||||
use futures_core::ready;
|
||||
use futures_util::{stream, FutureExt as _};
|
||||
use pin_project_lite::pin_project;
|
||||
use static_assertions::{assert_impl_all, assert_not_impl_all};
|
||||
|
||||
use super::*;
|
||||
|
@ -166,12 +167,14 @@ mod tests {
|
|||
BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
|
||||
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
|
||||
|
||||
#[pin_project::pin_project(project = TimeDelayStreamProj)]
|
||||
#[derive(Debug)]
|
||||
enum TimeDelayStream {
|
||||
Start,
|
||||
Sleep(Pin<Box<Sleep>>),
|
||||
Done,
|
||||
pin_project! {
|
||||
#[derive(Debug)]
|
||||
#[project = TimeDelayStreamProj]
|
||||
enum TimeDelayStream {
|
||||
Start,
|
||||
Sleep { delay: Pin<Box<Sleep>> },
|
||||
Done,
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for TimeDelayStream {
|
||||
|
@ -184,12 +187,14 @@ mod tests {
|
|||
match self.as_mut().get_mut() {
|
||||
TimeDelayStream::Start => {
|
||||
let sleep = sleep(Duration::from_millis(1));
|
||||
self.as_mut().set(TimeDelayStream::Sleep(Box::pin(sleep)));
|
||||
self.as_mut().set(TimeDelayStream::Sleep {
|
||||
delay: Box::pin(sleep),
|
||||
});
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
TimeDelayStream::Sleep(ref mut delay) => {
|
||||
TimeDelayStream::Sleep { ref mut delay } => {
|
||||
ready!(delay.poll_unpin(cx));
|
||||
self.set(TimeDelayStream::Done);
|
||||
cx.waker().wake_by_ref();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
error::Error as StdError,
|
||||
mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
|
@ -16,7 +17,7 @@ use super::BodySize;
|
|||
/// An interface types that can converted to bytes and used as response bodies.
|
||||
// TODO: examples
|
||||
pub trait MessageBody {
|
||||
type Error;
|
||||
type Error: Into<Box<dyn StdError>>;
|
||||
|
||||
/// Body size hint.
|
||||
fn size(&self) -> BodySize;
|
||||
|
@ -135,25 +136,6 @@ impl MessageBody for Bytes {
|
|||
}
|
||||
}
|
||||
|
||||
// impl<'a> MessageBody for &'a Bytes {
|
||||
// type Error = Infallible;
|
||||
|
||||
// fn size(&self) -> BodySize {
|
||||
// BodySize::Sized(self.len() as u64)
|
||||
// }
|
||||
|
||||
// fn poll_next(
|
||||
// self: Pin<&mut Self>,
|
||||
// _cx: &mut Context<'_>,
|
||||
// ) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
// if self.is_empty() {
|
||||
// Poll::Ready(None)
|
||||
// } else {
|
||||
// Poll::Ready(Some(Ok(self.clone())))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl MessageBody for BytesMut {
|
||||
type Error = Infallible;
|
||||
|
||||
|
@ -168,29 +150,31 @@ impl MessageBody for BytesMut {
|
|||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze())))
|
||||
let bytes = mem::take(self.get_mut()).freeze();
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a> MessageBody for &'a BytesMut {
|
||||
// type Error = Infallible;
|
||||
impl MessageBody for Vec<u8> {
|
||||
type Error = Infallible;
|
||||
|
||||
// fn size(&self) -> BodySize {
|
||||
// BodySize::Sized(self.len() as u64)
|
||||
// }
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
// fn poll_next(
|
||||
// self: Pin<&mut Self>,
|
||||
// _cx: &mut Context<'_>,
|
||||
// ) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
// if self.is_empty() {
|
||||
// Poll::Ready(None)
|
||||
// } else {
|
||||
// Poll::Ready(Some(Ok(self.clone().freeze())))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
let bytes = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(Bytes::from(bytes))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for &'static str {
|
||||
type Error = Infallible;
|
||||
|
@ -213,25 +197,6 @@ impl MessageBody for &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageBody for Vec<u8> {
|
||||
type Error = Infallible;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(Bytes::from(mem::take(self.get_mut())))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for String {
|
||||
type Error = Infallible;
|
||||
|
||||
|
@ -246,51 +211,12 @@ impl MessageBody for String {
|
|||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(Bytes::from(mem::take(self.get_mut())))))
|
||||
let string = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(Bytes::from(string))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a> MessageBody for &'a String {
|
||||
// type Error = Infallible;
|
||||
|
||||
// fn size(&self) -> BodySize {
|
||||
// BodySize::Sized(self.len() as u64)
|
||||
// }
|
||||
|
||||
// fn poll_next(
|
||||
// self: Pin<&mut Self>,
|
||||
// _cx: &mut Context<'_>,
|
||||
// ) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
// if self.is_empty() {
|
||||
// Poll::Ready(None)
|
||||
// } else {
|
||||
// Poll::Ready(Some(Ok(Bytes::from(self.clone()))))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl MessageBody for Cow<'_, str> {
|
||||
// type Error = Infallible;
|
||||
|
||||
// fn size(&self) -> BodySize {
|
||||
// BodySize::Sized(self.len() as u64)
|
||||
// }
|
||||
|
||||
// fn poll_next(
|
||||
// self: Pin<&mut Self>,
|
||||
// cx: &mut Context<'_>,
|
||||
// ) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
// if self.is_empty() {
|
||||
// Poll::Ready(None)
|
||||
// } else {
|
||||
// let cow = Pin::into_inner(self);
|
||||
// let mut string = cow.clone().into_owned();
|
||||
// Pin::new(&mut string).poll_next(cx)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl MessageBody for bytestring::ByteString {
|
||||
type Error = Infallible;
|
||||
|
||||
|
@ -307,8 +233,6 @@ impl MessageBody for bytestring::ByteString {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: ensure consistent impls of MessageBody that always terminate
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct MessageBodyMapErr<B, F> {
|
||||
#[pin]
|
||||
|
@ -334,6 +258,7 @@ impl<B, F, E> MessageBody for MessageBodyMapErr<B, F>
|
|||
where
|
||||
B: MessageBody,
|
||||
F: FnOnce(B::Error) -> E,
|
||||
E: Into<Box<dyn StdError>>,
|
||||
{
|
||||
type Error = E;
|
||||
|
||||
|
|
|
@ -103,8 +103,10 @@ pin_project! {
|
|||
#[project = EncoderBodyProj]
|
||||
enum EncoderBody<B> {
|
||||
None,
|
||||
|
||||
// TODO: this variant is not used but RA can't see it because of macro wrapper
|
||||
Bytes { bytes: Bytes },
|
||||
Bytes { body: Bytes },
|
||||
|
||||
Stream { #[pin] body: B },
|
||||
}
|
||||
}
|
||||
|
@ -113,12 +115,12 @@ impl<B> MessageBody for EncoderBody<B>
|
|||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
type Error = EncoderError<B::Error>;
|
||||
type Error = EncoderError;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
EncoderBody::None => BodySize::None,
|
||||
EncoderBody::Bytes { bytes } => bytes.size(),
|
||||
EncoderBody::Bytes { body } => body.size(),
|
||||
EncoderBody::Stream { body } => body.size(),
|
||||
}
|
||||
}
|
||||
|
@ -130,17 +132,16 @@ where
|
|||
match self.project() {
|
||||
EncoderBodyProj::None => Poll::Ready(None),
|
||||
|
||||
EncoderBodyProj::Bytes { bytes } => {
|
||||
if bytes.is_empty() {
|
||||
EncoderBodyProj::Bytes { body } => {
|
||||
if body.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(std::mem::take(bytes))))
|
||||
Poll::Ready(Some(Ok(std::mem::take(body))))
|
||||
}
|
||||
}
|
||||
|
||||
EncoderBodyProj::Stream { body } => {
|
||||
body.poll_next(cx).map_err(EncoderError::Body)
|
||||
}
|
||||
EncoderBodyProj::Stream { body } => body
|
||||
.poll_next(cx)
|
||||
.map_err(|err| EncoderError::Body(err.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +150,7 @@ impl<B> MessageBody for Encoder<B>
|
|||
where
|
||||
B: MessageBody,
|
||||
{
|
||||
type Error = EncoderError<B::Error>;
|
||||
type Error = EncoderError;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
if self.encoder.is_none() {
|
||||
|
@ -369,9 +370,9 @@ impl ContentEncoder {
|
|||
|
||||
#[derive(Debug, Display)]
|
||||
#[non_exhaustive]
|
||||
pub enum EncoderError<E> {
|
||||
pub enum EncoderError {
|
||||
#[display(fmt = "body")]
|
||||
Body(E),
|
||||
Body(Box<dyn StdError>),
|
||||
|
||||
#[display(fmt = "blocking")]
|
||||
Blocking(BlockingError),
|
||||
|
@ -380,18 +381,18 @@ pub enum EncoderError<E> {
|
|||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl<E: StdError + 'static> StdError for EncoderError<E> {
|
||||
impl StdError for EncoderError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match self {
|
||||
EncoderError::Body(err) => Some(err),
|
||||
EncoderError::Body(err) => Some(&**err),
|
||||
EncoderError::Blocking(err) => Some(err),
|
||||
EncoderError::Io(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: StdError + 'static> From<EncoderError<E>> for crate::Error {
|
||||
fn from(err: EncoderError<E>) -> Self {
|
||||
impl From<EncoderError> for crate::Error {
|
||||
fn from(err: EncoderError) -> Self {
|
||||
crate::Error::new_encoder().with_cause(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::body::{BodySize, MessageBody};
|
||||
use crate::error::Error;
|
||||
use crate::h1::{Codec, Message};
|
||||
use crate::response::Response;
|
||||
use crate::{
|
||||
body::{BodySize, MessageBody},
|
||||
error::Error,
|
||||
h1::{Codec, Message},
|
||||
response::Response,
|
||||
};
|
||||
|
||||
/// Send HTTP/1 response
|
||||
#[pin_project::pin_project]
|
||||
pub struct SendResponse<T, B> {
|
||||
res: Option<Message<(Response<()>, BodySize)>>,
|
||||
#[pin]
|
||||
body: Option<B>,
|
||||
#[pin]
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
pin_project! {
|
||||
/// Send HTTP/1 response
|
||||
pub struct SendResponse<T, B> {
|
||||
res: Option<Message<(Response<()>, BodySize)>>,
|
||||
|
||||
#[pin]
|
||||
body: Option<B>,
|
||||
|
||||
#[pin]
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> SendResponse<T, B>
|
||||
|
|
|
@ -51,7 +51,7 @@ where
|
|||
{
|
||||
pub(crate) fn new(
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
mut connection: Connection<T, Bytes>,
|
||||
mut conn: Connection<T, Bytes>,
|
||||
on_connect_data: OnConnectData,
|
||||
config: ServiceConfig,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
|
@ -66,14 +66,14 @@ where
|
|||
})
|
||||
.unwrap_or_else(|| Box::pin(sleep(dur))),
|
||||
on_flight: false,
|
||||
ping_pong: connection.ping_pong().unwrap(),
|
||||
ping_pong: conn.ping_pong().unwrap(),
|
||||
});
|
||||
|
||||
Self {
|
||||
flow,
|
||||
config,
|
||||
peer_addr,
|
||||
connection,
|
||||
connection: conn,
|
||||
on_connect_data,
|
||||
ping_pong,
|
||||
_phantom: PhantomData,
|
||||
|
|
|
@ -15,7 +15,7 @@ use crate::{
|
|||
header::{self, IntoHeaderValue},
|
||||
http::{HeaderMap, StatusCode},
|
||||
message::{BoxedResponseHead, ResponseHead},
|
||||
ResponseBuilder,
|
||||
Error, ResponseBuilder,
|
||||
};
|
||||
|
||||
/// An HTTP response.
|
||||
|
@ -233,21 +233,16 @@ impl<B: Default> Default for Response<B> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: fix this impl
|
||||
// impl<B, I, E> From<Result<I, E>> for Response<BoxBody>
|
||||
// where
|
||||
// B: MessageBody + 'static,
|
||||
// B::Error: Into<Box<dyn StdError + 'static>>,
|
||||
// I: Into<Response<B>>,
|
||||
// E: Into<Error>,
|
||||
// {
|
||||
// fn from(res: Result<I, E>) -> Self {
|
||||
// match res {
|
||||
// Ok(val) => val.into(),
|
||||
// Err(err) => err.into().into(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>>
|
||||
for Response<BoxBody>
|
||||
{
|
||||
fn from(res: Result<I, E>) -> Self {
|
||||
match res {
|
||||
Ok(val) => val.into(),
|
||||
Err(err) => Response::from(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResponseBuilder> for Response<BoxBody> {
|
||||
fn from(mut builder: ResponseBuilder) -> Self {
|
||||
|
@ -288,12 +283,14 @@ impl From<String> for Response<String> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: was this is useful impl
|
||||
// impl<'a> From<&'a String> for Response<&'a String> {
|
||||
// fn from(val: &'a String) -> Self {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
impl From<&String> for Response<String> {
|
||||
fn from(val: &String) -> Self {
|
||||
let mut res = Response::with_body(StatusCode::OK, val.clone());
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for Response<Bytes> {
|
||||
fn from(val: Bytes) -> Self {
|
||||
|
@ -322,8 +319,6 @@ impl From<ByteString> for Response<ByteString> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: impl into Response for ByteString
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -239,7 +239,6 @@ impl ResponseBuilder {
|
|||
{
|
||||
match self.message_body(body) {
|
||||
Ok(res) => res.map_body(|_, body| EitherBody::left(body)),
|
||||
// TODO: add error path
|
||||
Err(err) => Response::from(err).map_body(|_, body| EitherBody::right(body)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use actix_service::{
|
|||
fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
|
||||
};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use pin_project::pin_project;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
|
@ -519,23 +519,27 @@ where
|
|||
|
||||
match proto {
|
||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||
state: State::H2Handshake(Some((
|
||||
h2::handshake_with_timeout(io, &self.cfg),
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
))),
|
||||
state: State::H2Handshake {
|
||||
handshake: Some((
|
||||
h2::handshake_with_timeout(io, &self.cfg),
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
)),
|
||||
},
|
||||
},
|
||||
|
||||
Protocol::Http1 => HttpServiceHandlerResponse {
|
||||
state: State::H1(h1::Dispatcher::new(
|
||||
io,
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
)),
|
||||
state: State::H1 {
|
||||
dispatcher: h1::Dispatcher::new(
|
||||
io,
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
proto => unimplemented!("Unsupported HTTP version: {:?}.", proto),
|
||||
|
@ -543,58 +547,67 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[pin_project(project = StateProj)]
|
||||
enum State<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead,
|
||||
T: AsyncWrite,
|
||||
T: Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S: Service<Request>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
H1(#[pin] h1::Dispatcher<T, S, B, X, U>),
|
||||
H2(#[pin] h2::Dispatcher<T, S, B, X, U>),
|
||||
H2Handshake(
|
||||
Option<(
|
||||
h2::HandshakeWithTimeout<T>,
|
||||
ServiceConfig,
|
||||
Rc<HttpFlow<S, X, U>>,
|
||||
OnConnectData,
|
||||
Option<net::SocketAddr>,
|
||||
)>,
|
||||
),
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
H1 { #[pin] dispatcher: h1::Dispatcher<T, S, B, X, U> },
|
||||
H2 { #[pin] dispatcher: h2::Dispatcher<T, S, B, X, U> },
|
||||
H2Handshake {
|
||||
handshake: Option<(
|
||||
h2::HandshakeWithTimeout<T>,
|
||||
ServiceConfig,
|
||||
Rc<HttpFlow<S, X, U>>,
|
||||
OnConnectData,
|
||||
Option<net::SocketAddr>,
|
||||
)>,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_project]
|
||||
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
pin_project! {
|
||||
pub struct HttpServiceHandlerResponse<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead,
|
||||
T: AsyncWrite,
|
||||
T: Unpin,
|
||||
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>> + 'static,
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
S::Error: 'static,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Response: 'static,
|
||||
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
B: MessageBody,
|
||||
B::Error: Into<Box<dyn StdError>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
#[pin]
|
||||
state: State<T, S, B, X, U>,
|
||||
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
#[pin]
|
||||
state: State<T, S, B, X, U>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B, X, U> Future for HttpServiceHandlerResponse<T, S, B, X, U>
|
||||
|
@ -619,23 +632,24 @@ where
|
|||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.as_mut().project().state.project() {
|
||||
StateProj::H1(disp) => disp.poll(cx),
|
||||
StateProj::H2(disp) => disp.poll(cx),
|
||||
StateProj::H2Handshake(data) => {
|
||||
StateProj::H1 { dispatcher } => dispatcher.poll(cx),
|
||||
StateProj::H2 { dispatcher } => dispatcher.poll(cx),
|
||||
StateProj::H2Handshake { handshake: data } => {
|
||||
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
||||
Ok((conn, timer)) => {
|
||||
let (_, cfg, srv, on_connect_data, peer_addr) =
|
||||
let (_, config, flow, on_connect_data, peer_addr) =
|
||||
data.take().unwrap();
|
||||
self.as_mut().project().state.set(State::H2(
|
||||
h2::Dispatcher::new(
|
||||
srv,
|
||||
|
||||
self.as_mut().project().state.set(State::H2 {
|
||||
dispatcher: h2::Dispatcher::new(
|
||||
flow,
|
||||
conn,
|
||||
on_connect_data,
|
||||
cfg,
|
||||
config,
|
||||
peer_addr,
|
||||
timer,
|
||||
),
|
||||
));
|
||||
});
|
||||
self.poll(cx)
|
||||
}
|
||||
Err(err) => {
|
||||
|
|
|
@ -4,17 +4,21 @@ use std::task::{Context, Poll};
|
|||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_service::{IntoService, Service};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{Codec, Frame, Message};
|
||||
|
||||
#[pin_project::pin_project]
|
||||
pub struct Dispatcher<S, T>
|
||||
where
|
||||
S: Service<Frame, Response = Message> + 'static,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
#[pin]
|
||||
inner: inner::Dispatcher<S, T, Codec, Message>,
|
||||
pin_project! {
|
||||
pub struct Dispatcher<S, T>
|
||||
where
|
||||
S: Service<Frame, Response = Message>,
|
||||
S: 'static,
|
||||
T: AsyncRead,
|
||||
T: AsyncWrite,
|
||||
{
|
||||
#[pin]
|
||||
inner: inner::Dispatcher<S, T, Codec, Message>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Dispatcher<S, T>
|
||||
|
|
|
@ -22,7 +22,7 @@ actix-web = { version = "4.0.0-beta.11", default-features = false }
|
|||
bytes = "1"
|
||||
bytestring = "1"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
pin-project = "1.0.0"
|
||||
pin-project-lite = "0.2"
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
@ -30,6 +30,7 @@ use actix_web::{
|
|||
use bytes::{Bytes, BytesMut};
|
||||
use bytestring::ByteString;
|
||||
use futures_core::Stream;
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::sync::oneshot::Sender;
|
||||
|
||||
/// Perform WebSocket handshake and start actor.
|
||||
|
@ -462,13 +463,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[pin_project::pin_project]
|
||||
struct WsStream<S> {
|
||||
#[pin]
|
||||
stream: S,
|
||||
decoder: Codec,
|
||||
buf: BytesMut,
|
||||
closed: bool,
|
||||
pin_project! {
|
||||
struct WsStream<S> {
|
||||
#[pin]
|
||||
stream: S,
|
||||
decoder: Codec,
|
||||
buf: BytesMut,
|
||||
closed: bool,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> WsStream<S>
|
||||
|
|
54
src/app.rs
54
src/app.rs
|
@ -1,37 +1,35 @@
|
|||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc};
|
||||
|
||||
use actix_http::body::{BoxBody, MessageBody};
|
||||
use actix_http::{Extensions, Request};
|
||||
use actix_service::boxed::{self, BoxServiceFactory};
|
||||
use actix_http::{
|
||||
body::{BoxBody, MessageBody},
|
||||
Extensions, Request,
|
||||
};
|
||||
use actix_service::{
|
||||
apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform,
|
||||
apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt,
|
||||
Transform,
|
||||
};
|
||||
use futures_util::future::FutureExt as _;
|
||||
|
||||
use crate::app_service::{AppEntry, AppInit, AppRoutingFactory};
|
||||
use crate::config::ServiceConfig;
|
||||
use crate::data::{Data, DataFactory, FnDataFactory};
|
||||
use crate::dev::ResourceDef;
|
||||
use crate::error::Error;
|
||||
use crate::resource::Resource;
|
||||
use crate::route::Route;
|
||||
use crate::service::{
|
||||
AppServiceFactory, HttpServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
||||
ServiceResponse,
|
||||
use crate::{
|
||||
app_service::{AppEntry, AppInit, AppRoutingFactory},
|
||||
config::ServiceConfig,
|
||||
data::{Data, DataFactory, FnDataFactory},
|
||||
dev::ResourceDef,
|
||||
error::Error,
|
||||
resource::Resource,
|
||||
route::Route,
|
||||
service::{
|
||||
AppServiceFactory, BoxedHttpServiceFactory, HttpServiceFactory, ServiceFactoryWrapper,
|
||||
ServiceRequest, ServiceResponse,
|
||||
},
|
||||
};
|
||||
|
||||
type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
|
||||
/// Application builder - structure that follows the builder pattern
|
||||
/// for building application instances.
|
||||
pub struct App<T, B> {
|
||||
endpoint: T,
|
||||
services: Vec<Box<dyn AppServiceFactory>>,
|
||||
default: Option<Rc<HttpNewService>>,
|
||||
default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||
factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
|
||||
data_factories: Vec<FnDataFactory>,
|
||||
external: Vec<ResourceDef>,
|
||||
|
@ -287,7 +285,7 @@ where
|
|||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
pub fn default_service<F, U>(mut self, f: F) -> Self
|
||||
pub fn default_service<F, U>(mut self, svc: F) -> Self
|
||||
where
|
||||
F: IntoServiceFactory<U, ServiceRequest>,
|
||||
U: ServiceFactory<
|
||||
|
@ -298,10 +296,12 @@ where
|
|||
> + 'static,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
// create and configure default resource
|
||||
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
|
||||
|e| log::error!("Can not construct default service: {:?}", e),
|
||||
))));
|
||||
let svc = svc
|
||||
.into_factory()
|
||||
.map(|res| res.map_into_boxed_body())
|
||||
.map_init_err(|e| log::error!("Can not construct default service: {:?}", e));
|
||||
|
||||
self.default = Some(Rc::new(boxed::factory(svc)));
|
||||
|
||||
self
|
||||
}
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
use std::future::Future;
|
||||
|
||||
use actix_service::{
|
||||
boxed::{self, BoxServiceFactory},
|
||||
fn_service,
|
||||
};
|
||||
use actix_service::{boxed, fn_service};
|
||||
|
||||
use crate::{
|
||||
body::EitherBody,
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error, FromRequest, HttpResponse, Responder,
|
||||
body::MessageBody,
|
||||
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
|
||||
BoxError, FromRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// A request handler is an async function that accepts zero or more parameters that can be
|
||||
|
@ -25,20 +22,14 @@ where
|
|||
fn call(&self, param: T) -> R;
|
||||
}
|
||||
|
||||
pub fn handler_service<F, T, R>(
|
||||
handler: F,
|
||||
) -> BoxServiceFactory<
|
||||
(),
|
||||
ServiceRequest,
|
||||
ServiceResponse<EitherBody<<R::Output as Responder>::Body>>,
|
||||
Error,
|
||||
(),
|
||||
>
|
||||
pub fn handler_service<F, T, R>(handler: F) -> BoxedHttpServiceFactory
|
||||
where
|
||||
F: Handler<T, R>,
|
||||
T: FromRequest,
|
||||
R: Future,
|
||||
R::Output: Responder,
|
||||
<R::Output as Responder>::Body: MessageBody,
|
||||
<<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
|
||||
{
|
||||
boxed::factory(fn_service(move |req: ServiceRequest| {
|
||||
let handler = handler.clone();
|
||||
|
@ -46,15 +37,13 @@ where
|
|||
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).map_body(|_, body| EitherBody::right(body))
|
||||
}
|
||||
Err(err) => HttpResponse::from_error(err).map_into_boxed_body(),
|
||||
|
||||
Ok(data) => handler
|
||||
.call(data)
|
||||
.await
|
||||
.respond_to(&req)
|
||||
.map_body(|_, body| EitherBody::left(body)),
|
||||
.map_into_boxed_body(),
|
||||
};
|
||||
|
||||
Ok(ServiceResponse::new(req, res))
|
||||
|
|
|
@ -115,3 +115,5 @@ pub use crate::scope::Scope;
|
|||
pub use crate::server::HttpServer;
|
||||
// TODO: is exposing the error directly really needed
|
||||
pub use crate::types::{Either, EitherExtractError};
|
||||
|
||||
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{cell::RefCell, error::Error as StdError, fmt, future::Future, rc::Rc};
|
||||
use std::{cell::RefCell, fmt, future::Future, rc::Rc};
|
||||
|
||||
use actix_http::Extensions;
|
||||
use actix_router::{IntoPatterns, Patterns};
|
||||
|
@ -21,7 +21,7 @@ use crate::{
|
|||
BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest,
|
||||
ServiceResponse,
|
||||
},
|
||||
Error, FromRequest, HttpResponse,
|
||||
BoxError, Error, FromRequest, HttpResponse,
|
||||
};
|
||||
|
||||
/// *Resource* is an entry in resources table which corresponds to requested URL.
|
||||
|
@ -239,9 +239,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>>,
|
||||
<R::Output as Responder>::Body: MessageBody,
|
||||
<<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
|
||||
{
|
||||
self.routes.push(Route::new().to(handler));
|
||||
self
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::error::Error as StdError;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use actix_http::{
|
||||
body::{BoxBody, EitherBody, MessageBody},
|
||||
|
@ -6,7 +6,7 @@ use actix_http::{
|
|||
};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use crate::{Error, HttpRequest, HttpResponse, HttpResponseBuilder};
|
||||
use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder};
|
||||
|
||||
/// Trait implemented by types that can be converted to an HTTP response.
|
||||
///
|
||||
|
@ -99,7 +99,7 @@ impl Responder for actix_http::ResponseBuilder {
|
|||
impl<T> Responder for Option<T>
|
||||
where
|
||||
T: Responder,
|
||||
<T::Body as MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
|
||||
<T::Body as MessageBody>::Error: Into<BoxError>,
|
||||
{
|
||||
type Body = EitherBody<T::Body>;
|
||||
|
||||
|
@ -118,7 +118,7 @@ where
|
|||
impl<T, E> Responder for Result<T, E>
|
||||
where
|
||||
T: Responder,
|
||||
<T::Body as MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
|
||||
<T::Body as MessageBody>::Error: Into<BoxError>,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Body = EitherBody<T::Body>;
|
||||
|
@ -129,8 +129,8 @@ where
|
|||
.respond_to(req)
|
||||
.map_body(|_, body| EitherBody::left(body)),
|
||||
|
||||
Err(e) => {
|
||||
HttpResponse::from_error(e.into()).map_body(|_, body| EitherBody::right(body))
|
||||
Err(err) => {
|
||||
HttpResponse::from_error(err.into()).map_body(|_, body| EitherBody::right(body))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,25 +170,22 @@ impl_responder_by_forward_into_base_response!(BytesMut);
|
|||
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;
|
||||
macro_rules! impl_into_string_responder {
|
||||
($res:ty) => {
|
||||
impl Responder for $res {
|
||||
type Body = String;
|
||||
|
||||
// fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
// HttpResponse::Ok().content_type($ct).body(self)
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
let string: String = self.into();
|
||||
let res: actix_http::Response<_> = string.into();
|
||||
res.into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ($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_into_string_responder!(&'_ String);
|
||||
impl_into_string_responder!(Cow<'_, str>);
|
||||
|
||||
/// Allows overriding status code and headers for a responder.
|
||||
pub struct CustomResponder<T> {
|
||||
|
@ -257,7 +254,7 @@ impl<T: Responder> CustomResponder<T> {
|
|||
impl<T> Responder for CustomResponder<T>
|
||||
where
|
||||
T: Responder,
|
||||
<T::Body as MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
|
||||
<T::Body as MessageBody>::Error: Into<BoxError>,
|
||||
{
|
||||
type Body = EitherBody<T::Body>;
|
||||
|
||||
|
@ -265,7 +262,7 @@ where
|
|||
let headers = match self.headers {
|
||||
Ok(headers) => headers,
|
||||
Err(err) => {
|
||||
return HttpResponse::from_error(Error::from(err))
|
||||
return HttpResponse::from_error(err)
|
||||
.map_body(|_, body| EitherBody::right(body))
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
|||
};
|
||||
|
||||
use actix_http::{
|
||||
body::{BoxBody, MessageBody},
|
||||
body::{BoxBody, EitherBody, MessageBody},
|
||||
http::{header::HeaderMap, StatusCode},
|
||||
Extensions, Response, ResponseHead,
|
||||
};
|
||||
|
@ -228,7 +228,15 @@ impl<B> HttpResponse<B> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: old into_body equivalent, maybe
|
||||
// TODO: docs for the body map methods below
|
||||
|
||||
pub fn map_into_left_body<R>(self) -> HttpResponse<EitherBody<B, R>> {
|
||||
self.map_body(|_, body| EitherBody::left(body))
|
||||
}
|
||||
|
||||
pub fn map_into_right_body<L>(self) -> HttpResponse<EitherBody<L, B>> {
|
||||
self.map_body(|_, body| EitherBody::right(body))
|
||||
}
|
||||
|
||||
pub fn map_into_boxed_body(self) -> HttpResponse<BoxBody>
|
||||
where
|
||||
|
|
28
src/route.rs
28
src/route.rs
|
@ -1,11 +1,11 @@
|
|||
#![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`)
|
||||
|
||||
use std::{error::Error as StdError, future::Future, mem, rc::Rc};
|
||||
use std::{future::Future, mem, rc::Rc};
|
||||
|
||||
use actix_http::http::Method;
|
||||
use actix_service::{
|
||||
boxed::{self, BoxService, BoxServiceFactory},
|
||||
Service, ServiceFactory, ServiceFactoryExt,
|
||||
boxed::{self, BoxService},
|
||||
fn_service, Service, ServiceFactory, ServiceFactoryExt,
|
||||
};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
|
||||
|
@ -13,8 +13,8 @@ use crate::{
|
|||
body::MessageBody,
|
||||
guard::{self, Guard},
|
||||
handler::{handler_service, Handler},
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error, FromRequest, HttpResponse, Responder,
|
||||
service::{BoxedHttpServiceFactory, ServiceRequest, ServiceResponse},
|
||||
BoxError, Error, FromRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// Resource route definition
|
||||
|
@ -22,7 +22,7 @@ use crate::{
|
|||
/// Route uses builder-like pattern for configuration.
|
||||
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
||||
pub struct Route {
|
||||
service: BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>,
|
||||
service: BoxedHttpServiceFactory,
|
||||
guards: Rc<Vec<Box<dyn Guard>>>,
|
||||
}
|
||||
|
||||
|
@ -31,10 +31,9 @@ impl Route {
|
|||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Route {
|
||||
Route {
|
||||
// TODO: remove double boxing
|
||||
service: boxed::factory(
|
||||
handler_service(HttpResponse::NotFound).map(|res| res.map_into_boxed_body()),
|
||||
),
|
||||
service: boxed::factory(fn_service(|req: ServiceRequest| async {
|
||||
Ok(req.into_response(HttpResponse::NotFound()))
|
||||
})),
|
||||
guards: Rc::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
@ -185,13 +184,10 @@ 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>>,
|
||||
<R::Output as Responder>::Body: MessageBody,
|
||||
<<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
|
||||
{
|
||||
// TODO: remove double boxing
|
||||
self.service =
|
||||
boxed::factory(handler_service(handler).map(|res| res.map_into_boxed_body()));
|
||||
self.service = handler_service(handler);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
@ -27,9 +27,9 @@ use crate::{
|
|||
Error, HttpRequest, HttpResponse,
|
||||
};
|
||||
|
||||
pub(crate) type BoxedHttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
|
||||
pub(crate) type BoxedHttpService = BoxService<ServiceRequest, ServiceResponse<BoxBody>, Error>;
|
||||
pub(crate) type BoxedHttpServiceFactory =
|
||||
BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
|
||||
BoxServiceFactory<(), ServiceRequest, ServiceResponse<BoxBody>, Error, ()>;
|
||||
|
||||
pub trait HttpServiceFactory {
|
||||
fn register(self, config: &mut AppService);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! For either helper, see [`Either`].
|
||||
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
future::Future,
|
||||
mem,
|
||||
pin::Pin,
|
||||
|
@ -15,7 +14,7 @@ use pin_project_lite::pin_project;
|
|||
use crate::{
|
||||
body, dev,
|
||||
web::{Form, Json},
|
||||
Error, FromRequest, HttpRequest, HttpResponse, Responder,
|
||||
BoxError, Error, FromRequest, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// Combines two extractor or responder types into a single type.
|
||||
|
@ -145,9 +144,9 @@ 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>>,
|
||||
<L::Body as dev::MessageBody>::Error: Into<BoxError>,
|
||||
R: Responder,
|
||||
<R::Body as dev::MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
|
||||
<R::Body as dev::MessageBody>::Error: Into<BoxError>,
|
||||
{
|
||||
type Body = body::EitherBody<L::Body, R::Body>;
|
||||
|
||||
|
|
Loading…
Reference in New Issue