1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-06-25 06:39:22 +02:00

body ergonomics v3 (#2468)

This commit is contained in:
Rob Ede
2021-12-04 19:40:47 +00:00
committed by GitHub
parent a2d5c5a058
commit c7c02ef99d
84 changed files with 2134 additions and 1685 deletions

View File

@ -1,333 +0,0 @@
use std::{
borrow::Cow,
error::Error as StdError,
fmt, mem,
pin::Pin,
task::{Context, Poll},
};
use bytes::{Bytes, BytesMut};
use futures_core::Stream;
use pin_project::pin_project;
use crate::error::Error;
use super::{BodySize, BodyStream, MessageBody, MessageBodyMapErr, SizedStream};
#[deprecated(since = "4.0.0", note = "Renamed to `AnyBody`.")]
pub type Body = AnyBody;
/// Represents various types of HTTP message body.
#[pin_project(project = AnyBodyProj)]
#[derive(Clone)]
pub enum AnyBody<B = BoxBody> {
/// Empty response. `Content-Length` header is not set.
None,
/// Complete, in-memory response body.
Bytes(Bytes),
/// Generic / Other message body.
Body(#[pin] B),
}
impl AnyBody {
/// Constructs a "body" representing an empty response.
pub fn none() -> Self {
Self::None
}
/// Constructs a new, 0-length body.
pub fn empty() -> Self {
Self::Bytes(Bytes::new())
}
/// Create boxed body from generic message body.
pub fn new_boxed<B>(body: B) -> Self
where
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError + 'static>>,
{
Self::Body(BoxBody::from_body(body))
}
/// Constructs new `AnyBody` instance from a slice of bytes by copying it.
///
/// If your bytes container is owned, it may be cheaper to use a `From` impl.
pub fn copy_from_slice(s: &[u8]) -> Self {
Self::Bytes(Bytes::copy_from_slice(s))
}
#[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Renamed to `copy_from_slice`.")]
pub fn from_slice(s: &[u8]) -> Self {
Self::Bytes(Bytes::copy_from_slice(s))
}
}
impl<B> AnyBody<B>
where
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError + 'static>>,
{
/// Create body from generic message body.
pub fn new(body: B) -> Self {
Self::Body(body)
}
pub fn into_boxed(self) -> AnyBody {
match self {
Self::None => AnyBody::None,
Self::Bytes(bytes) => AnyBody::Bytes(bytes),
Self::Body(body) => AnyBody::new_boxed(body),
}
}
}
impl<B> MessageBody for AnyBody<B>
where
B: MessageBody,
B::Error: Into<Box<dyn StdError>> + 'static,
{
type Error = Error;
fn size(&self) -> BodySize {
match self {
AnyBody::None => BodySize::None,
AnyBody::Bytes(ref bin) => BodySize::Sized(bin.len() as u64),
AnyBody::Body(ref body) => body.size(),
}
}
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match self.project() {
AnyBodyProj::None => Poll::Ready(None),
AnyBodyProj::Bytes(bin) => {
let len = bin.len();
if len == 0 {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(mem::take(bin))))
}
}
AnyBodyProj::Body(body) => body
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err)),
}
}
}
impl PartialEq for AnyBody {
fn eq(&self, other: &AnyBody) -> bool {
match *self {
AnyBody::None => matches!(*other, AnyBody::None),
AnyBody::Bytes(ref b) => match *other {
AnyBody::Bytes(ref b2) => b == b2,
_ => false,
},
AnyBody::Body(_) => false,
}
}
}
impl<S: fmt::Debug> fmt::Debug for AnyBody<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
AnyBody::None => write!(f, "AnyBody::None"),
AnyBody::Bytes(ref bytes) => write!(f, "AnyBody::Bytes({:?})", bytes),
AnyBody::Body(ref stream) => write!(f, "AnyBody::Message({:?})", stream),
}
}
}
impl<B> From<&'static str> for AnyBody<B> {
fn from(string: &'static str) -> Self {
Self::Bytes(Bytes::from_static(string.as_ref()))
}
}
impl<B> From<&'static [u8]> for AnyBody<B> {
fn from(bytes: &'static [u8]) -> Self {
Self::Bytes(Bytes::from_static(bytes))
}
}
impl<B> From<Vec<u8>> for AnyBody<B> {
fn from(vec: Vec<u8>) -> Self {
Self::Bytes(Bytes::from(vec))
}
}
impl<B> From<String> for AnyBody<B> {
fn from(string: String) -> Self {
Self::Bytes(Bytes::from(string))
}
}
impl<B> From<&'_ String> for AnyBody<B> {
fn from(string: &String) -> Self {
Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&string)))
}
}
impl<B> From<Cow<'_, str>> for AnyBody<B> {
fn from(string: Cow<'_, str>) -> Self {
match string {
Cow::Owned(s) => Self::from(s),
Cow::Borrowed(s) => {
Self::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)))
}
}
}
}
impl<B> From<Bytes> for AnyBody<B> {
fn from(bytes: Bytes) -> Self {
Self::Bytes(bytes)
}
}
impl<B> From<BytesMut> for AnyBody<B> {
fn from(bytes: BytesMut) -> Self {
Self::Bytes(bytes.freeze())
}
}
impl<S, E> From<SizedStream<S>> for AnyBody<SizedStream<S>>
where
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Box<dyn StdError>> + 'static,
{
fn from(stream: SizedStream<S>) -> Self {
AnyBody::new(stream)
}
}
impl<S, E> From<SizedStream<S>> for AnyBody
where
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Box<dyn StdError>> + 'static,
{
fn from(stream: SizedStream<S>) -> Self {
AnyBody::new_boxed(stream)
}
}
impl<S, E> From<BodyStream<S>> for AnyBody<BodyStream<S>>
where
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Box<dyn StdError>> + 'static,
{
fn from(stream: BodyStream<S>) -> Self {
AnyBody::new(stream)
}
}
impl<S, E> From<BodyStream<S>> for AnyBody
where
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Box<dyn StdError>> + 'static,
{
fn from(stream: BodyStream<S>) -> Self {
AnyBody::new_boxed(stream)
}
}
/// A boxed message body with boxed errors.
pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
impl BoxBody {
/// Boxes a `MessageBody` and any errors it generates.
pub fn from_body<B>(body: B) -> Self
where
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError + 'static>>,
{
let body = MessageBodyMapErr::new(body, Into::into);
Self(Box::pin(body))
}
/// Returns a mutable pinned reference to the inner message body type.
pub fn as_pin_mut(
&mut self,
) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
self.0.as_mut()
}
}
impl fmt::Debug for BoxBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("BoxAnyBody(dyn MessageBody)")
}
}
impl MessageBody for BoxBody {
type Error = Error;
fn size(&self) -> BodySize {
self.0.size()
}
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
self.0
.as_mut()
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err))
}
}
#[cfg(test)]
mod tests {
use std::marker::PhantomPinned;
use static_assertions::{assert_impl_all, assert_not_impl_all};
use super::*;
use crate::body::to_bytes;
struct PinType(PhantomPinned);
impl MessageBody for PinType {
type Error = crate::Error;
fn size(&self) -> BodySize {
unimplemented!()
}
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
unimplemented!()
}
}
assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin);
assert_impl_all!(AnyBody<AnyBody<()>>: MessageBody, fmt::Debug, Send, Sync, Unpin);
assert_impl_all!(AnyBody<Bytes>: MessageBody, fmt::Debug, Send, Sync, Unpin);
assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin);
assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin);
assert_impl_all!(AnyBody<PinType>: MessageBody);
assert_not_impl_all!(AnyBody: Send, Sync, Unpin);
assert_not_impl_all!(BoxBody: Send, Sync, Unpin);
assert_not_impl_all!(AnyBody<PinType>: Send, Sync, Unpin);
#[actix_rt::test]
async fn nested_boxed_body() {
let body = AnyBody::copy_from_slice(&[1, 2, 3]);
let boxed_body = BoxBody::from_body(BoxBody::from_body(body));
assert_eq!(
to_bytes(boxed_body).await.unwrap(),
Bytes::from(vec![1, 2, 3]),
);
}
}

View File

@ -20,6 +20,8 @@ pin_project! {
}
}
// TODO: from_infallible method
impl<S, E> BodyStream<S>
where
S: Stream<Item = Result<Bytes, E>>,
@ -75,6 +77,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 +169,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 +189,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();

View File

@ -0,0 +1,80 @@
use std::{
error::Error as StdError,
fmt,
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use super::{BodySize, MessageBody, MessageBodyMapErr};
use crate::Error;
/// A boxed message body with boxed errors.
pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
impl BoxBody {
/// Boxes a `MessageBody` and any errors it generates.
pub fn new<B>(body: B) -> Self
where
B: MessageBody + 'static,
{
let body = MessageBodyMapErr::new(body, Into::into);
Self(Box::pin(body))
}
/// Returns a mutable pinned reference to the inner message body type.
pub fn as_pin_mut(
&mut self,
) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
self.0.as_mut()
}
}
impl fmt::Debug for BoxBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("BoxBody(dyn MessageBody)")
}
}
impl MessageBody for BoxBody {
type Error = Error;
fn size(&self) -> BodySize {
self.0.size()
}
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
self.0
.as_mut()
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err))
}
}
#[cfg(test)]
mod tests {
use static_assertions::{assert_impl_all, assert_not_impl_all};
use super::*;
use crate::body::to_bytes;
assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin);
assert_not_impl_all!(BoxBody: Send, Sync, Unpin);
#[actix_rt::test]
async fn nested_boxed_body() {
let body = Bytes::from_static(&[1, 2, 3]);
let boxed_body = BoxBody::new(BoxBody::new(body));
assert_eq!(
to_bytes(boxed_body).await.unwrap(),
Bytes::from(vec![1, 2, 3]),
);
}
}

View File

@ -0,0 +1,83 @@
use std::{
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use pin_project_lite::pin_project;
use super::{BodySize, BoxBody, MessageBody};
use crate::Error;
pin_project! {
#[project = EitherBodyProj]
#[derive(Debug, Clone)]
pub enum EitherBody<L, R = BoxBody> {
/// A body of type `L`.
Left { #[pin] body: L },
/// A body of type `R`.
Right { #[pin] body: R },
}
}
impl<L> EitherBody<L, BoxBody> {
/// Creates new `EitherBody` using left variant and boxed right variant.
pub fn new(body: L) -> Self {
Self::Left { body }
}
}
impl<L, R> EitherBody<L, R> {
/// Creates new `EitherBody` using left variant.
pub fn left(body: L) -> Self {
Self::Left { body }
}
/// Creates new `EitherBody` using right variant.
pub fn right(body: R) -> Self {
Self::Right { body }
}
}
impl<L, R> MessageBody for EitherBody<L, R>
where
L: MessageBody + 'static,
R: MessageBody + 'static,
{
type Error = Error;
fn size(&self) -> BodySize {
match self {
EitherBody::Left { body } => body.size(),
EitherBody::Right { body } => body.size(),
}
}
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match self.project() {
EitherBodyProj::Left { body } => body
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err)),
EitherBodyProj::Right { body } => body
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn type_parameter_inference() {
let _body: EitherBody<(), _> = EitherBody::new(());
let _body: EitherBody<_, ()> = EitherBody::left(());
let _body: EitherBody<(), _> = EitherBody::right(());
}
}

View File

@ -2,6 +2,7 @@
use std::{
convert::Infallible,
error::Error as StdError,
mem,
pin::Pin,
task::{Context, Poll},
@ -13,9 +14,12 @@ 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;
// TODO: consider this bound to only fmt::Display since the error type is not really used
// and there is an impl for Into<Box<StdError>> on String
type Error: Into<Box<dyn StdError>>;
/// Body size hint.
fn size(&self) -> BodySize;
@ -27,152 +31,218 @@ pub trait MessageBody {
) -> Poll<Option<Result<Bytes, Self::Error>>>;
}
impl MessageBody for () {
type Error = Infallible;
mod foreign_impls {
use super::*;
fn size(&self) -> BodySize {
BodySize::Sized(0)
}
impl MessageBody for Infallible {
type Error = Infallible;
fn poll_next(
self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Poll::Ready(None)
}
}
#[inline]
fn size(&self) -> BodySize {
match *self {}
}
impl<B> MessageBody for Box<B>
where
B: MessageBody + Unpin,
{
type Error = B::Error;
fn size(&self) -> BodySize {
self.as_ref().size()
}
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Pin::new(self.get_mut().as_mut()).poll_next(cx)
}
}
impl<B> MessageBody for Pin<Box<B>>
where
B: MessageBody,
{
type Error = B::Error;
fn size(&self) -> BodySize {
self.as_ref().size()
}
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
self.as_mut().poll_next(cx)
}
}
impl MessageBody for Bytes {
type Error = Infallible;
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
fn poll_next(
self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(mem::take(self.get_mut()))))
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match *self {}
}
}
}
impl MessageBody for BytesMut {
type Error = Infallible;
impl MessageBody for () {
type Error = Infallible;
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(0)
}
fn poll_next(
self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze())))
}
}
}
impl MessageBody for &'static str {
type Error = Infallible;
impl<B> MessageBody for Box<B>
where
B: MessageBody + Unpin,
{
type Error = B::Error;
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn size(&self) -> BodySize {
self.as_ref().size()
}
fn poll_next(
self: Pin<&mut Self>,
_: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(Bytes::from_static(
mem::take(self.get_mut()).as_ref(),
))))
#[inline]
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Pin::new(self.get_mut().as_mut()).poll_next(cx)
}
}
}
impl MessageBody for Vec<u8> {
type Error = Infallible;
impl<B> MessageBody for Pin<Box<B>>
where
B: MessageBody,
{
type Error = B::Error;
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn size(&self) -> BodySize {
self.as_ref().size()
}
fn poll_next(
self: Pin<&mut Self>,
_: &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())))))
#[inline]
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
self.as_mut().poll_next(cx)
}
}
}
impl MessageBody for String {
type Error = Infallible;
impl MessageBody for &'static [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 {
let bytes = mem::take(self.get_mut());
let bytes = Bytes::from_static(bytes);
Poll::Ready(Some(Ok(bytes)))
}
}
}
fn poll_next(
self: Pin<&mut Self>,
_: &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()).into_bytes(),
))))
impl MessageBody for 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 {
let bytes = mem::take(self.get_mut());
Poll::Ready(Some(Ok(bytes)))
}
}
}
impl MessageBody for BytesMut {
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 bytes = mem::take(self.get_mut()).freeze();
Poll::Ready(Some(Ok(bytes)))
}
}
}
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 {
let bytes = mem::take(self.get_mut());
Poll::Ready(Some(Ok(Bytes::from(bytes))))
}
}
}
impl MessageBody for &'static 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 string = mem::take(self.get_mut());
let bytes = Bytes::from_static(string.as_bytes());
Poll::Ready(Some(Ok(bytes)))
}
}
}
impl MessageBody for 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 {
let string = mem::take(self.get_mut());
Poll::Ready(Some(Ok(Bytes::from(string))))
}
}
}
impl MessageBody for bytestring::ByteString {
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>>> {
let string = mem::take(self.get_mut());
Poll::Ready(Some(Ok(string.into_bytes())))
}
}
}
@ -202,6 +272,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;
@ -226,3 +297,129 @@ where
}
}
}
#[cfg(test)]
mod tests {
use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut};
use super::*;
macro_rules! assert_poll_next {
($pin:expr, $exp:expr) => {
assert_eq!(
poll_fn(|cx| $pin.as_mut().poll_next(cx))
.await
.unwrap() // unwrap option
.unwrap(), // unwrap result
$exp
);
};
}
macro_rules! assert_poll_next_none {
($pin:expr) => {
assert!(poll_fn(|cx| $pin.as_mut().poll_next(cx)).await.is_none());
};
}
#[actix_rt::test]
async fn boxing_equivalence() {
assert_eq!(().size(), BodySize::Sized(0));
assert_eq!(().size(), Box::new(()).size());
assert_eq!(().size(), Box::pin(()).size());
let pl = Box::new(());
pin!(pl);
assert_poll_next_none!(pl);
let mut pl = Box::pin(());
assert_poll_next_none!(pl);
}
#[actix_rt::test]
async fn test_unit() {
let pl = ();
assert_eq!(pl.size(), BodySize::Sized(0));
pin!(pl);
assert_poll_next_none!(pl);
}
#[actix_rt::test]
async fn test_static_str() {
assert_eq!("".size(), BodySize::Sized(0));
assert_eq!("test".size(), BodySize::Sized(4));
let pl = "test";
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_static_bytes() {
assert_eq!(b"".as_ref().size(), BodySize::Sized(0));
assert_eq!(b"test".as_ref().size(), BodySize::Sized(4));
let pl = b"test".as_ref();
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_vec() {
assert_eq!(vec![0; 0].size(), BodySize::Sized(0));
assert_eq!(Vec::from("test").size(), BodySize::Sized(4));
let pl = Vec::from("test");
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_bytes() {
assert_eq!(Bytes::new().size(), BodySize::Sized(0));
assert_eq!(Bytes::from_static(b"test").size(), BodySize::Sized(4));
let pl = Bytes::from_static(b"test");
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_bytes_mut() {
assert_eq!(BytesMut::new().size(), BodySize::Sized(0));
assert_eq!(BytesMut::from(b"test".as_ref()).size(), BodySize::Sized(4));
let pl = BytesMut::from("test");
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn test_string() {
assert_eq!(String::new().size(), BodySize::Sized(0));
assert_eq!("test".to_owned().size(), BodySize::Sized(4));
let pl = "test".to_owned();
pin!(pl);
assert_poll_next!(pl, Bytes::from("test"));
}
// down-casting used to be done with a method on MessageBody trait
// test is kept to demonstrate equivalence of Any trait
#[actix_rt::test]
async fn test_body_casting() {
let mut body = String::from("hello cast");
// let mut resp_body: &mut dyn MessageBody<Error = Error> = &mut body;
let resp_body: &mut dyn std::any::Any = &mut body;
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast");
let body = &mut resp_body.downcast_mut::<String>().unwrap();
body.push('!');
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast!");
let not_body = resp_body.downcast_ref::<()>();
assert!(not_body.is_none());
}
}

View File

@ -1,272 +1,20 @@
//! Traits and structures to aid consuming and writing HTTP payloads.
use std::task::Poll;
use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut};
use futures_core::ready;
#[allow(clippy::module_inception)]
mod body;
mod body_stream;
mod boxed;
mod either;
mod message_body;
mod none;
mod size;
mod sized_stream;
mod utils;
#[allow(deprecated)]
pub use self::body::{AnyBody, Body, BoxBody};
pub use self::body_stream::BodyStream;
pub use self::boxed::BoxBody;
pub use self::either::EitherBody;
pub use self::message_body::MessageBody;
pub(crate) use self::message_body::MessageBodyMapErr;
pub use self::none::None;
pub use self::size::BodySize;
pub use self::sized_stream::SizedStream;
/// Collects the body produced by a `MessageBody` implementation into `Bytes`.
///
/// Any errors produced by the body stream are returned immediately.
///
/// # Examples
/// ```
/// use actix_http::body::{AnyBody, to_bytes};
/// use bytes::Bytes;
///
/// # async fn test_to_bytes() {
/// let body = AnyBody::none();
/// let bytes = to_bytes(body).await.unwrap();
/// assert!(bytes.is_empty());
///
/// let body = AnyBody::copy_from_slice(b"123");
/// let bytes = to_bytes(body).await.unwrap();
/// assert_eq!(bytes, b"123"[..]);
/// # }
/// ```
pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
let cap = match body.size() {
BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
BodySize::Sized(size) => size as usize,
// good enough first guess for chunk size
BodySize::Stream => 32_768,
};
let mut buf = BytesMut::with_capacity(cap);
pin!(body);
poll_fn(|cx| loop {
let body = body.as_mut();
match ready!(body.poll_next(cx)) {
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
None => return Poll::Ready(Ok(())),
Some(Err(err)) => return Poll::Ready(Err(err)),
}
})
.await?;
Ok(buf.freeze())
}
#[cfg(test)]
mod tests {
use std::pin::Pin;
use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut};
use super::{to_bytes, AnyBody as TestAnyBody, BodySize, MessageBody as _};
impl AnyBody {
pub(crate) fn get_ref(&self) -> &[u8] {
match *self {
AnyBody::Bytes(ref bin) => bin,
_ => panic!(),
}
}
}
/// AnyBody alias because rustc does not (can not?) infer the default type parameter.
type AnyBody = TestAnyBody;
#[actix_rt::test]
async fn test_static_str() {
assert_eq!(AnyBody::from("").size(), BodySize::Sized(0));
assert_eq!(AnyBody::from("test").size(), BodySize::Sized(4));
assert_eq!(AnyBody::from("test").get_ref(), b"test");
assert_eq!("test".size(), BodySize::Sized(4));
assert_eq!(
poll_fn(|cx| Pin::new(&mut "test").poll_next(cx))
.await
.unwrap()
.ok(),
Some(Bytes::from("test"))
);
}
#[actix_rt::test]
async fn test_static_bytes() {
assert_eq!(AnyBody::from(b"test".as_ref()).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(b"test".as_ref()).get_ref(), b"test");
assert_eq!(
AnyBody::copy_from_slice(b"test".as_ref()).size(),
BodySize::Sized(4)
);
assert_eq!(
AnyBody::copy_from_slice(b"test".as_ref()).get_ref(),
b"test"
);
let sb = Bytes::from(&b"test"[..]);
pin!(sb);
assert_eq!(sb.size(), BodySize::Sized(4));
assert_eq!(
poll_fn(|cx| sb.as_mut().poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("test"))
);
}
#[actix_rt::test]
async fn test_vec() {
assert_eq!(AnyBody::from(Vec::from("test")).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(Vec::from("test")).get_ref(), b"test");
let test_vec = Vec::from("test");
pin!(test_vec);
assert_eq!(test_vec.size(), BodySize::Sized(4));
assert_eq!(
poll_fn(|cx| test_vec.as_mut().poll_next(cx))
.await
.unwrap()
.ok(),
Some(Bytes::from("test"))
);
}
#[actix_rt::test]
async fn test_bytes() {
let b = Bytes::from("test");
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
pin!(b);
assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!(
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("test"))
);
}
#[actix_rt::test]
async fn test_bytes_mut() {
let b = BytesMut::from("test");
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
pin!(b);
assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!(
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("test"))
);
}
#[actix_rt::test]
async fn test_string() {
let b = "test".to_owned();
assert_eq!(AnyBody::from(b.clone()).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(b.clone()).get_ref(), b"test");
assert_eq!(AnyBody::from(&b).size(), BodySize::Sized(4));
assert_eq!(AnyBody::from(&b).get_ref(), b"test");
pin!(b);
assert_eq!(b.size(), BodySize::Sized(4));
assert_eq!(
poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(),
Some(Bytes::from("test"))
);
}
#[actix_rt::test]
async fn test_unit() {
assert_eq!(().size(), BodySize::Sized(0));
assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx))
.await
.is_none());
}
#[actix_rt::test]
async fn test_box_and_pin() {
let val = Box::new(());
pin!(val);
assert_eq!(val.size(), BodySize::Sized(0));
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
let mut val = Box::pin(());
assert_eq!(val.size(), BodySize::Sized(0));
assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
}
#[actix_rt::test]
async fn test_body_eq() {
assert!(
AnyBody::Bytes(Bytes::from_static(b"1"))
== AnyBody::Bytes(Bytes::from_static(b"1"))
);
assert!(AnyBody::Bytes(Bytes::from_static(b"1")) != AnyBody::None);
}
#[actix_rt::test]
async fn test_body_debug() {
assert!(format!("{:?}", AnyBody::None).contains("Body::None"));
assert!(format!("{:?}", AnyBody::from(Bytes::from_static(b"1"))).contains('1'));
}
#[actix_rt::test]
async fn test_serde_json() {
use serde_json::{json, Value};
assert_eq!(
AnyBody::from(
serde_json::to_vec(&Value::String("test".to_owned())).unwrap()
)
.size(),
BodySize::Sized(6)
);
assert_eq!(
AnyBody::from(
serde_json::to_vec(&json!({"test-key":"test-value"})).unwrap()
)
.size(),
BodySize::Sized(25)
);
}
// down-casting used to be done with a method on MessageBody trait
// test is kept to demonstrate equivalence of Any trait
#[actix_rt::test]
async fn test_body_casting() {
let mut body = String::from("hello cast");
// let mut resp_body: &mut dyn MessageBody<Error = Error> = &mut body;
let resp_body: &mut dyn std::any::Any = &mut body;
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast");
let body = &mut resp_body.downcast_mut::<String>().unwrap();
body.push('!');
let body = resp_body.downcast_ref::<String>().unwrap();
assert_eq!(body, "hello cast!");
let not_body = resp_body.downcast_ref::<()>();
assert!(not_body.is_none());
}
#[actix_rt::test]
async fn test_to_bytes() {
let body = AnyBody::empty();
let bytes = to_bytes(body).await.unwrap();
assert!(bytes.is_empty());
let body = AnyBody::copy_from_slice(b"123");
let bytes = to_bytes(body).await.unwrap();
assert_eq!(bytes, b"123"[..]);
}
}
pub use self::utils::to_bytes;

View File

@ -0,0 +1,43 @@
use std::{
convert::Infallible,
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes;
use super::{BodySize, MessageBody};
/// Body type for responses that forbid payloads.
///
/// Distinct from an empty response which would contain a Content-Length header.
///
/// For an "empty" body, use `()` or `Bytes::new()`.
#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub struct None;
impl None {
/// Constructs new "none" body.
#[inline]
pub fn new() -> Self {
None
}
}
impl MessageBody for None {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::None
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Poll::Ready(Option::None)
}
}

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

@ -32,6 +32,8 @@ where
}
}
// TODO: from_infallible method
impl<S, E> MessageBody for SizedStream<S>
where
S: Stream<Item = Result<Bytes, E>>,

View File

@ -0,0 +1,78 @@
use std::task::Poll;
use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut};
use futures_core::ready;
use super::{BodySize, MessageBody};
/// Collects the body produced by a `MessageBody` implementation into `Bytes`.
///
/// Any errors produced by the body stream are returned immediately.
///
/// # Examples
/// ```
/// use actix_http::body::{self, to_bytes};
/// use bytes::Bytes;
///
/// # async fn test_to_bytes() {
/// let body = body::None::new();
/// let bytes = to_bytes(body).await.unwrap();
/// assert!(bytes.is_empty());
///
/// let body = Bytes::from_static(b"123");
/// let bytes = to_bytes(body).await.unwrap();
/// assert_eq!(bytes, b"123"[..]);
/// # }
/// ```
pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
let cap = match body.size() {
BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()),
BodySize::Sized(size) => size as usize,
// good enough first guess for chunk size
BodySize::Stream => 32_768,
};
let mut buf = BytesMut::with_capacity(cap);
pin!(body);
poll_fn(|cx| loop {
let body = body.as_mut();
match ready!(body.poll_next(cx)) {
Some(Ok(bytes)) => buf.extend_from_slice(&*bytes),
None => return Poll::Ready(Ok(())),
Some(Err(err)) => return Poll::Ready(Err(err)),
}
})
.await?;
Ok(buf.freeze())
}
#[cfg(test)]
mod test {
use futures_util::{stream, StreamExt as _};
use super::*;
use crate::{body::BodyStream, Error};
#[actix_rt::test]
async fn test_to_bytes() {
let bytes = to_bytes(()).await.unwrap();
assert!(bytes.is_empty());
let body = Bytes::from_static(b"123");
let bytes = to_bytes(body).await.unwrap();
assert_eq!(bytes, b"123"[..]);
let stream =
stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
.map(Ok::<_, Error>);
let body = BodyStream::new(stream);
let bytes = to_bytes(body).await.unwrap();
assert_eq!(bytes, b"123abc"[..]);
}
}

View File

@ -1,10 +1,10 @@
use std::{error::Error as StdError, fmt, marker::PhantomData, net, rc::Rc};
use std::{fmt, marker::PhantomData, net, rc::Rc};
use actix_codec::Framed;
use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use crate::{
body::{AnyBody, MessageBody},
body::{BoxBody, MessageBody},
config::{KeepAlive, ServiceConfig},
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
h2::H2Service,
@ -31,7 +31,7 @@ pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
<S::Service as Service<Request>>::Future: 'static,
{
@ -54,11 +54,11 @@ where
impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
<S::Service as Service<Request>>::Future: 'static,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U::Error: fmt::Display,
@ -120,7 +120,7 @@ where
where
F: IntoServiceFactory<X1, Request>,
X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Response<AnyBody>>,
X1::Error: Into<Response<BoxBody>>,
X1::InitError: fmt::Debug,
{
HttpServiceBuilder {
@ -178,7 +178,7 @@ where
where
B: MessageBody,
F: IntoServiceFactory<S, Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
{
@ -200,12 +200,11 @@ where
pub fn h2<F, B>(self, service: F) -> H2Service<T, S, B>
where
F: IntoServiceFactory<S, Request>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
{
let cfg = ServiceConfig::new(
self.keep_alive,
@ -223,12 +222,11 @@ where
pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
where
F: IntoServiceFactory<S, Request>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
{
let cfg = ServiceConfig::new(
self.keep_alive,

View File

@ -12,7 +12,7 @@ use actix_rt::task::{spawn_blocking, JoinHandle};
use bytes::Bytes;
use derive_more::Display;
use futures_core::ready;
use pin_project::pin_project;
use pin_project_lite::pin_project;
#[cfg(feature = "compress-brotli")]
use brotli2::write::BrotliEncoder;
@ -23,8 +23,10 @@ use flate2::write::{GzEncoder, ZlibEncoder};
#[cfg(feature = "compress-zstd")]
use zstd::stream::write::Encoder as ZstdEncoder;
use super::Writer;
use crate::{
body::{AnyBody, BodySize, MessageBody},
body::{BodySize, MessageBody},
error::BlockingError,
http::{
header::{ContentEncoding, CONTENT_ENCODING},
HeaderValue, StatusCode,
@ -32,84 +34,92 @@ use crate::{
ResponseHead,
};
use super::Writer;
use crate::error::BlockingError;
const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024;
#[pin_project]
pub struct Encoder<B> {
eof: bool,
#[pin]
body: EncoderBody<B>,
encoder: Option<ContentEncoder>,
fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
pin_project! {
pub struct Encoder<B> {
#[pin]
body: EncoderBody<B>,
encoder: Option<ContentEncoder>,
fut: Option<JoinHandle<Result<ContentEncoder, io::Error>>>,
eof: bool,
}
}
impl<B: MessageBody> Encoder<B> {
fn none() -> Self {
Encoder {
body: EncoderBody::None,
encoder: None,
fut: None,
eof: true,
}
}
pub fn response(
encoding: ContentEncoding,
head: &mut ResponseHead,
body: AnyBody<B>,
) -> AnyBody<Encoder<B>> {
body: B,
) -> Self {
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|| head.status == StatusCode::NO_CONTENT
|| encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto);
let body = match body {
AnyBody::None => return AnyBody::None,
AnyBody::Bytes(buf) => {
if can_encode {
EncoderBody::Bytes(buf)
} else {
return AnyBody::Bytes(buf);
}
}
AnyBody::Body(body) => EncoderBody::Stream(body),
};
match body.size() {
// no need to compress an empty body
BodySize::None => return Self::none(),
// we cannot assume that Sized is not a stream
BodySize::Sized(_) | BodySize::Stream => {}
}
// TODO potentially some optimisation for single-chunk responses here by trying to read the
// payload eagerly, stopping after 2 polls if the first is a chunk and the second is None
if can_encode {
// Modify response body only if encoder is not None
// Modify response body only if encoder is set
if let Some(enc) = ContentEncoder::encoder(encoding) {
update_head(encoding, head);
head.no_chunking(false);
return AnyBody::Body(Encoder {
body,
eof: false,
fut: None,
return Encoder {
body: EncoderBody::Stream { body },
encoder: Some(enc),
});
fut: None,
eof: false,
};
}
}
AnyBody::Body(Encoder {
body,
eof: false,
fut: None,
Encoder {
body: EncoderBody::Stream { body },
encoder: None,
})
fut: None,
eof: false,
}
}
}
#[pin_project(project = EncoderBodyProj)]
enum EncoderBody<B> {
Bytes(Bytes),
Stream(#[pin] B),
pin_project! {
#[project = EncoderBodyProj]
enum EncoderBody<B> {
None,
Stream { #[pin] body: B },
}
}
impl<B> MessageBody for EncoderBody<B>
where
B: MessageBody,
{
type Error = EncoderError<B::Error>;
type Error = EncoderError;
fn size(&self) -> BodySize {
match self {
EncoderBody::Bytes(ref b) => b.size(),
EncoderBody::Stream(ref b) => b.size(),
EncoderBody::None => BodySize::None,
EncoderBody::Stream { body } => body.size(),
}
}
@ -118,14 +128,11 @@ where
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match self.project() {
EncoderBodyProj::Bytes(b) => {
if b.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(std::mem::take(b))))
}
}
EncoderBodyProj::Stream(b) => b.poll_next(cx).map_err(EncoderError::Body),
EncoderBodyProj::None => Poll::Ready(None),
EncoderBodyProj::Stream { body } => body
.poll_next(cx)
.map_err(|err| EncoderError::Body(err.into())),
}
}
}
@ -134,7 +141,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() {
@ -197,6 +204,7 @@ where
None => {
if let Some(encoder) = this.encoder.take() {
let chunk = encoder.finish().map_err(EncoderError::Io)?;
if chunk.is_empty() {
return Poll::Ready(None);
} else {
@ -222,12 +230,15 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
enum ContentEncoder {
#[cfg(feature = "compress-gzip")]
Deflate(ZlibEncoder<Writer>),
#[cfg(feature = "compress-gzip")]
Gzip(GzEncoder<Writer>),
#[cfg(feature = "compress-brotli")]
Br(BrotliEncoder<Writer>),
// We need explicit 'static lifetime here because ZstdEncoder need lifetime
// argument, and we use `spawn_blocking` in `Encoder::poll_next` that require `FnOnce() -> R + Send + 'static`
// Wwe need explicit 'static lifetime here because ZstdEncoder needs a lifetime argument and we
// use `spawn_blocking` in `Encoder::poll_next` that requires `FnOnce() -> R + Send + 'static`.
#[cfg(feature = "compress-zstd")]
Zstd(ZstdEncoder<'static, Writer>),
}
@ -240,20 +251,24 @@ impl ContentEncoder {
Writer::new(),
flate2::Compression::fast(),
))),
#[cfg(feature = "compress-gzip")]
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
Writer::new(),
flate2::Compression::fast(),
))),
#[cfg(feature = "compress-brotli")]
ContentEncoding::Br => {
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
}
#[cfg(feature = "compress-zstd")]
ContentEncoding::Zstd => {
let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?;
Some(ContentEncoder::Zstd(encoder))
}
_ => None,
}
}
@ -263,10 +278,13 @@ impl ContentEncoder {
match *self {
#[cfg(feature = "compress-brotli")]
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "compress-gzip")]
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "compress-gzip")]
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
#[cfg(feature = "compress-zstd")]
ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(),
}
@ -279,16 +297,19 @@ impl ContentEncoder {
Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err),
},
#[cfg(feature = "compress-gzip")]
ContentEncoder::Gzip(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err),
},
#[cfg(feature = "compress-gzip")]
ContentEncoder::Deflate(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()),
Err(err) => Err(err),
},
#[cfg(feature = "compress-zstd")]
ContentEncoder::Zstd(encoder) => match encoder.finish() {
Ok(writer) => Ok(writer.buf.freeze()),
@ -307,6 +328,7 @@ impl ContentEncoder {
Err(err)
}
},
#[cfg(feature = "compress-gzip")]
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
@ -315,6 +337,7 @@ impl ContentEncoder {
Err(err)
}
},
#[cfg(feature = "compress-gzip")]
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
@ -323,6 +346,7 @@ impl ContentEncoder {
Err(err)
}
},
#[cfg(feature = "compress-zstd")]
ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
Ok(_) => Ok(()),
@ -337,9 +361,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),
@ -348,18 +372,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)
}
}

View File

@ -10,6 +10,9 @@ mod encoder;
pub use self::decoder::Decoder;
pub use self::encoder::Encoder;
/// Special-purpose writer for streaming (de-)compression.
///
/// Pre-allocates 8KiB of capacity.
pub(self) struct Writer {
buf: BytesMut,
}

View File

@ -5,7 +5,7 @@ use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Err
use derive_more::{Display, Error, From};
use http::{uri::InvalidUri, StatusCode};
use crate::{body::AnyBody, ws, Response};
use crate::{body::BoxBody, ws, Response};
pub use http::Error as HttpError;
@ -66,14 +66,15 @@ impl Error {
}
}
impl<B> From<Error> for Response<AnyBody<B>> {
impl From<Error> for Response<BoxBody> {
fn from(err: Error) -> Self {
// TODO: more appropriate error status codes, usage assessment needed
let status_code = match err.inner.kind {
Kind::Parse => StatusCode::BAD_REQUEST,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
Response::new(status_code).set_body(AnyBody::from(err.to_string()))
Response::new(status_code).set_body(BoxBody::new(err.to_string()))
}
}
@ -132,12 +133,6 @@ impl From<std::convert::Infallible> for Error {
}
}
impl From<ws::ProtocolError> for Error {
fn from(err: ws::ProtocolError) -> Self {
Self::new_ws().with_cause(err)
}
}
impl From<HttpError> for Error {
fn from(err: HttpError) -> Self {
Self::new_http().with_cause(err)
@ -150,6 +145,12 @@ impl From<ws::HandshakeError> for Error {
}
}
impl From<ws::ProtocolError> for Error {
fn from(err: ws::ProtocolError) -> Self {
Self::new_ws().with_cause(err)
}
}
/// A set of errors that can occur during parsing HTTP streams.
#[derive(Debug, Display, Error)]
#[non_exhaustive]
@ -240,7 +241,7 @@ impl From<ParseError> for Error {
}
}
impl From<ParseError> for Response<AnyBody> {
impl From<ParseError> for Response<BoxBody> {
fn from(err: ParseError) -> Self {
Error::from(err).into()
}
@ -337,7 +338,7 @@ pub enum DispatchError {
/// Service error
// FIXME: display and error type
#[display(fmt = "Service Error")]
Service(#[error(not(source))] Response<AnyBody>),
Service(#[error(not(source))] Response<BoxBody>),
/// Body error
// FIXME: display and error type
@ -421,11 +422,11 @@ mod tests {
#[test]
fn test_into_response() {
let resp: Response<AnyBody> = ParseError::Incomplete.into();
let resp: Response<BoxBody> = ParseError::Incomplete.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into();
let resp: Response<AnyBody> = Error::new_http().with_cause(err).into();
let resp: Response<BoxBody> = Error::new_http().with_cause(err).into();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
@ -450,7 +451,7 @@ mod tests {
fn test_error_http_response() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let err = Error::new_io().with_cause(orig);
let resp: Response<AnyBody> = err.into();
let resp: Response<BoxBody> = err.into();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}

View File

@ -1,6 +1,5 @@
use std::{
collections::VecDeque,
error::Error as StdError,
fmt,
future::Future,
io, mem, net,
@ -19,7 +18,7 @@ use log::{error, trace};
use pin_project::pin_project;
use crate::{
body::{AnyBody, BodySize, MessageBody},
body::{BodySize, BoxBody, MessageBody},
config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError},
service::HttpFlow,
@ -51,13 +50,12 @@ bitflags! {
pub struct Dispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -73,13 +71,12 @@ where
enum DispatcherState<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -92,13 +89,12 @@ where
struct InnerDispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -137,13 +133,12 @@ where
X: Service<Request, Response = Request>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
{
None,
ExpectCall(#[pin] X::Future),
ServiceCall(#[pin] S::Future),
SendPayload(#[pin] B),
SendErrorPayload(#[pin] AnyBody),
SendErrorPayload(#[pin] BoxBody),
}
impl<S, B, X> State<S, B, X>
@ -153,7 +148,6 @@ where
X: Service<Request, Response = Request>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
{
fn is_empty(&self) -> bool {
matches!(self, State::None)
@ -171,14 +165,13 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -232,14 +225,13 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -335,7 +327,7 @@ where
fn send_error_response(
mut self: Pin<&mut Self>,
message: Response<()>,
body: AnyBody,
body: BoxBody,
) -> Result<(), DispatchError> {
let size = self.as_mut().send_response_inner(message, &body)?;
let state = match size {
@ -380,7 +372,7 @@ where
// send_response would update InnerDispatcher state to SendPayload or
// None(If response body is empty).
// continue loop to poll it.
self.as_mut().send_error_response(res, AnyBody::empty())?;
self.as_mut().send_error_response(res, BoxBody::new(()))?;
}
// return with upgrade request and poll it exclusively.
@ -400,7 +392,7 @@ where
// send service call error as response
Poll::Ready(Err(err)) => {
let res: Response<AnyBody> = err.into();
let res: Response<BoxBody> = err.into();
let (res, body) = res.replace_body(());
self.as_mut().send_error_response(res, body)?;
}
@ -497,7 +489,7 @@ where
// send expect error as response
Poll::Ready(Err(err)) => {
let res: Response<AnyBody> = err.into();
let res: Response<BoxBody> = err.into();
let (res, body) = res.replace_body(());
self.as_mut().send_error_response(res, body)?;
}
@ -546,7 +538,7 @@ where
// to notify the dispatcher a new state is set and the outer loop
// should be continue.
Poll::Ready(Err(err)) => {
let res: Response<AnyBody> = err.into();
let res: Response<BoxBody> = err.into();
let (res, body) = res.replace_body(());
return self.send_error_response(res, body);
}
@ -566,7 +558,7 @@ where
Poll::Pending => Ok(()),
// see the comment on ExpectCall state branch's Ready(Err(err)).
Poll::Ready(Err(err)) => {
let res: Response<AnyBody> = err.into();
let res: Response<BoxBody> = err.into();
let (res, body) = res.replace_body(());
self.send_error_response(res, body)
}
@ -772,7 +764,7 @@ where
trace!("Slow request timeout");
let _ = self.as_mut().send_error_response(
Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
AnyBody::empty(),
BoxBody::new(()),
);
this = self.project();
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
@ -909,14 +901,13 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
@ -1067,17 +1058,19 @@ mod tests {
}
}
fn ok_service() -> impl Service<Request, Response = Response<AnyBody>, Error = Error>
fn ok_service(
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
{
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
}
fn echo_path_service(
) -> impl Service<Request, Response = Response<AnyBody>, Error = Error> {
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
{
fn_service(|req: Request| {
let path = req.path().as_bytes();
ready(Ok::<_, Error>(
Response::ok().set_body(AnyBody::copy_from_slice(path)),
Response::ok().set_body(Bytes::copy_from_slice(path)),
))
})
}

View File

@ -1,5 +1,4 @@
use std::{
error::Error as StdError,
fmt,
marker::PhantomData,
net,
@ -16,7 +15,7 @@ use actix_utils::future::ready;
use futures_core::future::LocalBoxFuture;
use crate::{
body::{AnyBody, MessageBody},
body::{BoxBody, MessageBody},
config::ServiceConfig,
error::DispatchError,
service::HttpServiceHandler,
@ -38,7 +37,7 @@ pub struct H1Service<T, S, B, X = ExpectHandler, U = UpgradeHandler> {
impl<T, S, B> H1Service<T, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
@ -63,21 +62,20 @@ impl<S, B, X, U> H1Service<TcpStream, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<TcpStream, Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create simple tcp stream service
@ -114,16 +112,15 @@ mod openssl {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -132,7 +129,7 @@ mod openssl {
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create OpenSSL based service.
@ -177,16 +174,15 @@ mod rustls {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -195,7 +191,7 @@ mod rustls {
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls based service.
@ -226,7 +222,7 @@ mod rustls {
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
@ -234,7 +230,7 @@ where
pub fn expect<X1>(self, expect: X1) -> H1Service<T, S, B, X1, U>
where
X1: ServiceFactory<Request, Response = Request>,
X1::Error: Into<Response<AnyBody>>,
X1::Error: Into<Response<BoxBody>>,
X1::InitError: fmt::Debug,
{
H1Service {
@ -277,21 +273,20 @@ where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::Response: Into<Response<B>>,
S::InitError: fmt::Debug,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
type Response = ();
@ -347,17 +342,16 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
{
type Response = ();
type Error = DispatchError;

View File

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

View File

@ -24,7 +24,7 @@ use log::{error, trace};
use pin_project_lite::pin_project;
use crate::{
body::{AnyBody, BodySize, MessageBody},
body::{BodySize, BoxBody, MessageBody},
config::ServiceConfig,
service::HttpFlow,
OnConnectData, Payload, Request, Response, ResponseHead,
@ -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,
@ -92,12 +92,11 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
S::Future: 'static,
S::Response: Into<Response<B>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
{
type Output = Result<(), crate::error::DispatchError>;
@ -132,7 +131,7 @@ where
let res = match fut.await {
Ok(res) => handle_response(res.into(), tx, config).await,
Err(err) => {
let res: Response<AnyBody> = err.into();
let res: Response<BoxBody> = err.into();
handle_response(res, tx, config).await
}
};
@ -207,7 +206,6 @@ async fn handle_response<B>(
) -> Result<(), DispatchError>
where
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
{
let (res, body) = res.replace_body(());

View File

@ -1,5 +1,4 @@
use std::{
error::Error as StdError,
future::Future,
marker::PhantomData,
net,
@ -19,7 +18,7 @@ use futures_core::{future::LocalBoxFuture, ready};
use log::error;
use crate::{
body::{AnyBody, MessageBody},
body::{BoxBody, MessageBody},
config::ServiceConfig,
error::DispatchError,
service::HttpFlow,
@ -39,12 +38,11 @@ pub struct H2Service<T, S, B> {
impl<T, S, B> H2Service<T, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
{
/// Create new `H2Service` instance with config.
pub(crate) fn with_config<F: IntoServiceFactory<S, Request>>(
@ -70,12 +68,11 @@ impl<S, B> H2Service<TcpStream, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
{
/// Create plain TCP based service
pub fn tcp(
@ -114,12 +111,11 @@ mod openssl {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
{
/// Create OpenSSL based service.
pub fn openssl(
@ -162,12 +158,11 @@ mod rustls {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
{
/// Create Rustls based service.
pub fn rustls(
@ -204,12 +199,11 @@ where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
{
type Response = ();
type Error = DispatchError;
@ -244,7 +238,7 @@ where
impl<T, S, B> H2ServiceHandler<T, S, B>
where
S: Service<Request>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
@ -267,11 +261,10 @@ impl<T, S, B> Service<(T, Option<net::SocketAddr>)> for H2ServiceHandler<T, S, B
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
{
type Response = ();
type Error = DispatchError;
@ -320,7 +313,7 @@ pub struct H2ServiceHandlerResponse<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
@ -332,11 +325,10 @@ impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
{
type Output = Result<(), DispatchError>;

View File

@ -46,8 +46,8 @@ pub trait Head: Default + 'static {
#[derive(Debug)]
pub struct RequestHead {
pub uri: Uri,
pub method: Method,
pub uri: Uri,
pub version: Version,
pub headers: HeaderMap,
pub extensions: RefCell<Extensions>,
@ -58,13 +58,13 @@ pub struct RequestHead {
impl Default for RequestHead {
fn default() -> RequestHead {
RequestHead {
uri: Uri::default(),
method: Method::default(),
uri: Uri::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
flags: Flags::empty(),
peer_addr: None,
extensions: RefCell::new(Extensions::new()),
peer_addr: None,
flags: Flags::empty(),
}
}
}
@ -192,6 +192,7 @@ impl RequestHead {
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum RequestHeadType {
Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>),

View File

@ -6,14 +6,15 @@ use std::{
};
use bytes::{Bytes, BytesMut};
use bytestring::ByteString;
use crate::{
body::{AnyBody, MessageBody},
error::Error,
body::{BoxBody, MessageBody},
extensions::Extensions,
header::{self, IntoHeaderValue},
http::{HeaderMap, StatusCode},
message::{BoxedResponseHead, ResponseHead},
ResponseBuilder,
Error, ResponseBuilder,
};
/// An HTTP response.
@ -22,13 +23,13 @@ pub struct Response<B> {
pub(crate) body: B,
}
impl Response<AnyBody> {
impl Response<BoxBody> {
/// Constructs a new response with default body.
#[inline]
pub fn new(status: StatusCode) -> Self {
Response {
head: BoxedResponseHead::new(status),
body: AnyBody::empty(),
body: BoxBody::new(()),
}
}
@ -189,6 +190,14 @@ impl<B> Response<B> {
}
}
#[inline]
pub fn map_into_boxed_body(self) -> Response<BoxBody>
where
B: MessageBody + 'static,
{
self.map_body(|_, body| BoxBody::new(body))
}
/// Returns body, consuming this response.
pub fn into_body(self) -> B {
self.body
@ -223,81 +232,99 @@ impl<B: Default> Default for Response<B> {
}
}
impl<I: Into<Response<AnyBody>>, E: Into<Error>> From<Result<I, E>>
for Response<AnyBody>
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) => err.into().into(),
Err(err) => Response::from(err.into()),
}
}
}
impl From<ResponseBuilder> for Response<AnyBody> {
impl From<ResponseBuilder> for Response<BoxBody> {
fn from(mut builder: ResponseBuilder) -> Self {
builder.finish()
builder.finish().map_into_boxed_body()
}
}
impl From<std::convert::Infallible> for Response<AnyBody> {
impl From<std::convert::Infallible> for Response<BoxBody> {
fn from(val: std::convert::Infallible) -> Self {
match val {}
}
}
impl From<&'static str> for Response<AnyBody> {
impl From<&'static str> for Response<&'static str> {
fn from(val: &'static str) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8)
.body(val)
let mut res = Response::with_body(StatusCode::OK, val);
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
res.headers_mut().insert(header::CONTENT_TYPE, mime);
res
}
}
impl From<&'static [u8]> for Response<AnyBody> {
impl From<&'static [u8]> for Response<&'static [u8]> {
fn from(val: &'static [u8]) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM)
.body(val)
let mut res = Response::with_body(StatusCode::OK, val);
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
res.headers_mut().insert(header::CONTENT_TYPE, mime);
res
}
}
impl From<String> for Response<AnyBody> {
impl From<String> for Response<String> {
fn from(val: String) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8)
.body(val)
let mut res = Response::with_body(StatusCode::OK, val);
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
res.headers_mut().insert(header::CONTENT_TYPE, mime);
res
}
}
impl<'a> From<&'a String> for Response<AnyBody> {
fn from(val: &'a String) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::TEXT_PLAIN_UTF_8)
.body(val)
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<AnyBody> {
impl From<Bytes> for Response<Bytes> {
fn from(val: Bytes) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM)
.body(val)
let mut res = Response::with_body(StatusCode::OK, val);
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
res.headers_mut().insert(header::CONTENT_TYPE, mime);
res
}
}
impl From<BytesMut> for Response<AnyBody> {
impl From<BytesMut> for Response<BytesMut> {
fn from(val: BytesMut) -> Self {
Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_OCTET_STREAM)
.body(val)
let mut res = Response::with_body(StatusCode::OK, val);
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
res.headers_mut().insert(header::CONTENT_TYPE, mime);
res
}
}
impl From<ByteString> for Response<ByteString> {
fn from(val: ByteString) -> Self {
let mut res = Response::with_body(StatusCode::OK, val);
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
res.headers_mut().insert(header::CONTENT_TYPE, mime);
res
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
use crate::{
body::to_bytes,
http::header::{HeaderValue, CONTENT_TYPE, COOKIE},
};
#[test]
fn test_debug() {
@ -309,73 +336,73 @@ mod tests {
assert!(dbg.contains("Response"));
}
#[test]
fn test_into_response() {
let resp: Response<AnyBody> = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
#[actix_rt::test]
async fn test_into_response() {
let res = Response::from("test");
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
let resp: Response<AnyBody> = b"test".as_ref().into();
assert_eq!(resp.status(), StatusCode::OK);
let res = Response::from(b"test".as_ref());
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
let resp: Response<AnyBody> = "test".to_owned().into();
assert_eq!(resp.status(), StatusCode::OK);
let res = Response::from("test".to_owned());
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
let resp: Response<AnyBody> = (&"test".to_owned()).into();
assert_eq!(resp.status(), StatusCode::OK);
let res = Response::from("test".to_owned());
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain; charset=utf-8")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
let b = Bytes::from_static(b"test");
let resp: Response<AnyBody> = b.into();
assert_eq!(resp.status(), StatusCode::OK);
let res = Response::from(b);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
let b = Bytes::from_static(b"test");
let resp: Response<AnyBody> = b.into();
assert_eq!(resp.status(), StatusCode::OK);
let res = Response::from(b);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
let b = BytesMut::from("test");
let resp: Response<AnyBody> = b.into();
assert_eq!(resp.status(), StatusCode::OK);
let res = Response::from(b);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/octet-stream")
);
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body().get_ref(), b"test");
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(to_bytes(res.into_body()).await.unwrap(), &b"test"[..]);
}
}

View File

@ -2,19 +2,11 @@
use std::{
cell::{Ref, RefMut},
error::Error as StdError,
fmt,
future::Future,
pin::Pin,
str,
task::{Context, Poll},
fmt, str,
};
use bytes::Bytes;
use futures_core::Stream;
use crate::{
body::{AnyBody, BodyStream},
body::{EitherBody, MessageBody},
error::{Error, HttpError},
header::{self, IntoHeaderPair, IntoHeaderValue},
message::{BoxedResponseHead, ConnectionType, ResponseHead},
@ -235,10 +227,14 @@ impl ResponseBuilder {
/// Generate response with a wrapped body.
///
/// This `ResponseBuilder` will be left in a useless state.
#[inline]
pub fn body<B: Into<AnyBody>>(&mut self, body: B) -> Response<AnyBody> {
self.message_body(body.into())
.unwrap_or_else(Response::from)
pub fn body<B>(&mut self, body: B) -> Response<EitherBody<B>>
where
B: MessageBody + 'static,
{
match self.message_body(body) {
Ok(res) => res.map_body(|_, body| EitherBody::left(body)),
Err(err) => Response::from(err).map_body(|_, body| EitherBody::right(body)),
}
}
/// Generate response with a body.
@ -253,24 +249,12 @@ impl ResponseBuilder {
Ok(Response { head, body })
}
/// Generate response with a streaming body.
///
/// This `ResponseBuilder` will be left in a useless state.
#[inline]
pub fn streaming<S, E>(&mut self, stream: S) -> Response<AnyBody>
where
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<Box<dyn StdError>> + 'static,
{
self.body(AnyBody::new_boxed(BodyStream::new(stream)))
}
/// Generate response with an empty body.
///
/// This `ResponseBuilder` will be left in a useless state.
#[inline]
pub fn finish(&mut self) -> Response<AnyBody> {
self.body(AnyBody::empty())
pub fn finish(&mut self) -> Response<EitherBody<()>> {
self.body(())
}
/// Create an owned `ResponseBuilder`, leaving the original in a useless state.
@ -327,14 +311,6 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
}
}
impl Future for ResponseBuilder {
type Output = Result<Response<AnyBody>, Error>;
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(Ok(self.finish()))
}
}
impl fmt::Debug for ResponseBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let head = self.head.as_ref().unwrap();
@ -356,8 +332,9 @@ impl fmt::Debug for ResponseBuilder {
#[cfg(test)]
mod tests {
use bytes::Bytes;
use super::*;
use crate::body::AnyBody;
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
#[test]
@ -383,20 +360,28 @@ mod tests {
#[test]
fn test_force_close() {
let resp = Response::build(StatusCode::OK).force_close().finish();
assert!(!resp.keep_alive())
assert!(!resp.keep_alive());
}
#[test]
fn test_content_type() {
let resp = Response::build(StatusCode::OK)
.content_type("text/plain")
.body(AnyBody::empty());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
.body(Bytes::new());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain");
let resp = Response::build(StatusCode::OK)
.content_type(mime::APPLICATION_JAVASCRIPT_UTF_8)
.body(Bytes::new());
assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(),
"application/javascript; charset=utf-8"
);
}
#[test]
fn test_into_builder() {
let mut resp: Response<AnyBody> = "test".into();
let mut resp: Response<_> = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
resp.headers_mut().insert(

View File

@ -1,5 +1,4 @@
use std::{
error::Error as StdError,
fmt,
future::Future,
marker::PhantomData,
@ -15,10 +14,10 @@ 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::{AnyBody, MessageBody},
body::{BoxBody, MessageBody},
builder::HttpServiceBuilder,
config::{KeepAlive, ServiceConfig},
error::DispatchError,
@ -38,7 +37,7 @@ pub struct HttpService<T, S, B, X = h1::ExpectHandler, U = h1::UpgradeHandler> {
impl<T, S, B> HttpService<T, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
@ -53,12 +52,11 @@ where
impl<T, S, B> HttpService<T, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
{
/// Create new `HttpService` instance.
pub fn new<F: IntoServiceFactory<S, Request>>(service: F) -> Self {
@ -93,7 +91,7 @@ where
impl<T, S, B, X, U> HttpService<T, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
@ -107,7 +105,7 @@ where
pub fn expect<X1>(self, expect: X1) -> HttpService<T, S, B, X1, U>
where
X1: ServiceFactory<Request, Config = (), Response = Request>,
X1::Error: Into<Response<AnyBody>>,
X1::Error: Into<Response<BoxBody>>,
X1::InitError: fmt::Debug,
{
HttpService {
@ -151,17 +149,16 @@ impl<S, B, X, U> HttpService<TcpStream, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -170,7 +167,7 @@ where
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create simple tcp stream service
@ -208,17 +205,16 @@ mod openssl {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -227,7 +223,7 @@ mod openssl {
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create OpenSSL based service.
@ -281,17 +277,16 @@ mod rustls {
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
@ -300,7 +295,7 @@ mod rustls {
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls based service.
@ -348,22 +343,21 @@ where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
type Response = ();
@ -426,11 +420,11 @@ where
impl<T, S, B, X, U> HttpServiceHandler<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<AnyBody>>,
S::Error: Into<Response<BoxBody>>,
X: Service<Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, h1::Codec>)>,
U::Error: Into<Response<AnyBody>>,
U::Error: Into<Response<BoxBody>>,
{
pub(super) fn new(
cfg: ServiceConfig,
@ -450,7 +444,7 @@ where
pub(super) fn _poll_ready(
&self,
cx: &mut Context<'_>,
) -> Poll<Result<(), Response<AnyBody>>> {
) -> Poll<Result<(), Response<BoxBody>>> {
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
@ -486,18 +480,17 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display + Into<Response<AnyBody>>,
U::Error: fmt::Display + Into<Response<BoxBody>>,
{
type Response = ();
type Error = DispatchError;
@ -519,23 +512,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 +540,65 @@ 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<AnyBody>>,
S: Service<Request>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>>,
B: MessageBody,
B::Error: Into<Box<dyn StdError>>,
B: MessageBody,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
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<AnyBody>> + '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,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
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>
@ -602,15 +606,14 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S: Service<Request>,
S::Error: Into<Response<AnyBody>> + 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Future: 'static,
S::Response: Into<Response<B>> + 'static,
B: MessageBody + 'static,
B::Error: Into<Box<dyn StdError>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<AnyBody>>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, h1::Codec>), Response = ()>,
U::Error: fmt::Display,
@ -619,23 +622,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) => {

View File

@ -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>
@ -72,7 +76,7 @@ mod inner {
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed};
use crate::{body::AnyBody, Response};
use crate::{body::BoxBody, Response};
/// Framed transport errors
pub enum DispatcherError<E, U, I>
@ -136,7 +140,7 @@ mod inner {
}
}
impl<E, U, I> From<DispatcherError<E, U, I>> for Response<AnyBody>
impl<E, U, I> From<DispatcherError<E, U, I>> for Response<BoxBody>
where
E: fmt::Debug + fmt::Display,
U: Encoder<I> + Decoder,
@ -144,7 +148,7 @@ mod inner {
<U as Decoder>::Error: fmt::Debug,
{
fn from(err: DispatcherError<E, U, I>) -> Self {
Response::internal_server_error().set_body(AnyBody::from(err.to_string()))
Response::internal_server_error().set_body(BoxBody::new(err.to_string()))
}
}

View File

@ -8,9 +8,9 @@ use std::io;
use derive_more::{Display, Error, From};
use http::{header, Method, StatusCode};
use crate::body::BoxBody;
use crate::{
body::AnyBody, header::HeaderValue, message::RequestHead, response::Response,
ResponseBuilder,
header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder,
};
mod codec;
@ -69,7 +69,7 @@ pub enum ProtocolError {
}
/// WebSocket handshake errors
#[derive(Debug, PartialEq, Display, Error)]
#[derive(Debug, Clone, Copy, PartialEq, Display, Error)]
pub enum HandshakeError {
/// Only get method is allowed.
#[display(fmt = "Method not allowed.")]
@ -96,8 +96,8 @@ pub enum HandshakeError {
BadWebsocketKey,
}
impl From<&HandshakeError> for Response<AnyBody> {
fn from(err: &HandshakeError) -> Self {
impl From<HandshakeError> for Response<BoxBody> {
fn from(err: HandshakeError) -> Self {
match err {
HandshakeError::GetMethodRequired => {
let mut res = Response::new(StatusCode::METHOD_NOT_ALLOWED);
@ -139,9 +139,9 @@ impl From<&HandshakeError> for Response<AnyBody> {
}
}
impl From<HandshakeError> for Response<AnyBody> {
fn from(err: HandshakeError) -> Self {
(&err).into()
impl From<&HandshakeError> for Response<BoxBody> {
fn from(err: &HandshakeError) -> Self {
(*err).into()
}
}
@ -220,9 +220,10 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
#[cfg(test)]
mod tests {
use crate::{header, Method};
use super::*;
use crate::{body::AnyBody, test::TestRequest};
use http::{header, Method};
use crate::test::TestRequest;
#[test]
fn test_handshake() {
@ -336,17 +337,17 @@ mod tests {
#[test]
fn test_ws_error_http_response() {
let resp: Response<AnyBody> = HandshakeError::GetMethodRequired.into();
let resp: Response<BoxBody> = HandshakeError::GetMethodRequired.into();
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let resp: Response<AnyBody> = HandshakeError::NoWebsocketUpgrade.into();
let resp: Response<BoxBody> = HandshakeError::NoWebsocketUpgrade.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: Response<AnyBody> = HandshakeError::NoConnectionUpgrade.into();
let resp: Response<BoxBody> = HandshakeError::NoConnectionUpgrade.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: Response<AnyBody> = HandshakeError::NoVersionHeader.into();
let resp: Response<BoxBody> = HandshakeError::NoVersionHeader.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: Response<AnyBody> = HandshakeError::UnsupportedVersion.into();
let resp: Response<BoxBody> = HandshakeError::UnsupportedVersion.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
let resp: Response<AnyBody> = HandshakeError::BadWebsocketKey.into();
let resp: Response<BoxBody> = HandshakeError::BadWebsocketKey.into();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
}