diff --git a/CHANGES.md b/CHANGES.md
index c754d4dd6..2dc45c3ed 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -4,6 +4,8 @@
 ### Added
 * Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
 * `AcceptEncoding` typed header. [#2482]
+* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
+* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
 
 ### Changed
 * Rename `Accept::{mime_precedence => ranked}`. [#2480]
@@ -11,8 +13,10 @@
 
 ### Fixed
 * Accept wildcard `*` items in `AcceptLanguage`. [#2480]
+* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468]
 * Typed headers containing lists that require one or more items now enforce this minimum. [#2482]
 
+[#2468]: https://github.com/actix/actix-web/pull/2468
 [#2480]: https://github.com/actix/actix-web/pull/2480
 [#2482]: https://github.com/actix/actix-web/pull/2482
 
diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs
index fbb46e417..68221ccc3 100644
--- a/actix-files/src/chunked.rs
+++ b/actix-files/src/chunked.rs
@@ -6,8 +6,7 @@ use std::{
     task::{Context, Poll},
 };
 
-use actix_web::error::Error;
-use bytes::Bytes;
+use actix_web::{error::Error, web::Bytes};
 use futures_core::{ready, Stream};
 use pin_project_lite::pin_project;
 
diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs
index 547048bbd..89775c6b3 100644
--- a/actix-files/src/named.rs
+++ b/actix-files/src/named.rs
@@ -10,12 +10,12 @@ use std::{
 #[cfg(unix)]
 use std::os::unix::fs::MetadataExt;
 
-use actix_http::body::AnyBody;
 use actix_service::{Service, ServiceFactory};
 use actix_web::{
+    body::{self, BoxBody, SizedStream},
     dev::{
         AppService, BodyEncoding, HttpServiceFactory, ResourceDef, ServiceRequest,
-        ServiceResponse, SizedStream,
+        ServiceResponse,
     },
     http::{
         header::{
@@ -113,6 +113,8 @@ pub(crate) use std::fs::File;
 #[cfg(feature = "experimental-io-uring")]
 pub(crate) use tokio_uring::fs::File;
 
+use super::chunked;
+
 impl NamedFile {
     /// Creates an instance from a previously opened file.
     ///
@@ -394,7 +396,7 @@ impl NamedFile {
     }
 
     /// Creates an `HttpResponse` with file as a streaming body.
-    pub fn into_response(self, req: &HttpRequest) -> HttpResponse {
+    pub fn into_response(self, req: &HttpRequest) -> HttpResponse<BoxBody> {
         if self.status_code != StatusCode::OK {
             let mut res = HttpResponse::build(self.status_code);
 
@@ -416,7 +418,7 @@ impl NamedFile {
                 res.encoding(current_encoding);
             }
 
-            let reader = super::chunked::new_chunked_read(self.md.len(), 0, self.file);
+            let reader = chunked::new_chunked_read(self.md.len(), 0, self.file);
 
             return res.streaming(reader);
         }
@@ -527,10 +529,13 @@ impl NamedFile {
         if precondition_failed {
             return resp.status(StatusCode::PRECONDITION_FAILED).finish();
         } else if not_modified {
-            return resp.status(StatusCode::NOT_MODIFIED).body(AnyBody::None);
+            return resp
+                .status(StatusCode::NOT_MODIFIED)
+                .body(body::None::new())
+                .map_into_boxed_body();
         }
 
-        let reader = super::chunked::new_chunked_read(length, offset, self.file);
+        let reader = chunked::new_chunked_read(length, offset, self.file);
 
         if offset != 0 || length != self.md.len() {
             resp.status(StatusCode::PARTIAL_CONTENT);
@@ -595,7 +600,9 @@ impl DerefMut for NamedFile {
 }
 
 impl Responder for NamedFile {
-    fn respond_to(self, req: &HttpRequest) -> HttpResponse {
+    type Body = BoxBody;
+
+    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
         self.into_response(req)
     }
 }
diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md
index 1eaccfb2e..23c15296a 100644
--- a/actix-http/CHANGES.md
+++ b/actix-http/CHANGES.md
@@ -3,9 +3,31 @@
 ## Unreleased - 2021-xx-xx
 ### Added
 * Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
-* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483] 
+* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
+* `Response::map_into_boxed_body`. [#2468]
+* `body::EitherBody` enum. [#2468]
+* `body::None` struct. [#2468]
+* Impl `MessageBody` for `bytestring::ByteString`. [#2468]
+* `impl Clone for ws::HandshakeError`. [#2468]
+
+### Changed
+* Rename `body::BoxBody::{from_body => new}`. [#2468]
+* Body type for `Responses` returned from `Response::{new, ok, etc...}` is now `BoxBody`. [#2468]
+* The `Error` associated type on `MessageBody` type now requires `impl Error` (or similar). [#2468]
+* Error types using in service builders now require `Into<Response<BoxBody>>`. [#2468]
+* `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
+* `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
+* `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
+
+### Removed
+* `ResponseBuilder::streaming`. [#2468]
+* `impl Future` for `ResponseBuilder`. [#2468]
+* Remove unnecessary `MessageBody` bound on types passed to `body::AnyBody::new`. [#2468]
+* Move `body::AnyBody` to `awc`. Replaced with `EitherBody` and `BoxBody`. [#2468]
 
 [#2483]: https://github.com/actix/actix-web/pull/2483
+[#2468]: https://github.com/actix/actix-web/pull/2468
+
 
 ## 3.0.0-beta.14 - 2021-11-30
 ### Changed
diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs
index 6e5ddec7c..6092c01ce 100644
--- a/actix-http/examples/echo2.rs
+++ b/actix-http/examples/echo2.rs
@@ -1,12 +1,14 @@
 use std::io;
 
-use actix_http::{body::AnyBody, http::HeaderValue, http::StatusCode};
-use actix_http::{Error, HttpService, Request, Response};
+use actix_http::{
+    body::MessageBody, http::HeaderValue, http::StatusCode, Error, HttpService, Request,
+    Response,
+};
 use actix_server::Server;
 use bytes::BytesMut;
 use futures_util::StreamExt as _;
 
-async fn handle_request(mut req: Request) -> Result<Response<AnyBody>, Error> {
+async fn handle_request(mut req: Request) -> Result<Response<impl MessageBody>, Error> {
     let mut body = BytesMut::new();
     while let Some(item) = req.payload().next().await {
         body.extend_from_slice(&item?)
diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs
deleted file mode 100644
index e8861024b..000000000
--- a/actix-http/src/body/body.rs
+++ /dev/null
@@ -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]),
-        );
-    }
-}
diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs
index 31de9b48f..1da7a848a 100644
--- a/actix-http/src/body/body_stream.rs
+++ b/actix-http/src/body/body_stream.rs
@@ -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();
diff --git a/actix-http/src/body/boxed.rs b/actix-http/src/body/boxed.rs
new file mode 100644
index 000000000..9442bd1df
--- /dev/null
+++ b/actix-http/src/body/boxed.rs
@@ -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]),
+        );
+    }
+}
diff --git a/actix-http/src/body/either.rs b/actix-http/src/body/either.rs
new file mode 100644
index 000000000..6169ee627
--- /dev/null
+++ b/actix-http/src/body/either.rs
@@ -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(());
+    }
+}
diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs
index 62a7e9b1c..053b6f286 100644
--- a/actix-http/src/body/message_body.rs
+++ b/actix-http/src/body/message_body.rs
@@ -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());
+    }
+}
diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs
index df6c6b08a..af7c4626f 100644
--- a/actix-http/src/body/mod.rs
+++ b/actix-http/src/body/mod.rs
@@ -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;
diff --git a/actix-http/src/body/none.rs b/actix-http/src/body/none.rs
new file mode 100644
index 000000000..0fc7c8c9f
--- /dev/null
+++ b/actix-http/src/body/none.rs
@@ -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)
+    }
+}
diff --git a/actix-http/src/body/size.rs b/actix-http/src/body/size.rs
index e238eadac..d64af9d44 100644
--- a/actix-http/src/body/size.rs
+++ b/actix-http/src/body/size.rs
@@ -18,7 +18,7 @@ pub enum BodySize {
 }
 
 impl BodySize {
-    /// Returns true if size hint indicates no or empty body.
+    /// Returns true if size hint indicates omitted or empty body.
     ///
     /// Streams will return false because it cannot be known without reading the stream.
     ///
diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs
index b92de44cc..c8606897d 100644
--- a/actix-http/src/body/sized_stream.rs
+++ b/actix-http/src/body/sized_stream.rs
@@ -32,6 +32,8 @@ where
     }
 }
 
+// TODO: from_infallible method
+
 impl<S, E> MessageBody for SizedStream<S>
 where
     S: Stream<Item = Result<Bytes, E>>,
diff --git a/actix-http/src/body/utils.rs b/actix-http/src/body/utils.rs
new file mode 100644
index 000000000..a421ffd76
--- /dev/null
+++ b/actix-http/src/body/utils.rs
@@ -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"[..]);
+    }
+}
diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs
index 4e68dc920..ca821f1d9 100644
--- a/actix-http/src/builder.rs
+++ b/actix-http/src/builder.rs
@@ -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,
diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs
index 62100ff1d..49e5663dc 100644
--- a/actix-http/src/encoding/encoder.rs
+++ b/actix-http/src/encoding/encoder.rs
@@ -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)
     }
 }
diff --git a/actix-http/src/encoding/mod.rs b/actix-http/src/encoding/mod.rs
index cb271c638..d51dd66c0 100644
--- a/actix-http/src/encoding/mod.rs
+++ b/actix-http/src/encoding/mod.rs
@@ -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,
 }
diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs
index 970c0c564..231e90e57 100644
--- a/actix-http/src/error.rs
+++ b/actix-http/src/error.rs
@@ -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);
     }
 
diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs
index 163d84f5b..6695d1bf3 100644
--- a/actix-http/src/h1/dispatcher.rs
+++ b/actix-http/src/h1/dispatcher.rs
@@ -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)),
             ))
         })
     }
diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs
index 8a50417d2..70e83901c 100644
--- a/actix-http/src/h1/service.rs
+++ b/actix-http/src/h1/service.rs
@@ -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;
diff --git a/actix-http/src/h1/utils.rs b/actix-http/src/h1/utils.rs
index 2547f4494..905585a32 100644
--- a/actix-http/src/h1/utils.rs
+++ b/actix-http/src/h1/utils.rs
@@ -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>
diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs
index 607997eb7..6d2f4579a 100644
--- a/actix-http/src/h2/dispatcher.rs
+++ b/actix-http/src/h2/dispatcher.rs
@@ -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(());
 
diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs
index 0ad17ec0a..8a9061b94 100644
--- a/actix-http/src/h2/service.rs
+++ b/actix-http/src/h2/service.rs
@@ -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>;
 
diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs
index e0bed0631..c8e1ce6db 100644
--- a/actix-http/src/message.rs
+++ b/actix-http/src/message.rs
@@ -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>),
diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs
index 47f1c37e2..ad41094ae 100644
--- a/actix-http/src/response.rs
+++ b/actix-http/src/response.rs
@@ -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"[..]);
     }
 }
diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs
index c5fcb625c..0537112d5 100644
--- a/actix-http/src/response_builder.rs
+++ b/actix-http/src/response_builder.rs
@@ -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(
diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs
index fb0cccb38..7af34ba05 100644
--- a/actix-http/src/service.rs
+++ b/actix-http/src/service.rs
@@ -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) => {
diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs
index f49cbe5d4..a3f766e9c 100644
--- a/actix-http/src/ws/dispatcher.rs
+++ b/actix-http/src/ws/dispatcher.rs
@@ -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()))
         }
     }
 
diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs
index 70e0e62a2..cb1aa6730 100644
--- a/actix-http/src/ws/mod.rs
+++ b/actix-http/src/ws/mod.rs
@@ -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);
     }
 }
diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs
index 414266d81..4c923873f 100644
--- a/actix-http/tests/test_client.rs
+++ b/actix-http/tests/test_client.rs
@@ -1,7 +1,7 @@
 use std::convert::Infallible;
 
 use actix_http::{
-    body::AnyBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response,
+    body::BoxBody, http, http::StatusCode, HttpMessage, HttpService, Request, Response,
 };
 use actix_http_test::test_server;
 use actix_service::ServiceFactoryExt;
@@ -99,7 +99,7 @@ async fn test_with_query_parameter() {
 #[display(fmt = "expect failed")]
 struct ExpectFailed;
 
-impl From<ExpectFailed> for Response<AnyBody> {
+impl From<ExpectFailed> for Response<BoxBody> {
     fn from(_: ExpectFailed) -> Self {
         Response::new(StatusCode::EXPECTATION_FAILED)
     }
diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs
index e7dd78171..86ee17c74 100644
--- a/actix-http/tests/test_openssl.rs
+++ b/actix-http/tests/test_openssl.rs
@@ -5,7 +5,7 @@ extern crate tls_openssl as openssl;
 use std::{convert::Infallible, io};
 
 use actix_http::{
-    body::{AnyBody, SizedStream},
+    body::{BodyStream, BoxBody, SizedStream},
     error::PayloadError,
     http::{
         header::{self, HeaderValue},
@@ -348,7 +348,7 @@ async fn test_h2_body_chunked_explicit() {
                 ok::<_, Infallible>(
                     Response::build(StatusCode::OK)
                         .insert_header((header::TRANSFER_ENCODING, "chunked"))
-                        .streaming(body),
+                        .body(BodyStream::new(body)),
                 )
             })
             .openssl(tls_config())
@@ -399,9 +399,11 @@ async fn test_h2_response_http_error_handling() {
 #[display(fmt = "error")]
 struct BadRequest;
 
-impl From<BadRequest> for Response<AnyBody> {
+impl From<BadRequest> for Response<BoxBody> {
     fn from(err: BadRequest) -> Self {
-        Response::build(StatusCode::BAD_REQUEST).body(err.to_string())
+        Response::build(StatusCode::BAD_REQUEST)
+            .body(err.to_string())
+            .map_into_boxed_body()
     }
 }
 
@@ -409,7 +411,7 @@ impl From<BadRequest> for Response<AnyBody> {
 async fn test_h2_service_error() {
     let mut srv = test_server(move || {
         HttpService::build()
-            .h2(|_| err::<Response<AnyBody>, _>(BadRequest))
+            .h2(|_| err::<Response<BoxBody>, _>(BadRequest))
             .openssl(tls_config())
             .map_err(|_| ())
     })
diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs
index b5289bf7c..873752779 100644
--- a/actix-http/tests/test_rustls.rs
+++ b/actix-http/tests/test_rustls.rs
@@ -10,7 +10,7 @@ use std::{
 };
 
 use actix_http::{
-    body::{AnyBody, SizedStream},
+    body::{BodyStream, BoxBody, SizedStream},
     error::PayloadError,
     http::{
         header::{self, HeaderName, HeaderValue},
@@ -416,7 +416,7 @@ async fn test_h2_body_chunked_explicit() {
                 ok::<_, Infallible>(
                     Response::build(StatusCode::OK)
                         .insert_header((header::TRANSFER_ENCODING, "chunked"))
-                        .streaming(body),
+                        .body(BodyStream::new(body)),
                 )
             })
             .rustls(tls_config())
@@ -467,9 +467,9 @@ async fn test_h2_response_http_error_handling() {
 #[display(fmt = "error")]
 struct BadRequest;
 
-impl From<BadRequest> for Response<AnyBody> {
+impl From<BadRequest> for Response<BoxBody> {
     fn from(_: BadRequest) -> Self {
-        Response::bad_request().set_body(AnyBody::from("error"))
+        Response::bad_request().set_body(BoxBody::new("error"))
     }
 }
 
@@ -477,7 +477,7 @@ impl From<BadRequest> for Response<AnyBody> {
 async fn test_h2_service_error() {
     let mut srv = test_server(move || {
         HttpService::build()
-            .h2(|_| err::<Response<AnyBody>, _>(BadRequest))
+            .h2(|_| err::<Response<BoxBody>, _>(BadRequest))
             .rustls(tls_config())
     })
     .await;
@@ -494,7 +494,7 @@ async fn test_h2_service_error() {
 async fn test_h1_service_error() {
     let mut srv = test_server(move || {
         HttpService::build()
-            .h1(|_| err::<Response<AnyBody>, _>(BadRequest))
+            .h1(|_| err::<Response<BoxBody>, _>(BadRequest))
             .rustls(tls_config())
     })
     .await;
diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs
index 11bc8e939..adf2a28ca 100644
--- a/actix-http/tests/test_server.rs
+++ b/actix-http/tests/test_server.rs
@@ -6,7 +6,7 @@ use std::{
 };
 
 use actix_http::{
-    body::{AnyBody, SizedStream},
+    body::{self, BodyStream, BoxBody, SizedStream},
     header, http, Error, HttpMessage, HttpService, KeepAlive, Request, Response,
     StatusCode,
 };
@@ -69,7 +69,7 @@ async fn test_h1_2() {
 #[display(fmt = "expect failed")]
 struct ExpectFailed;
 
-impl From<ExpectFailed> for Response<AnyBody> {
+impl From<ExpectFailed> for Response<BoxBody> {
     fn from(_: ExpectFailed) -> Self {
         Response::new(StatusCode::EXPECTATION_FAILED)
     }
@@ -622,7 +622,7 @@ async fn test_h1_body_chunked_explicit() {
                 ok::<_, Infallible>(
                     Response::build(StatusCode::OK)
                         .insert_header((header::TRANSFER_ENCODING, "chunked"))
-                        .streaming(body),
+                        .body(BodyStream::new(body)),
                 )
             })
             .tcp()
@@ -656,7 +656,9 @@ async fn test_h1_body_chunked_implicit() {
         HttpService::build()
             .h1(|_| {
                 let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
-                ok::<_, Infallible>(Response::build(StatusCode::OK).streaming(body))
+                ok::<_, Infallible>(
+                    Response::build(StatusCode::OK).body(BodyStream::new(body)),
+                )
             })
             .tcp()
     })
@@ -714,9 +716,9 @@ async fn test_h1_response_http_error_handling() {
 #[display(fmt = "error")]
 struct BadRequest;
 
-impl From<BadRequest> for Response<AnyBody> {
+impl From<BadRequest> for Response<BoxBody> {
     fn from(_: BadRequest) -> Self {
-        Response::bad_request().set_body(AnyBody::from("error"))
+        Response::bad_request().set_body(BoxBody::new("error"))
     }
 }
 
@@ -724,7 +726,7 @@ impl From<BadRequest> for Response<AnyBody> {
 async fn test_h1_service_error() {
     let mut srv = test_server(|| {
         HttpService::build()
-            .h1(|_| err::<Response<AnyBody>, _>(BadRequest))
+            .h1(|_| err::<Response<()>, _>(BadRequest))
             .tcp()
     })
     .await;
@@ -773,36 +775,35 @@ async fn test_not_modified_spec_h1() {
     let mut srv = test_server(|| {
         HttpService::build()
             .h1(|req: Request| {
-                let res: Response<AnyBody> = match req.path() {
+                let res: Response<BoxBody> = match req.path() {
                     // with no content-length
                     "/none" => {
-                        Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None)
+                        Response::with_body(StatusCode::NOT_MODIFIED, body::None::new())
+                            .map_into_boxed_body()
                     }
 
                     // with no content-length
-                    "/body" => Response::with_body(
-                        StatusCode::NOT_MODIFIED,
-                        AnyBody::from("1234"),
-                    ),
+                    "/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
+                        .map_into_boxed_body(),
 
                     // with manual content-length header and specific None body
                     "/cl-none" => {
-                        let mut res =
-                            Response::with_body(StatusCode::NOT_MODIFIED, AnyBody::None);
+                        let mut res = Response::with_body(
+                            StatusCode::NOT_MODIFIED,
+                            body::None::new(),
+                        );
                         res.headers_mut()
                             .insert(CL.clone(), header::HeaderValue::from_static("24"));
-                        res
+                        res.map_into_boxed_body()
                     }
 
                     // with manual content-length header and ignore-able body
                     "/cl-body" => {
-                        let mut res = Response::with_body(
-                            StatusCode::NOT_MODIFIED,
-                            AnyBody::from("1234"),
-                        );
+                        let mut res =
+                            Response::with_body(StatusCode::NOT_MODIFIED, "1234");
                         res.headers_mut()
                             .insert(CL.clone(), header::HeaderValue::from_static("4"));
-                        res
+                        res.map_into_boxed_body()
                     }
 
                     _ => panic!("unknown route"),
diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs
index 6d0de2316..c91382013 100644
--- a/actix-http/tests/test_ws.rs
+++ b/actix-http/tests/test_ws.rs
@@ -6,7 +6,7 @@ use std::{
 
 use actix_codec::{AsyncRead, AsyncWrite, Framed};
 use actix_http::{
-    body::{AnyBody, BodySize},
+    body::{BodySize, BoxBody},
     h1,
     ws::{self, CloseCode, Frame, Item, Message},
     Error, HttpService, Request, Response,
@@ -50,14 +50,14 @@ enum WsServiceError {
     Dispatcher,
 }
 
-impl From<WsServiceError> for Response<AnyBody> {
+impl From<WsServiceError> for Response<BoxBody> {
     fn from(err: WsServiceError) -> Self {
         match err {
             WsServiceError::Http(err) => err.into(),
             WsServiceError::Ws(err) => err.into(),
             WsServiceError::Io(_err) => unreachable!(),
             WsServiceError::Dispatcher => Response::internal_server_error()
-                .set_body(AnyBody::from(format!("{}", err))),
+                .set_body(BoxBody::new(format!("{}", err))),
         }
     }
 }
diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs
index b80918ec0..1decd6e98 100644
--- a/actix-test/src/lib.rs
+++ b/actix-test/src/lib.rs
@@ -31,7 +31,7 @@ extern crate tls_openssl as openssl;
 #[cfg(feature = "rustls")]
 extern crate tls_rustls as rustls;
 
-use std::{error::Error as StdError, fmt, net, thread, time::Duration};
+use std::{fmt, net, thread, time::Duration};
 
 use actix_codec::{AsyncRead, AsyncWrite, Framed};
 pub use actix_http::test::TestBuffer;
@@ -41,7 +41,8 @@ use actix_http::{
 };
 use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
 use actix_web::{
-    dev::{AppConfig, MessageBody, Server, ServerHandle, Service},
+    body::MessageBody,
+    dev::{AppConfig, Server, ServerHandle, Service},
     rt::{self, System},
     web, Error,
 };
@@ -88,7 +89,6 @@ where
     S::Response: Into<Response<B>> + 'static,
     <S::Service as Service<Request>>::Future: 'static,
     B: MessageBody + 'static,
-    B::Error: Into<Box<dyn StdError>>,
 {
     start_with(TestServerConfig::default(), factory)
 }
@@ -128,7 +128,6 @@ where
     S::Response: Into<Response<B>> + 'static,
     <S::Service as Service<Request>>::Future: 'static,
     B: MessageBody + 'static,
-    B::Error: Into<Box<dyn StdError>>,
 {
     // for sending handles and server info back from the spawned thread
     let (started_tx, started_rx) = std::sync::mpsc::channel();
diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml
index e7b8cd0f0..28b5b29ea 100644
--- a/actix-web-actors/Cargo.toml
+++ b/actix-web-actors/Cargo.toml
@@ -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]
diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs
index f0a53d4e0..b6323c2ed 100644
--- a/actix-web-actors/src/ws.rs
+++ b/actix-web-actors/src/ws.rs
@@ -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>
diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs
index 0a8e50b3e..e481b2839 100644
--- a/actix-web-actors/tests/test_ws.rs
+++ b/actix-web-actors/tests/test_ws.rs
@@ -78,7 +78,7 @@ async fn test_with_credentials() {
     match srv.ws().await {
         Ok(_) => panic!("WebSocket client without credentials should panic"),
         Err(awc::error::WsClientError::InvalidResponseStatus(status)) => {
-            assert_eq!(status, StatusCode::UNAUTHORIZED)
+            assert_eq!(status, StatusCode::UNAUTHORIZED);
         }
         Err(e) => panic!("Invalid error from WebSocket client: {}", e),
     }
diff --git a/awc/Cargo.toml b/awc/Cargo.toml
index 851e5cd43..fc60f5edb 100644
--- a/awc/Cargo.toml
+++ b/awc/Cargo.toml
@@ -105,6 +105,7 @@ brotli2 = "0.3.2"
 env_logger = "0.9"
 flate2 = "1.0.13"
 futures-util = { version = "0.3.7", default-features = false }
+static_assertions = "1.1"
 rcgen = "0.8"
 rustls-pemfile = "0.2"
 
diff --git a/awc/src/any_body.rs b/awc/src/any_body.rs
new file mode 100644
index 000000000..cb9038ff3
--- /dev/null
+++ b/awc/src/any_body.rs
@@ -0,0 +1,266 @@
+use std::{
+    borrow::Cow,
+    fmt, mem,
+    pin::Pin,
+    task::{Context, Poll},
+};
+
+use bytes::{Bytes, BytesMut};
+use futures_core::Stream;
+use pin_project_lite::pin_project;
+
+use actix_http::body::{BodySize, BodyStream, BoxBody, MessageBody, SizedStream};
+
+use crate::BoxError;
+
+pin_project! {
+    /// Represents various types of HTTP message body.
+    #[derive(Clone)]
+    #[project = AnyBodyProj]
+    pub enum AnyBody<B = BoxBody> {
+        /// Empty response. `Content-Length` header is not set.
+        None,
+
+        /// Complete, in-memory response body.
+        Bytes { body: Bytes },
+
+        /// Generic / Other message body.
+        Body { #[pin] body: 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 { body: Bytes::new() }
+    }
+
+    /// Create boxed body from generic message body.
+    pub fn new_boxed<B>(body: B) -> Self
+    where
+        B: MessageBody + 'static,
+    {
+        Self::Body {
+            body: BoxBody::new(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 {
+            body: 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 {
+            body: Bytes::copy_from_slice(s),
+        }
+    }
+}
+
+impl<B> AnyBody<B> {
+    /// Create body from generic message body.
+    pub fn new(body: B) -> Self {
+        Self::Body { body }
+    }
+}
+
+impl<B> AnyBody<B>
+where
+    B: MessageBody + 'static,
+{
+    pub fn into_boxed(self) -> AnyBody {
+        match self {
+            Self::None => AnyBody::None,
+            Self::Bytes { body: bytes } => AnyBody::Bytes { body: bytes },
+            Self::Body { body } => AnyBody::new_boxed(body),
+        }
+    }
+}
+
+impl<B> MessageBody for AnyBody<B>
+where
+    B: MessageBody,
+{
+    type Error = crate::BoxError;
+
+    fn size(&self) -> BodySize {
+        match self {
+            AnyBody::None => BodySize::None,
+            AnyBody::Bytes { ref body } => BodySize::Sized(body.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 { body } => {
+                let len = body.len();
+                if len == 0 {
+                    Poll::Ready(None)
+                } else {
+                    Poll::Ready(Some(Ok(mem::take(body))))
+                }
+            }
+
+            AnyBodyProj::Body { body } => body.poll_next(cx).map_err(|err| err.into()),
+        }
+    }
+}
+
+impl PartialEq for AnyBody {
+    fn eq(&self, other: &AnyBody) -> bool {
+        match self {
+            AnyBody::None => matches!(*other, AnyBody::None),
+            AnyBody::Bytes { body } => match other {
+                AnyBody::Bytes { body: b2 } => body == 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 body } => write!(f, "AnyBody::Bytes({:?})", body),
+            AnyBody::Body { ref body } => write!(f, "AnyBody::Message({:?})", body),
+        }
+    }
+}
+
+impl<B> From<&'static str> for AnyBody<B> {
+    fn from(string: &'static str) -> Self {
+        Self::Bytes {
+            body: Bytes::from_static(string.as_ref()),
+        }
+    }
+}
+
+impl<B> From<&'static [u8]> for AnyBody<B> {
+    fn from(bytes: &'static [u8]) -> Self {
+        Self::Bytes {
+            body: Bytes::from_static(bytes),
+        }
+    }
+}
+
+impl<B> From<Vec<u8>> for AnyBody<B> {
+    fn from(vec: Vec<u8>) -> Self {
+        Self::Bytes {
+            body: Bytes::from(vec),
+        }
+    }
+}
+
+impl<B> From<String> for AnyBody<B> {
+    fn from(string: String) -> Self {
+        Self::Bytes {
+            body: Bytes::from(string),
+        }
+    }
+}
+
+impl<B> From<&'_ String> for AnyBody<B> {
+    fn from(string: &String) -> Self {
+        Self::Bytes {
+            body: 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 {
+                body: Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(s)),
+            },
+        }
+    }
+}
+
+impl<B> From<Bytes> for AnyBody<B> {
+    fn from(bytes: Bytes) -> Self {
+        Self::Bytes { body: bytes }
+    }
+}
+
+impl<B> From<BytesMut> for AnyBody<B> {
+    fn from(bytes: BytesMut) -> Self {
+        Self::Bytes {
+            body: bytes.freeze(),
+        }
+    }
+}
+
+impl<S, E> From<SizedStream<S>> for AnyBody
+where
+    S: Stream<Item = Result<Bytes, E>> + 'static,
+    E: Into<BoxError> + 'static,
+{
+    fn from(stream: SizedStream<S>) -> Self {
+        AnyBody::new_boxed(stream)
+    }
+}
+
+impl<S, E> From<BodyStream<S>> for AnyBody
+where
+    S: Stream<Item = Result<Bytes, E>> + 'static,
+    E: Into<BoxError> + 'static,
+{
+    fn from(stream: BodyStream<S>) -> Self {
+        AnyBody::new_boxed(stream)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::marker::PhantomPinned;
+
+    use static_assertions::{assert_impl_all, assert_not_impl_all};
+
+    use super::*;
+
+    struct PinType(PhantomPinned);
+
+    impl MessageBody for PinType {
+        type Error = crate::BoxError;
+
+        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!(AnyBody<PinType>: MessageBody);
+
+    assert_not_impl_all!(AnyBody: Send, Sync, Unpin);
+    assert_not_impl_all!(AnyBody<PinType>: Send, Sync, Unpin);
+}
diff --git a/awc/src/client/connection.rs b/awc/src/client/connection.rs
index 6bbc9ad07..0e1f0bfec 100644
--- a/awc/src/client/connection.rs
+++ b/awc/src/client/connection.rs
@@ -12,9 +12,9 @@ use bytes::Bytes;
 use futures_core::future::LocalBoxFuture;
 use h2::client::SendRequest;
 
-use actix_http::{
-    body::MessageBody, h1::ClientCodec, Error, Payload, RequestHeadType, ResponseHead,
-};
+use actix_http::{body::MessageBody, h1::ClientCodec, Payload, RequestHeadType, ResponseHead};
+
+use crate::BoxError;
 
 use super::error::SendRequestError;
 use super::pool::Acquired;
@@ -254,7 +254,7 @@ where
     where
         H: Into<RequestHeadType> + 'static,
         RB: MessageBody + 'static,
-        RB::Error: Into<Error>,
+        RB::Error: Into<BoxError>,
     {
         Box::pin(async move {
             match self {
diff --git a/awc/src/client/error.rs b/awc/src/client/error.rs
index 68ffb6fbf..d351b5d5e 100644
--- a/awc/src/client/error.rs
+++ b/awc/src/client/error.rs
@@ -1,13 +1,13 @@
-use std::{error::Error as StdError, fmt, io};
+use std::{fmt, io};
 
 use derive_more::{Display, From};
 
-use actix_http::{
-    error::{Error, ParseError},
-    http::Error as HttpError,
-};
+use actix_http::{error::ParseError, http::Error as HttpError};
+
 #[cfg(feature = "openssl")]
-use actix_tls::accept::openssl::reexports::Error as OpenSslError;
+use actix_tls::accept::openssl::reexports::Error as OpensslError;
+
+use crate::BoxError;
 
 /// A set of errors that can occur while connecting to an HTTP host
 #[derive(Debug, Display, From)]
@@ -20,7 +20,7 @@ pub enum ConnectError {
     /// SSL error
     #[cfg(feature = "openssl")]
     #[display(fmt = "{}", _0)]
-    SslError(OpenSslError),
+    SslError(OpensslError),
 
     /// Failed to resolve the hostname
     #[display(fmt = "Failed resolving hostname: {}", _0)]
@@ -118,11 +118,11 @@ pub enum SendRequestError {
     TunnelNotSupported,
 
     /// Error sending request body
-    Body(Error),
+    Body(BoxError),
 
     /// Other errors that can occur after submitting a request.
     #[display(fmt = "{:?}: {}", _1, _0)]
-    Custom(Box<dyn StdError>, Box<dyn fmt::Debug>),
+    Custom(BoxError, Box<dyn fmt::Debug>),
 }
 
 impl std::error::Error for SendRequestError {}
@@ -141,7 +141,7 @@ pub enum FreezeRequestError {
 
     /// Other errors that can occur after submitting a request.
     #[display(fmt = "{:?}: {}", _1, _0)]
-    Custom(Box<dyn StdError>, Box<dyn fmt::Debug>),
+    Custom(BoxError, Box<dyn fmt::Debug>),
 }
 
 impl std::error::Error for FreezeRequestError {}
diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs
index c442cd4cf..b26a97eeb 100644
--- a/awc/src/client/h1proto.rs
+++ b/awc/src/client/h1proto.rs
@@ -13,7 +13,7 @@ use actix_http::{
         header::{HeaderMap, IntoHeaderValue, EXPECT, HOST},
         StatusCode,
     },
-    Error, Payload, RequestHeadType, ResponseHead,
+    Payload, RequestHeadType, ResponseHead,
 };
 use actix_utils::future::poll_fn;
 use bytes::buf::BufMut;
@@ -22,6 +22,8 @@ use futures_core::{ready, Stream};
 use futures_util::SinkExt as _;
 use pin_project_lite::pin_project;
 
+use crate::BoxError;
+
 use super::connection::{ConnectionIo, H1Connection};
 use super::error::{ConnectError, SendRequestError};
 
@@ -33,7 +35,7 @@ pub(crate) async fn send_request<Io, B>(
 where
     Io: ConnectionIo,
     B: MessageBody,
-    B::Error: Into<Error>,
+    B::Error: Into<BoxError>,
 {
     // set request host header
     if !head.as_ref().headers.contains_key(HOST)
@@ -155,7 +157,7 @@ pub(crate) async fn send_body<Io, B>(
 where
     Io: ConnectionIo,
     B: MessageBody,
-    B::Error: Into<Error>,
+    B::Error: Into<BoxError>,
 {
     actix_rt::pin!(body);
 
@@ -166,7 +168,7 @@ where
                 Some(Ok(chunk)) => {
                     framed.as_mut().write(h1::Message::Chunk(Some(chunk)))?;
                 }
-                Some(Err(err)) => return Err(err.into().into()),
+                Some(Err(err)) => return Err(SendRequestError::Body(err.into())),
                 None => {
                     eof = true;
                     framed.as_mut().write(h1::Message::Chunk(None))?;
diff --git a/awc/src/client/h2proto.rs b/awc/src/client/h2proto.rs
index 66fb790a3..9ced5776b 100644
--- a/awc/src/client/h2proto.rs
+++ b/awc/src/client/h2proto.rs
@@ -13,9 +13,11 @@ use log::trace;
 use actix_http::{
     body::{BodySize, MessageBody},
     header::HeaderMap,
-    Error, Payload, RequestHeadType, ResponseHead,
+    Payload, RequestHeadType, ResponseHead,
 };
 
+use crate::BoxError;
+
 use super::{
     config::ConnectorConfig,
     connection::{ConnectionIo, H2Connection},
@@ -30,7 +32,7 @@ pub(crate) async fn send_request<Io, B>(
 where
     Io: ConnectionIo,
     B: MessageBody,
-    B::Error: Into<Error>,
+    B::Error: Into<BoxError>,
 {
     trace!("Sending client request: {:?} {:?}", head, body.size());
 
@@ -133,10 +135,12 @@ where
 async fn send_body<B>(body: B, mut send: SendStream<Bytes>) -> Result<(), SendRequestError>
 where
     B: MessageBody,
-    B::Error: Into<Error>,
+    B::Error: Into<BoxError>,
 {
     let mut buf = None;
+
     actix_rt::pin!(body);
+
     loop {
         if buf.is_none() {
             match poll_fn(|cx| body.as_mut().poll_next(cx)).await {
@@ -144,10 +148,10 @@ where
                     send.reserve_capacity(b.len());
                     buf = Some(b);
                 }
-                Some(Err(e)) => return Err(e.into().into()),
+                Some(Err(err)) => return Err(SendRequestError::Body(err.into())),
                 None => {
-                    if let Err(e) = send.send_data(Bytes::new(), true) {
-                        return Err(e.into());
+                    if let Err(err) = send.send_data(Bytes::new(), true) {
+                        return Err(err.into());
                     }
                     send.reserve_capacity(0);
                     return Ok(());
diff --git a/awc/src/connect.rs b/awc/src/connect.rs
index 05f2a6495..19870b069 100644
--- a/awc/src/connect.rs
+++ b/awc/src/connect.rs
@@ -7,16 +7,17 @@ use std::{
 };
 
 use actix_codec::Framed;
-use actix_http::{
-    body::AnyBody, h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead,
-};
+use actix_http::{h1::ClientCodec, Payload, RequestHead, RequestHeadType, ResponseHead};
 use actix_service::Service;
 use futures_core::{future::LocalBoxFuture, ready};
 
-use crate::client::{
-    Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
+use crate::{
+    any_body::AnyBody,
+    client::{
+        Connect as ClientConnect, ConnectError, Connection, ConnectionIo, SendRequestError,
+    },
+    response::ClientResponse,
 };
-use crate::response::ClientResponse;
 
 pub type BoxConnectorService = Rc<
     dyn Service<
diff --git a/awc/src/error.rs b/awc/src/error.rs
index d415efe95..726e1a506 100644
--- a/awc/src/error.rs
+++ b/awc/src/error.rs
@@ -11,6 +11,8 @@ use serde_json::error::Error as JsonError;
 
 pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
 
+// TODO: address display, error, and from impls
+
 /// Websocket client error
 #[derive(Debug, Display, From)]
 pub enum WsClientError {
diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs
index 46a00b000..472397359 100644
--- a/awc/src/frozen.rs
+++ b/awc/src/frozen.rs
@@ -1,18 +1,18 @@
-use std::{convert::TryFrom, error::Error as StdError, net, rc::Rc, time::Duration};
+use std::{convert::TryFrom, net, rc::Rc, time::Duration};
 
 use bytes::Bytes;
 use futures_core::Stream;
 use serde::Serialize;
 
 use actix_http::{
-    body::AnyBody,
     http::{header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, Method, Uri},
     RequestHead,
 };
 
 use crate::{
+    any_body::AnyBody,
     sender::{RequestSender, SendClientRequest},
-    ClientConfig,
+    BoxError, ClientConfig,
 };
 
 /// `FrozenClientRequest` struct represents cloneable client request.
@@ -82,7 +82,7 @@ impl FrozenClientRequest {
     pub fn send_stream<S, E>(&self, stream: S) -> SendClientRequest
     where
         S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
-        E: Into<Box<dyn StdError>> + 'static,
+        E: Into<BoxError> + 'static,
     {
         RequestSender::Rc(self.head.clone(), None).send_stream(
             self.addr,
@@ -207,7 +207,7 @@ impl FrozenSendBuilder {
     pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
     where
         S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
-        E: Into<Box<dyn StdError>> + 'static,
+        E: Into<BoxError> + 'static,
     {
         if let Some(e) = self.err {
             return e.into();
diff --git a/awc/src/lib.rs b/awc/src/lib.rs
index fc99419eb..2f4183120 100644
--- a/awc/src/lib.rs
+++ b/awc/src/lib.rs
@@ -104,6 +104,7 @@
 #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
 #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
 
+mod any_body;
 mod builder;
 mod client;
 mod connect;
@@ -139,6 +140,8 @@ use actix_service::Service;
 
 use self::client::{ConnectInfo, TcpConnectError, TcpConnection};
 
+pub(crate) type BoxError = Box<dyn std::error::Error>;
+
 /// An asynchronous HTTP and WebSocket client.
 ///
 /// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU
diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs
index 12a71f7cb..89cff22cd 100644
--- a/awc/src/middleware/redirect.rs
+++ b/awc/src/middleware/redirect.rs
@@ -8,7 +8,6 @@ use std::{
 };
 
 use actix_http::{
-    body::AnyBody,
     http::{header, Method, StatusCode, Uri},
     RequestHead, RequestHeadType,
 };
@@ -17,10 +16,12 @@ use bytes::Bytes;
 use futures_core::ready;
 
 use super::Transform;
-
-use crate::client::{InvalidUrl, SendRequestError};
-use crate::connect::{ConnectRequest, ConnectResponse};
-use crate::ClientResponse;
+use crate::{
+    any_body::AnyBody,
+    client::{InvalidUrl, SendRequestError},
+    connect::{ConnectRequest, ConnectResponse},
+    ClientResponse,
+};
 
 pub struct Redirect {
     max_redirect_times: u8,
@@ -95,7 +96,7 @@ where
                 };
 
                 let body_opt = match body {
-                    AnyBody::Bytes(ref b) => Some(b.clone()),
+                    AnyBody::Bytes { ref body } => Some(body.clone()),
                     _ => None,
                 };
 
@@ -192,7 +193,9 @@ where
                         let body_new = if is_redirect {
                             // try to reuse body
                             match body {
-                                Some(ref bytes) => AnyBody::Bytes(bytes.clone()),
+                                Some(ref bytes) => AnyBody::Bytes {
+                                    body: bytes.clone(),
+                                },
                                 // TODO: should this be AnyBody::Empty or AnyBody::None.
                                 _ => AnyBody::empty(),
                             }
diff --git a/awc/src/request.rs b/awc/src/request.rs
index f364b43c7..d26b703f6 100644
--- a/awc/src/request.rs
+++ b/awc/src/request.rs
@@ -1,11 +1,10 @@
-use std::{convert::TryFrom, error::Error as StdError, fmt, net, rc::Rc, time::Duration};
+use std::{convert::TryFrom, fmt, net, rc::Rc, time::Duration};
 
 use bytes::Bytes;
 use futures_core::Stream;
 use serde::Serialize;
 
 use actix_http::{
-    body::AnyBody,
     http::{
         header::{self, IntoHeaderPair},
         ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
@@ -13,15 +12,17 @@ use actix_http::{
     RequestHead,
 };
 
-#[cfg(feature = "cookies")]
-use crate::cookie::{Cookie, CookieJar};
 use crate::{
+    any_body::AnyBody,
     error::{FreezeRequestError, InvalidUrl},
     frozen::FrozenClientRequest,
     sender::{PrepForSendingError, RequestSender, SendClientRequest},
-    ClientConfig,
+    BoxError, ClientConfig,
 };
 
+#[cfg(feature = "cookies")]
+use crate::cookie::{Cookie, CookieJar};
+
 /// An HTTP Client request builder
 ///
 /// This type can be used to construct an instance of `ClientRequest` through a
@@ -404,7 +405,7 @@ impl ClientRequest {
     pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
     where
         S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
-        E: Into<Box<dyn StdError>> + 'static,
+        E: Into<BoxError> + 'static,
     {
         let slf = match self.prep_for_sending() {
             Ok(slf) => slf,
diff --git a/awc/src/sender.rs b/awc/src/sender.rs
index 7e1bcd646..51fce1913 100644
--- a/awc/src/sender.rs
+++ b/awc/src/sender.rs
@@ -1,5 +1,4 @@
 use std::{
-    error::Error as StdError,
     future::Future,
     net,
     pin::Pin,
@@ -9,12 +8,12 @@ use std::{
 };
 
 use actix_http::{
-    body::{AnyBody, BodyStream},
+    body::BodyStream,
     http::{
         header::{self, HeaderMap, HeaderName, IntoHeaderValue},
         Error as HttpError,
     },
-    Error, RequestHead, RequestHeadType,
+    RequestHead, RequestHeadType,
 };
 use actix_rt::time::{sleep, Sleep};
 use bytes::Bytes;
@@ -26,8 +25,9 @@ use serde::Serialize;
 use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream};
 
 use crate::{
+    any_body::AnyBody,
     error::{FreezeRequestError, InvalidUrl, SendRequestError},
-    ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
+    BoxError, ClientConfig, ClientResponse, ConnectRequest, ConnectResponse,
 };
 
 #[derive(Debug, From)]
@@ -162,12 +162,6 @@ impl From<SendRequestError> for SendClientRequest {
     }
 }
 
-impl From<Error> for SendClientRequest {
-    fn from(e: Error) -> Self {
-        SendClientRequest::Err(Some(e.into()))
-    }
-}
-
 impl From<HttpError> for SendClientRequest {
     fn from(e: HttpError) -> Self {
         SendClientRequest::Err(Some(e.into()))
@@ -236,7 +230,9 @@ impl RequestSender {
             response_decompress,
             timeout,
             config,
-            AnyBody::Bytes(Bytes::from(body)),
+            AnyBody::Bytes {
+                body: Bytes::from(body),
+            },
         )
     }
 
@@ -265,7 +261,9 @@ impl RequestSender {
             response_decompress,
             timeout,
             config,
-            AnyBody::Bytes(Bytes::from(body)),
+            AnyBody::Bytes {
+                body: Bytes::from(body),
+            },
         )
     }
 
@@ -279,7 +277,7 @@ impl RequestSender {
     ) -> SendClientRequest
     where
         S: Stream<Item = Result<Bytes, E>> + Unpin + 'static,
-        E: Into<Box<dyn StdError>> + 'static,
+        E: Into<BoxError> + 'static,
     {
         self.send_body(
             addr,
diff --git a/benches/responder.rs b/benches/responder.rs
index 5d0b98d5f..20aae3351 100644
--- a/benches/responder.rs
+++ b/benches/responder.rs
@@ -1,9 +1,10 @@
 use std::{future::Future, time::Instant};
 
+use actix_http::body::BoxBody;
 use actix_utils::future::{ready, Ready};
-use actix_web::http::StatusCode;
-use actix_web::test::TestRequest;
-use actix_web::{error, Error, HttpRequest, HttpResponse, Responder};
+use actix_web::{
+    error, http::StatusCode, test::TestRequest, Error, HttpRequest, HttpResponse, Responder,
+};
 use criterion::{criterion_group, criterion_main, Criterion};
 use futures_util::future::{join_all, Either};
 
@@ -50,7 +51,9 @@ where
 }
 
 impl Responder for StringResponder {
-    fn respond_to(self, _: &HttpRequest) -> HttpResponse {
+    type Body = BoxBody;
+
+    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
         HttpResponse::build(StatusCode::OK)
             .content_type("text/plain; charset=utf-8")
             .body(self.0)
@@ -58,9 +61,11 @@ impl Responder for StringResponder {
 }
 
 impl<T: Responder> Responder for OptionResponder<T> {
-    fn respond_to(self, req: &HttpRequest) -> HttpResponse {
+    type Body = BoxBody;
+
+    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
         match self.0 {
-            Some(t) => t.respond_to(req),
+            Some(t) => t.respond_to(req).map_into_boxed_body(),
             None => HttpResponse::from_error(error::ErrorInternalServerError("err")),
         }
     }
diff --git a/src/app.rs b/src/app.rs
index a291a959e..36063ec79 100644
--- a/src/app.rs
+++ b/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::{AnyBody, 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>,
@@ -39,7 +37,7 @@ pub struct App<T, B> {
     _phantom: PhantomData<B>,
 }
 
-impl App<AppEntry, AnyBody> {
+impl App<AppEntry, BoxBody> {
     /// Create application builder. Application can be configured with a builder-like pattern.
     #[allow(clippy::new_without_default)]
     pub fn new() -> Self {
@@ -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
     }
diff --git a/src/app_service.rs b/src/app_service.rs
index cf34b302e..bca8f2629 100644
--- a/src/app_service.rs
+++ b/src/app_service.rs
@@ -2,10 +2,7 @@ use std::{cell::RefCell, mem, rc::Rc};
 
 use actix_http::{Extensions, Request};
 use actix_router::{Path, ResourceDef, Router, Url};
-use actix_service::{
-    boxed::{self, BoxService, BoxServiceFactory},
-    fn_service, Service, ServiceFactory,
-};
+use actix_service::{boxed, fn_service, Service, ServiceFactory};
 use futures_core::future::LocalBoxFuture;
 use futures_util::future::join_all;
 
@@ -15,13 +12,14 @@ use crate::{
     guard::Guard,
     request::{HttpRequest, HttpRequestPool},
     rmap::ResourceMap,
-    service::{AppServiceFactory, ServiceRequest, ServiceResponse},
+    service::{
+        AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, ServiceRequest,
+        ServiceResponse,
+    },
     Error, HttpResponse,
 };
 
 type Guards = Vec<Box<dyn Guard>>;
-type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
-type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
 
 /// Service factory to convert `Request` to a `ServiceRequest<S>`.
 /// It also executes data factories.
@@ -39,7 +37,7 @@ where
     pub(crate) extensions: RefCell<Option<Extensions>>,
     pub(crate) async_data_factories: Rc<[FnDataFactory]>,
     pub(crate) services: Rc<RefCell<Vec<Box<dyn AppServiceFactory>>>>,
-    pub(crate) default: Option<Rc<HttpNewService>>,
+    pub(crate) default: Option<Rc<BoxedHttpServiceFactory>>,
     pub(crate) factory_ref: Rc<RefCell<Option<AppRoutingFactory>>>,
     pub(crate) external: RefCell<Vec<ResourceDef>>,
 }
@@ -230,8 +228,14 @@ where
 }
 
 pub struct AppRoutingFactory {
-    services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
-    default: Rc<HttpNewService>,
+    services: Rc<
+        [(
+            ResourceDef,
+            BoxedHttpServiceFactory,
+            RefCell<Option<Guards>>,
+        )],
+    >,
+    default: Rc<BoxedHttpServiceFactory>,
 }
 
 impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
@@ -279,8 +283,8 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
 
 /// The Actix Web router default entry point.
 pub struct AppRouting {
-    router: Router<HttpService, Guards>,
-    default: HttpService,
+    router: Router<BoxedHttpService, Guards>,
+    default: BoxedHttpService,
 }
 
 impl Service<ServiceRequest> for AppRouting {
diff --git a/src/data.rs b/src/data.rs
index d27ad196b..b29e4ecf4 100644
--- a/src/data.rs
+++ b/src/data.rs
@@ -284,7 +284,7 @@ mod tests {
     async fn test_data_from_arc() {
         let data_new = Data::new(String::from("test-123"));
         let data_from_arc = Data::from(Arc::new(String::from("test-123")));
-        assert_eq!(data_new.0, data_from_arc.0)
+        assert_eq!(data_new.0, data_from_arc.0);
     }
 
     #[actix_rt::test]
diff --git a/src/dev.rs b/src/dev.rs
index 59805b822..d4a64985c 100644
--- a/src/dev.rs
+++ b/src/dev.rs
@@ -1,7 +1,7 @@
 //! Lower-level types and re-exports.
 //!
 //! Most users will not have to interact with the types in this module, but it is useful for those
-//! writing extractors, middleware and libraries, or interacting with the service API directly.
+//! writing extractors, middleware, libraries, or interacting with the service API directly.
 
 pub use crate::config::{AppConfig, AppService};
 #[doc(hidden)]
@@ -14,11 +14,6 @@ pub use crate::types::form::UrlEncoded;
 pub use crate::types::json::JsonBody;
 pub use crate::types::readlines::Readlines;
 
-#[allow(deprecated)]
-pub use actix_http::body::{AnyBody, Body, BodySize, MessageBody, SizedStream};
-
-#[cfg(feature = "__compress")]
-pub use actix_http::encoding::Decoder as Decompress;
 pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead};
 pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
 pub use actix_server::{Server, ServerHandle};
@@ -26,8 +21,10 @@ pub use actix_service::{
     always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform,
 };
 
+#[cfg(feature = "__compress")]
+pub use actix_http::encoding::Decoder as Decompress;
+
 use crate::http::header::ContentEncoding;
-use actix_http::ResponseBuilder;
 
 use actix_router::Patterns;
 
@@ -62,7 +59,7 @@ pub trait BodyEncoding {
     fn encoding(&mut self, encoding: ContentEncoding) -> &mut Self;
 }
 
-impl BodyEncoding for ResponseBuilder {
+impl BodyEncoding for actix_http::ResponseBuilder {
     fn get_encoding(&self) -> Option<ContentEncoding> {
         self.extensions().get::<Enc>().map(|enc| enc.0)
     }
@@ -73,7 +70,7 @@ impl BodyEncoding for ResponseBuilder {
     }
 }
 
-impl<B> BodyEncoding for Response<B> {
+impl<B> BodyEncoding for actix_http::Response<B> {
     fn get_encoding(&self) -> Option<ContentEncoding> {
         self.extensions().get::<Enc>().map(|enc| enc.0)
     }
@@ -105,3 +102,41 @@ impl<B> BodyEncoding for crate::HttpResponse<B> {
         self
     }
 }
+
+// TODO: remove this if it doesn't appear to be needed
+
+#[allow(dead_code)]
+#[derive(Debug)]
+pub(crate) enum AnyBody {
+    None,
+    Full { body: crate::web::Bytes },
+    Boxed { body: actix_http::body::BoxBody },
+}
+
+impl crate::body::MessageBody for AnyBody {
+    type Error = crate::BoxError;
+
+    /// Body size hint.
+    fn size(&self) -> crate::body::BodySize {
+        match self {
+            AnyBody::None => crate::body::BodySize::None,
+            AnyBody::Full { body } => body.size(),
+            AnyBody::Boxed { body } => body.size(),
+        }
+    }
+
+    /// Attempt to pull out the next chunk of body bytes.
+    fn poll_next(
+        self: std::pin::Pin<&mut Self>,
+        cx: &mut std::task::Context<'_>,
+    ) -> std::task::Poll<Option<Result<crate::web::Bytes, Self::Error>>> {
+        match self.get_mut() {
+            AnyBody::None => std::task::Poll::Ready(None),
+            AnyBody::Full { body } => {
+                let bytes = std::mem::take(body);
+                std::task::Poll::Ready(Some(Ok(bytes)))
+            }
+            AnyBody::Boxed { body } => body.as_pin_mut().poll_next(cx),
+        }
+    }
+}
diff --git a/src/error/error.rs b/src/error/error.rs
index add290867..be17c1962 100644
--- a/src/error/error.rs
+++ b/src/error/error.rs
@@ -1,6 +1,6 @@
 use std::{error::Error as StdError, fmt};
 
-use actix_http::{body::AnyBody, Response};
+use actix_http::{body::BoxBody, Response};
 
 use crate::{HttpResponse, ResponseError};
 
@@ -69,8 +69,8 @@ impl<T: ResponseError + 'static> From<T> for Error {
     }
 }
 
-impl From<Error> for Response<AnyBody> {
-    fn from(err: Error) -> Response<AnyBody> {
+impl From<Error> for Response<BoxBody> {
+    fn from(err: Error) -> Response<BoxBody> {
         err.error_response().into()
     }
 }
diff --git a/src/error/internal.rs b/src/error/internal.rs
index 3d99012dc..c766ba83e 100644
--- a/src/error/internal.rs
+++ b/src/error/internal.rs
@@ -1,6 +1,10 @@
 use std::{cell::RefCell, fmt, io::Write as _};
 
-use actix_http::{body::AnyBody, header, StatusCode};
+use actix_http::{
+    body::BoxBody,
+    header::{self, IntoHeaderValue as _},
+    StatusCode,
+};
 use bytes::{BufMut as _, BytesMut};
 
 use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError};
@@ -84,11 +88,10 @@ where
                 let mut buf = BytesMut::new().writer();
                 let _ = write!(buf, "{}", self);
 
-                res.headers_mut().insert(
-                    header::CONTENT_TYPE,
-                    header::HeaderValue::from_static("text/plain; charset=utf-8"),
-                );
-                res.set_body(AnyBody::from(buf.into_inner()))
+                let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
+                res.headers_mut().insert(header::CONTENT_TYPE, mime);
+
+                res.set_body(BoxBody::new(buf.into_inner()))
             }
 
             InternalErrorType::Response(ref resp) => {
@@ -106,7 +109,9 @@ impl<T> Responder for InternalError<T>
 where
     T: fmt::Debug + fmt::Display + 'static,
 {
-    fn respond_to(self, _: &HttpRequest) -> HttpResponse {
+    type Body = BoxBody;
+
+    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
         HttpResponse::from_error(self)
     }
 }
diff --git a/src/error/macros.rs b/src/error/macros.rs
index 38650c5e8..78b1ed9f6 100644
--- a/src/error/macros.rs
+++ b/src/error/macros.rs
@@ -97,7 +97,7 @@ mod tests {
         let resp_body: &mut dyn MB = &mut body;
         let body = resp_body.downcast_ref::<String>().unwrap();
         assert_eq!(body, "hello cast");
-        let body = &mut resp_body.downcast_mut::<String>().unwrap();
+        let body = resp_body.downcast_mut::<String>().unwrap();
         body.push('!');
         let body = resp_body.downcast_ref::<String>().unwrap();
         assert_eq!(body, "hello cast!");
diff --git a/src/error/response_error.rs b/src/error/response_error.rs
index 2c34be3cb..7260efa1a 100644
--- a/src/error/response_error.rs
+++ b/src/error/response_error.rs
@@ -6,11 +6,17 @@ use std::{
     io::{self, Write as _},
 };
 
-use actix_http::{body::AnyBody, header, Response, StatusCode};
+use actix_http::{
+    body::BoxBody,
+    header::{self, IntoHeaderValue},
+    Response, StatusCode,
+};
 use bytes::BytesMut;
 
-use crate::error::{downcast_dyn, downcast_get_type_id};
-use crate::{helpers, HttpResponse};
+use crate::{
+    error::{downcast_dyn, downcast_get_type_id},
+    helpers, HttpResponse,
+};
 
 /// Errors that can generate responses.
 // TODO: add std::error::Error bound when replacement for Box<dyn Error> is found
@@ -27,18 +33,16 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
     ///
     /// By default, the generated response uses a 500 Internal Server Error status code, a
     /// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl.
-    fn error_response(&self) -> HttpResponse {
+    fn error_response(&self) -> HttpResponse<BoxBody> {
         let mut res = HttpResponse::new(self.status_code());
 
         let mut buf = BytesMut::new();
         let _ = write!(helpers::MutWriter(&mut buf), "{}", self);
 
-        res.headers_mut().insert(
-            header::CONTENT_TYPE,
-            header::HeaderValue::from_static("text/plain; charset=utf-8"),
-        );
+        let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
+        res.headers_mut().insert(header::CONTENT_TYPE, mime);
 
-        res.set_body(AnyBody::from(buf))
+        res.set_body(BoxBody::new(buf))
     }
 
     downcast_get_type_id!();
@@ -86,8 +90,8 @@ impl ResponseError for actix_http::Error {
         StatusCode::INTERNAL_SERVER_ERROR
     }
 
-    fn error_response(&self) -> HttpResponse {
-        HttpResponse::new(self.status_code()).set_body(self.to_string().into())
+    fn error_response(&self) -> HttpResponse<BoxBody> {
+        HttpResponse::with_body(self.status_code(), self.to_string()).map_into_boxed_body()
     }
 }
 
@@ -123,8 +127,8 @@ impl ResponseError for actix_http::error::ContentTypeError {
 }
 
 impl ResponseError for actix_http::ws::HandshakeError {
-    fn error_response(&self) -> HttpResponse {
-        Response::from(self).into()
+    fn error_response(&self) -> HttpResponse<BoxBody> {
+        Response::from(self).map_into_boxed_body().into()
     }
 }
 
diff --git a/src/handler.rs b/src/handler.rs
index ddefe8d53..e543ecc7f 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -1,21 +1,21 @@
 use std::future::Future;
 
-use actix_service::{
-    boxed::{self, BoxServiceFactory},
-    fn_service,
-};
+use actix_service::{boxed, fn_service};
 
 use crate::{
-    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
-/// extracted from a request (i.e., [`impl FromRequest`](crate::FromRequest)) and returns a type
-/// that can be converted into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
+/// extracted from a request (i.e., [`impl FromRequest`]) and returns a type that can be converted
+/// into an [`HttpResponse`] (that is, it impls the [`Responder`] trait).
 ///
 /// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not
-/// a valid handler. See [Request Handlers](https://actix.rs/docs/handlers/) for more information.
+/// a valid handler. See <https://actix.rs/docs/handlers> for more information.
+///
+/// [`impl FromRequest`]: crate::FromRequest
 pub trait Handler<T, R>: Clone + 'static
 where
     R: Future,
@@ -24,29 +24,44 @@ where
     fn call(&self, param: T) -> R;
 }
 
-pub fn handler_service<F, T, R>(
-    handler: F,
-) -> BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>
+pub(crate) 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();
+
         async move {
             let (req, mut payload) = req.into_parts();
+
             let res = match T::from_request(&req, &mut payload).await {
                 Err(err) => HttpResponse::from_error(err),
-                Ok(data) => handler.call(data).await.respond_to(&req),
+
+                Ok(data) => handler
+                    .call(data)
+                    .await
+                    .respond_to(&req)
+                    .map_into_boxed_body(),
             };
+
             Ok(ServiceResponse::new(req, res))
         }
     }))
 }
 
-/// FromRequest trait impl for tuples
+/// Generates a [`Handler`] trait impl for N-ary functions where N is specified with a sequence of
+/// space separated type parameters.
+///
+/// # Examples
+/// ```ignore
+/// factory_tuple! {}         // implements Handler for types: fn() -> Res
+/// factory_tuple! { A B C }  // implements Handler for types: fn(A, B, C) -> Res
+/// ```
 macro_rules! factory_tuple ({ $($param:ident)* } => {
     impl<Func, $($param,)* Res> Handler<($($param,)*), Res> for Func
     where Func: Fn($($param),*) -> Res + Clone + 'static,
diff --git a/src/http/header/accept.rs b/src/http/header/accept.rs
index fe291c011..70e4118cf 100644
--- a/src/http/header/accept.rs
+++ b/src/http/header/accept.rs
@@ -208,7 +208,7 @@ impl Accept {
     /// If no q-factors are provided, the first mime type is chosen. Note that items without
     /// q-factors are given the maximum preference value.
     ///
-    /// As per the spec, will return [`Mime::STAR_STAR`] (indicating no preference) if the contained
+    /// As per the spec, will return [`mime::STAR_STAR`] (indicating no preference) if the contained
     /// list is empty.
     ///
     /// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
diff --git a/src/lib.rs b/src/lib.rs
index 3ad77ff5f..f6ec4082a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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>;
diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs
index a75335981..e6ef1806f 100644
--- a/src/middleware/compat.rs
+++ b/src/middleware/compat.rs
@@ -1,13 +1,12 @@
 //! For middleware documentation, see [`Compat`].
 
 use std::{
-    error::Error as StdError,
     future::Future,
     pin::Pin,
     task::{Context, Poll},
 };
 
-use actix_http::body::{AnyBody, MessageBody};
+use actix_http::body::MessageBody;
 use actix_service::{Service, Transform};
 use futures_core::{future::LocalBoxFuture, ready};
 use pin_project_lite::pin_project;
@@ -123,10 +122,9 @@ pub trait MapServiceResponseBody {
 impl<B> MapServiceResponseBody for ServiceResponse<B>
 where
     B: MessageBody + Unpin + 'static,
-    B::Error: Into<Box<dyn StdError + 'static>>,
 {
     fn map_body(self) -> ServiceResponse {
-        self.map_body(|_, body| AnyBody::new_boxed(body))
+        self.map_into_boxed_body()
     }
 }
 
diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs
index 3b99fd6b3..d017e9a5a 100644
--- a/src/middleware/compress.rs
+++ b/src/middleware/compress.rs
@@ -10,14 +10,13 @@ use std::{
 };
 
 use actix_http::{
-    body::{AnyBody, MessageBody},
+    body::{EitherBody, MessageBody},
     encoding::Encoder,
     http::header::{ContentEncoding, ACCEPT_ENCODING},
     StatusCode,
 };
 use actix_service::{Service, Transform};
 use actix_utils::future::{ok, Either, Ready};
-use bytes::Bytes;
 use futures_core::ready;
 use once_cell::sync::Lazy;
 use pin_project_lite::pin_project;
@@ -62,7 +61,7 @@ where
     B: MessageBody,
     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
 {
-    type Response = ServiceResponse<AnyBody<Encoder<B>>>;
+    type Response = ServiceResponse<EitherBody<Encoder<B>>>;
     type Error = Error;
     type Transform = CompressMiddleware<S>;
     type InitError = ();
@@ -112,7 +111,7 @@ where
     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
     B: MessageBody,
 {
-    type Response = ServiceResponse<AnyBody<Encoder<B>>>;
+    type Response = ServiceResponse<EitherBody<Encoder<B>>>;
     type Error = Error;
     type Future = Either<CompressResponse<S, B>, Ready<Result<Self::Response, Self::Error>>>;
 
@@ -144,19 +143,15 @@ where
 
             // There is an HTTP header but we cannot match what client as asked for
             Some(Err(_)) => {
-                let res = HttpResponse::new(StatusCode::NOT_ACCEPTABLE);
+                let res = HttpResponse::with_body(
+                    StatusCode::NOT_ACCEPTABLE,
+                    SUPPORTED_ALGORITHM_NAMES.clone(),
+                );
 
-                let res: HttpResponse<AnyBody<Encoder<B>>> = res.map_body(move |head, _| {
-                    let body_bytes = Bytes::from(SUPPORTED_ALGORITHM_NAMES.as_bytes());
-
-                    Encoder::response(
-                        ContentEncoding::Identity,
-                        head,
-                        AnyBody::Bytes(body_bytes),
-                    )
-                });
-
-                Either::right(ok(req.into_response(res)))
+                Either::right(ok(req
+                    .into_response(res)
+                    .map_into_boxed_body()
+                    .map_into_right_body()))
             }
         }
     }
@@ -179,7 +174,7 @@ where
     B: MessageBody,
     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
 {
-    type Output = Result<ServiceResponse<AnyBody<Encoder<B>>>, Error>;
+    type Output = Result<ServiceResponse<EitherBody<Encoder<B>>>, Error>;
 
     fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
         let this = self.project();
@@ -193,10 +188,11 @@ where
                 };
 
                 Poll::Ready(Ok(resp.map_body(move |head, body| {
-                    Encoder::response(enc, head, AnyBody::Body(body))
+                    EitherBody::left(Encoder::response(enc, head, body))
                 })))
             }
-            Err(e) => Poll::Ready(Err(e)),
+
+            Err(err) => Poll::Ready(Err(err)),
         }
     }
 }
diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs
index 6ab16a4eb..f89b13a1c 100644
--- a/src/middleware/logger.rs
+++ b/src/middleware/logger.rs
@@ -22,7 +22,7 @@ use regex::{Regex, RegexSet};
 use time::{format_description::well_known::Rfc3339, OffsetDateTime};
 
 use crate::{
-    dev::{BodySize, MessageBody},
+    body::{BodySize, MessageBody},
     http::HeaderName,
     service::{ServiceRequest, ServiceResponse},
     Error, HttpResponse, Result,
diff --git a/src/resource.rs b/src/resource.rs
index 851ce0fc9..fc417bac2 100644
--- a/src/resource.rs
+++ b/src/resource.rs
@@ -1,32 +1,29 @@
-use std::cell::RefCell;
-use std::fmt;
-use std::future::Future;
-use std::rc::Rc;
+use std::{cell::RefCell, fmt, future::Future, rc::Rc};
 
 use actix_http::Extensions;
 use actix_router::{IntoPatterns, Patterns};
-use actix_service::boxed::{self, BoxService, BoxServiceFactory};
 use actix_service::{
-    apply, apply_fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory,
+    apply, apply_fn_factory, boxed, fn_service, IntoServiceFactory, Service, ServiceFactory,
     ServiceFactoryExt, Transform,
 };
 use futures_core::future::LocalBoxFuture;
 use futures_util::future::join_all;
 
 use crate::{
+    body::MessageBody,
     data::Data,
-    dev::{ensure_leading_slash, AppService, HttpServiceFactory, ResourceDef},
+    dev::{ensure_leading_slash, AppService, ResourceDef},
     guard::Guard,
     handler::Handler,
     responder::Responder,
     route::{Route, RouteService},
-    service::{ServiceRequest, ServiceResponse},
-    Error, FromRequest, HttpResponse,
+    service::{
+        BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest,
+        ServiceResponse,
+    },
+    BoxError, Error, FromRequest, HttpResponse,
 };
 
-type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
-type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
-
 /// *Resource* is an entry in resources table which corresponds to requested URL.
 ///
 /// Resource in turn has at least one route.
@@ -56,7 +53,7 @@ pub struct Resource<T = ResourceEndpoint> {
     routes: Vec<Route>,
     app_data: Option<Extensions>,
     guards: Vec<Box<dyn Guard>>,
-    default: HttpNewService,
+    default: BoxedHttpServiceFactory,
     factory_ref: Rc<RefCell<Option<ResourceFactory>>>,
 }
 
@@ -242,6 +239,8 @@ where
         I: FromRequest + 'static,
         R: Future + 'static,
         R::Output: Responder + '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
@@ -422,7 +421,7 @@ where
 
 pub struct ResourceFactory {
     routes: Vec<Route>,
-    default: HttpNewService,
+    default: BoxedHttpServiceFactory,
 }
 
 impl ServiceFactory<ServiceRequest> for ResourceFactory {
@@ -454,7 +453,7 @@ impl ServiceFactory<ServiceRequest> for ResourceFactory {
 
 pub struct ResourceService {
     routes: Vec<RouteService>,
-    default: HttpService,
+    default: BoxedHttpService,
 }
 
 impl Service<ServiceRequest> for ResourceService {
diff --git a/src/responder.rs b/src/responder.rs
index 8a84be598..9d8a0e8ed 100644
--- a/src/responder.rs
+++ b/src/responder.rs
@@ -1,19 +1,21 @@
 use std::borrow::Cow;
 
 use actix_http::{
-    body::AnyBody,
+    body::{BoxBody, EitherBody, MessageBody},
     http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode},
 };
 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.
 ///
 /// Any types that implement this trait can be used in the return type of a handler.
 pub trait Responder {
+    type Body: MessageBody + 'static;
+
     /// Convert self to `HttpResponse`.
-    fn respond_to(self, req: &HttpRequest) -> HttpResponse;
+    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>;
 
     /// Override a status code for a Responder.
     ///
@@ -59,38 +61,52 @@ pub trait Responder {
 }
 
 impl Responder for HttpResponse {
+    type Body = BoxBody;
+
     #[inline]
-    fn respond_to(self, _: &HttpRequest) -> HttpResponse {
+    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
         self
     }
 }
 
-impl Responder for actix_http::Response<AnyBody> {
+impl Responder for actix_http::Response<BoxBody> {
+    type Body = BoxBody;
+
     #[inline]
-    fn respond_to(self, _: &HttpRequest) -> HttpResponse {
+    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
         HttpResponse::from(self)
     }
 }
 
 impl Responder for HttpResponseBuilder {
+    type Body = BoxBody;
+
     #[inline]
-    fn respond_to(mut self, _: &HttpRequest) -> HttpResponse {
+    fn respond_to(mut self, _: &HttpRequest) -> HttpResponse<Self::Body> {
         self.finish()
     }
 }
 
 impl Responder for actix_http::ResponseBuilder {
+    type Body = BoxBody;
+
     #[inline]
-    fn respond_to(mut self, _: &HttpRequest) -> HttpResponse {
-        HttpResponse::from(self.finish())
+    fn respond_to(mut self, req: &HttpRequest) -> HttpResponse<Self::Body> {
+        self.finish().map_into_boxed_body().respond_to(req)
     }
 }
 
-impl<T: Responder> Responder for Option<T> {
-    fn respond_to(self, req: &HttpRequest) -> HttpResponse {
+impl<T> Responder for Option<T>
+where
+    T: Responder,
+    <T::Body as MessageBody>::Error: Into<BoxError>,
+{
+    type Body = EitherBody<T::Body>;
+
+    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
         match self {
-            Some(val) => val.respond_to(req),
-            None => HttpResponse::new(StatusCode::NOT_FOUND),
+            Some(val) => val.respond_to(req).map_into_left_body(),
+            None => HttpResponse::new(StatusCode::NOT_FOUND).map_into_right_body(),
         }
     }
 }
@@ -98,47 +114,69 @@ impl<T: Responder> Responder for Option<T> {
 impl<T, E> Responder for Result<T, E>
 where
     T: Responder,
+    <T::Body as MessageBody>::Error: Into<BoxError>,
     E: Into<Error>,
 {
-    fn respond_to(self, req: &HttpRequest) -> HttpResponse {
+    type Body = EitherBody<T::Body>;
+
+    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
         match self {
-            Ok(val) => val.respond_to(req),
-            Err(e) => HttpResponse::from_error(e.into()),
+            Ok(val) => val.respond_to(req).map_into_left_body(),
+            Err(err) => HttpResponse::from_error(err.into()).map_into_right_body(),
         }
     }
 }
 
 impl<T: Responder> Responder for (T, StatusCode) {
-    fn respond_to(self, req: &HttpRequest) -> HttpResponse {
+    type Body = T::Body;
+
+    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
         let mut res = self.0.respond_to(req);
         *res.status_mut() = self.1;
         res
     }
 }
 
-macro_rules! impl_responder {
-    ($res: ty, $ct: path) => {
+macro_rules! impl_responder_by_forward_into_base_response {
+    ($res:ty, $body:ty) => {
         impl Responder for $res {
-            fn respond_to(self, _: &HttpRequest) -> HttpResponse {
-                HttpResponse::Ok().content_type($ct).body(self)
+            type Body = $body;
+
+            fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
+                let res: actix_http::Response<_> = self.into();
+                res.into()
+            }
+        }
+    };
+
+    ($res:ty) => {
+        impl_responder_by_forward_into_base_response!($res, $res);
+    };
+}
+
+impl_responder_by_forward_into_base_response!(&'static [u8]);
+impl_responder_by_forward_into_base_response!(Bytes);
+impl_responder_by_forward_into_base_response!(BytesMut);
+
+impl_responder_by_forward_into_base_response!(&'static str);
+impl_responder_by_forward_into_base_response!(String);
+
+macro_rules! impl_into_string_responder {
+    ($res:ty) => {
+        impl Responder for $res {
+            type Body = String;
+
+            fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
+                let string: String = self.into();
+                let res: actix_http::Response<_> = string.into();
+                res.into()
             }
         }
     };
 }
 
-impl_responder!(&'static str, mime::TEXT_PLAIN_UTF_8);
-
-impl_responder!(String, mime::TEXT_PLAIN_UTF_8);
-
-impl_responder!(&'_ String, mime::TEXT_PLAIN_UTF_8);
-
-impl_responder!(Cow<'_, str>, mime::TEXT_PLAIN_UTF_8);
-
-impl_responder!(&'static [u8], mime::APPLICATION_OCTET_STREAM);
-
-impl_responder!(Bytes, mime::APPLICATION_OCTET_STREAM);
-
-impl_responder!(BytesMut, mime::APPLICATION_OCTET_STREAM);
+impl_into_string_responder!(&'_ String);
+impl_into_string_responder!(Cow<'_, str>);
 
 /// Allows overriding status code and headers for a responder.
 pub struct CustomResponder<T> {
@@ -204,11 +242,17 @@ impl<T: Responder> CustomResponder<T> {
     }
 }
 
-impl<T: Responder> Responder for CustomResponder<T> {
-    fn respond_to(self, req: &HttpRequest) -> HttpResponse {
+impl<T> Responder for CustomResponder<T>
+where
+    T: Responder,
+    <T::Body as MessageBody>::Error: Into<BoxError>,
+{
+    type Body = EitherBody<T::Body>;
+
+    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
         let headers = match self.headers {
             Ok(headers) => headers,
-            Err(err) => return HttpResponse::from_error(Error::from(err)),
+            Err(err) => return HttpResponse::from_error(err).map_into_right_body(),
         };
 
         let mut res = self.responder.respond_to(req);
@@ -222,7 +266,7 @@ impl<T: Responder> Responder for CustomResponder<T> {
             res.headers_mut().insert(k, v);
         }
 
-        res
+        res.map_into_left_body()
     }
 }
 
@@ -231,11 +275,15 @@ pub(crate) mod tests {
     use actix_service::Service;
     use bytes::{Bytes, BytesMut};
 
+    use actix_http::body::to_bytes;
+
     use super::*;
-    use crate::dev::AnyBody;
-    use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
-    use crate::test::{init_service, TestRequest};
-    use crate::{error, web, App};
+    use crate::{
+        error,
+        http::{header::CONTENT_TYPE, HeaderValue, StatusCode},
+        test::{assert_body_eq, init_service, TestRequest},
+        web, App,
+    };
 
     #[actix_rt::test]
     async fn test_option_responder() {
@@ -253,112 +301,116 @@ pub(crate) mod tests {
         let req = TestRequest::with_uri("/some").to_request();
         let resp = srv.call(req).await.unwrap();
         assert_eq!(resp.status(), StatusCode::OK);
-        match resp.response().body() {
-            AnyBody::Bytes(ref b) => {
-                let bytes = b.clone();
-                assert_eq!(bytes, Bytes::from_static(b"some"));
-            }
-            _ => panic!(),
-        }
-    }
-
-    pub(crate) trait BodyTest {
-        fn bin_ref(&self) -> &[u8];
-        fn body(&self) -> &AnyBody;
-    }
-
-    impl BodyTest for AnyBody {
-        fn bin_ref(&self) -> &[u8] {
-            match self {
-                AnyBody::Bytes(ref bin) => bin,
-                _ => unreachable!("bug in test impl"),
-            }
-        }
-        fn body(&self) -> &AnyBody {
-            self
-        }
+        assert_body_eq!(resp, b"some");
     }
 
     #[actix_rt::test]
     async fn test_responder() {
         let req = TestRequest::default().to_http_request();
 
-        let resp = "test".respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
+        let res = "test".respond_to(&req);
+        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")
         );
-
-        let resp = b"test".respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
         assert_eq!(
-            resp.headers().get(CONTENT_TYPE).unwrap(),
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
+
+        let res = b"test".respond_to(&req);
+        assert_eq!(res.status(), StatusCode::OK);
+        assert_eq!(
+            res.headers().get(CONTENT_TYPE).unwrap(),
             HeaderValue::from_static("application/octet-stream")
         );
-
-        let resp = "test".to_string().respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
         assert_eq!(
-            resp.headers().get(CONTENT_TYPE).unwrap(),
-            HeaderValue::from_static("text/plain; charset=utf-8")
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
         );
 
-        let resp = (&"test".to_string()).respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
+        let res = "test".to_string().respond_to(&req);
+        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!(
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
+
+        let res = (&"test".to_string()).respond_to(&req);
+        assert_eq!(res.status(), StatusCode::OK);
+        assert_eq!(
+            res.headers().get(CONTENT_TYPE).unwrap(),
+            HeaderValue::from_static("text/plain; charset=utf-8")
+        );
+        assert_eq!(
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
 
         let s = String::from("test");
-        let resp = Cow::Borrowed(s.as_str()).respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
+        let res = Cow::Borrowed(s.as_str()).respond_to(&req);
+        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")
         );
-
-        let resp = Cow::<'_, str>::Owned(s).respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
         assert_eq!(
-            resp.headers().get(CONTENT_TYPE).unwrap(),
-            HeaderValue::from_static("text/plain; charset=utf-8")
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
         );
 
-        let resp = Cow::Borrowed("test").respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
+        let res = Cow::<'_, str>::Owned(s).respond_to(&req);
+        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")
         );
-
-        let resp = Bytes::from_static(b"test").respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
         assert_eq!(
-            resp.headers().get(CONTENT_TYPE).unwrap(),
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
+
+        let res = Cow::Borrowed("test").respond_to(&req);
+        assert_eq!(res.status(), StatusCode::OK);
+        assert_eq!(
+            res.headers().get(CONTENT_TYPE).unwrap(),
+            HeaderValue::from_static("text/plain; charset=utf-8")
+        );
+        assert_eq!(
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
+
+        let res = Bytes::from_static(b"test").respond_to(&req);
+        assert_eq!(res.status(), StatusCode::OK);
+        assert_eq!(
+            res.headers().get(CONTENT_TYPE).unwrap(),
             HeaderValue::from_static("application/octet-stream")
         );
-
-        let resp = BytesMut::from(b"test".as_ref()).respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
         assert_eq!(
-            resp.headers().get(CONTENT_TYPE).unwrap(),
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
+
+        let res = BytesMut::from(b"test".as_ref()).respond_to(&req);
+        assert_eq!(res.status(), StatusCode::OK);
+        assert_eq!(
+            res.headers().get(CONTENT_TYPE).unwrap(),
             HeaderValue::from_static("application/octet-stream")
         );
+        assert_eq!(
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
 
         // InternalError
-        let resp = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
+        let res = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req);
+        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
     }
 
     #[actix_rt::test]
@@ -368,11 +420,14 @@ pub(crate) mod tests {
         // Result<I, E>
         let resp = Ok::<_, Error>("test".to_string()).respond_to(&req);
         assert_eq!(resp.status(), StatusCode::OK);
-        assert_eq!(resp.body().bin_ref(), b"test");
         assert_eq!(
             resp.headers().get(CONTENT_TYPE).unwrap(),
             HeaderValue::from_static("text/plain; charset=utf-8")
         );
+        assert_eq!(
+            to_bytes(resp.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
 
         let res = Err::<String, _>(error::InternalError::new("err", StatusCode::BAD_REQUEST))
             .respond_to(&req);
@@ -389,7 +444,10 @@ pub(crate) mod tests {
             .respond_to(&req);
 
         assert_eq!(res.status(), StatusCode::BAD_REQUEST);
-        assert_eq!(res.body().bin_ref(), b"test");
+        assert_eq!(
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
 
         let res = "test"
             .to_string()
@@ -397,11 +455,14 @@ pub(crate) mod tests {
             .respond_to(&req);
 
         assert_eq!(res.status(), StatusCode::OK);
-        assert_eq!(res.body().bin_ref(), b"test");
         assert_eq!(
             res.headers().get(CONTENT_TYPE).unwrap(),
             HeaderValue::from_static("json")
         );
+        assert_eq!(
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
     }
 
     #[actix_rt::test]
@@ -409,17 +470,23 @@ pub(crate) mod tests {
         let req = TestRequest::default().to_http_request();
         let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
         assert_eq!(res.status(), StatusCode::BAD_REQUEST);
-        assert_eq!(res.body().bin_ref(), b"test");
+        assert_eq!(
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
 
         let req = TestRequest::default().to_http_request();
         let res = ("test".to_string(), StatusCode::OK)
             .with_header((CONTENT_TYPE, mime::APPLICATION_JSON))
             .respond_to(&req);
         assert_eq!(res.status(), StatusCode::OK);
-        assert_eq!(res.body().bin_ref(), b"test");
         assert_eq!(
             res.headers().get(CONTENT_TYPE).unwrap(),
             HeaderValue::from_static("application/json")
         );
+        assert_eq!(
+            to_bytes(res.into_body()).await.unwrap(),
+            Bytes::from_static(b"test"),
+        );
     }
 }
diff --git a/src/response/builder.rs b/src/response/builder.rs
index e61f7e16f..b5bef2e99 100644
--- a/src/response/builder.rs
+++ b/src/response/builder.rs
@@ -1,14 +1,13 @@
 use std::{
     cell::{Ref, RefMut},
     convert::TryInto,
-    error::Error as StdError,
     future::Future,
     pin::Pin,
     task::{Context, Poll},
 };
 
 use actix_http::{
-    body::{AnyBody, BodyStream},
+    body::{BodyStream, BoxBody, MessageBody},
     http::{
         header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue},
         ConnectionType, Error as HttpError, StatusCode,
@@ -26,14 +25,14 @@ use cookie::{Cookie, CookieJar};
 
 use crate::{
     error::{Error, JsonPayloadError},
-    HttpResponse,
+    BoxError, HttpResponse,
 };
 
 /// An HTTP response builder.
 ///
 /// This type can be used to construct an instance of `Response` through a builder-like pattern.
 pub struct HttpResponseBuilder {
-    res: Option<Response<AnyBody>>,
+    res: Option<Response<BoxBody>>,
     err: Option<HttpError>,
     #[cfg(feature = "cookies")]
     cookies: Option<CookieJar>,
@@ -44,7 +43,7 @@ impl HttpResponseBuilder {
     /// Create response builder
     pub fn new(status: StatusCode) -> Self {
         Self {
-            res: Some(Response::new(status)),
+            res: Some(Response::with_body(status, BoxBody::new(()))),
             err: None,
             #[cfg(feature = "cookies")]
             cookies: None,
@@ -299,7 +298,6 @@ impl HttpResponseBuilder {
     }
 
     /// Mutable reference to a the response's extensions
-    #[inline]
     pub fn extensions_mut(&mut self) -> RefMut<'_, Extensions> {
         self.res
             .as_mut()
@@ -307,18 +305,20 @@ impl HttpResponseBuilder {
             .extensions_mut()
     }
 
-    /// Set a body and generate `Response`.
+    /// Set a body and build the `HttpResponse`.
     ///
     /// `HttpResponseBuilder` can not be used after this call.
-    #[inline]
-    pub fn body<B: Into<AnyBody>>(&mut self, body: B) -> HttpResponse<AnyBody> {
-        match self.message_body(body.into()) {
-            Ok(res) => res,
+    pub fn body<B>(&mut self, body: B) -> HttpResponse<BoxBody>
+    where
+        B: MessageBody + 'static,
+    {
+        match self.message_body(body) {
+            Ok(res) => res.map_into_boxed_body(),
             Err(err) => HttpResponse::from_error(err),
         }
     }
 
-    /// Set a body and generate `Response`.
+    /// Set a body and build the `HttpResponse`.
     ///
     /// `HttpResponseBuilder` can not be used after this call.
     pub fn message_body<B>(&mut self, body: B) -> Result<HttpResponse<B>, Error> {
@@ -332,7 +332,7 @@ impl HttpResponseBuilder {
             .expect("cannot reuse response builder")
             .set_body(body);
 
-        #[allow(unused_mut)]
+        #[allow(unused_mut)] // mut is only unused when cookies are disabled
         let mut res = HttpResponse::from(res);
 
         #[cfg(feature = "cookies")]
@@ -348,19 +348,19 @@ impl HttpResponseBuilder {
         Ok(res)
     }
 
-    /// Set a streaming body and generate `Response`.
+    /// Set a streaming body and build the `HttpResponse`.
     ///
     /// `HttpResponseBuilder` can not be used after this call.
     #[inline]
     pub fn streaming<S, E>(&mut self, stream: S) -> HttpResponse
     where
         S: Stream<Item = Result<Bytes, E>> + 'static,
-        E: Into<Box<dyn StdError>> + 'static,
+        E: Into<BoxError> + 'static,
     {
-        self.body(AnyBody::new_boxed(BodyStream::new(stream)))
+        self.body(BodyStream::new(stream))
     }
 
-    /// Set a json body and generate `Response`
+    /// Set a JSON body and build the `HttpResponse`.
     ///
     /// `HttpResponseBuilder` can not be used after this call.
     pub fn json(&mut self, value: impl Serialize) -> HttpResponse {
@@ -376,18 +376,18 @@ impl HttpResponseBuilder {
                     self.insert_header((header::CONTENT_TYPE, mime::APPLICATION_JSON));
                 }
 
-                self.body(AnyBody::from(body))
+                self.body(body)
             }
             Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
         }
     }
 
-    /// Set an empty body and generate `Response`
+    /// Set an empty body and build the `HttpResponse`.
     ///
     /// `HttpResponseBuilder` can not be used after this call.
     #[inline]
     pub fn finish(&mut self) -> HttpResponse {
-        self.body(AnyBody::empty())
+        self.body(())
     }
 
     /// This method construct new `HttpResponseBuilder`
@@ -416,7 +416,7 @@ impl From<HttpResponseBuilder> for HttpResponse {
     }
 }
 
-impl From<HttpResponseBuilder> for Response<AnyBody> {
+impl From<HttpResponseBuilder> for Response<BoxBody> {
     fn from(mut builder: HttpResponseBuilder) -> Self {
         builder.finish().into()
     }
@@ -435,12 +435,9 @@ mod tests {
     use actix_http::body;
 
     use super::*;
-    use crate::{
-        dev::AnyBody,
-        http::{
-            header::{self, HeaderValue, CONTENT_TYPE},
-            StatusCode,
-        },
+    use crate::http::{
+        header::{self, HeaderValue, CONTENT_TYPE},
+        StatusCode,
     };
 
     #[test]
@@ -475,7 +472,7 @@ mod tests {
     fn test_content_type() {
         let resp = HttpResponseBuilder::new(StatusCode::OK)
             .content_type("text/plain")
-            .body(AnyBody::empty());
+            .body(Bytes::new());
         assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
     }
 
diff --git a/src/response/response.rs b/src/response/response.rs
index 23562ab0e..97de21e42 100644
--- a/src/response/response.rs
+++ b/src/response/response.rs
@@ -8,7 +8,7 @@ use std::{
 };
 
 use actix_http::{
-    body::{AnyBody, MessageBody},
+    body::{BoxBody, EitherBody, MessageBody},
     http::{header::HeaderMap, StatusCode},
     Extensions, Response, ResponseHead,
 };
@@ -25,12 +25,12 @@ use {
 use crate::{error::Error, HttpResponseBuilder};
 
 /// An outgoing response.
-pub struct HttpResponse<B = AnyBody> {
+pub struct HttpResponse<B = BoxBody> {
     res: Response<B>,
     pub(crate) error: Option<Error>,
 }
 
-impl HttpResponse<AnyBody> {
+impl HttpResponse<BoxBody> {
     /// Constructs a response.
     #[inline]
     pub fn new(status: StatusCode) -> Self {
@@ -227,8 +227,26 @@ impl<B> HttpResponse<B> {
         }
     }
 
-    // TODO: into_body equivalent
-    // TODO: into_boxed_body
+    // TODO: docs for the body map methods below
+
+    #[inline]
+    pub fn map_into_left_body<R>(self) -> HttpResponse<EitherBody<B, R>> {
+        self.map_body(|_, body| EitherBody::left(body))
+    }
+
+    #[inline]
+    pub fn map_into_right_body<L>(self) -> HttpResponse<EitherBody<L, B>> {
+        self.map_body(|_, body| EitherBody::right(body))
+    }
+
+    #[inline]
+    pub fn map_into_boxed_body(self) -> HttpResponse<BoxBody>
+    where
+        B: MessageBody + 'static,
+    {
+        // TODO: avoid double boxing with down-casting, if it improves perf
+        self.map_body(|_, body| BoxBody::new(body))
+    }
 
     /// Extract response body
     pub fn into_body(self) -> B {
@@ -273,14 +291,14 @@ impl<B> From<HttpResponse<B>> for Response<B> {
     }
 }
 
-// Future is only implemented for AnyBody payload type because it's the most useful for making
+// Future is only implemented for BoxBody payload type because it's the most useful for making
 // simple handlers without async blocks. Making it generic over all MessageBody types requires a
 // future impl on Response which would cause it's body field to be, undesirably, Option<B>.
 //
 // This impl is not particularly efficient due to the Response construction and should probably
 // not be invoked if performance is important. Prefer an async fn/block in such cases.
-impl Future for HttpResponse<AnyBody> {
-    type Output = Result<Response<AnyBody>, Error>;
+impl Future for HttpResponse<BoxBody> {
+    type Output = Result<Response<BoxBody>, Error>;
 
     fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
         if let Some(err) = self.error.take() {
diff --git a/src/route.rs b/src/route.rs
index 0c0699430..1eb323068 100644
--- a/src/route.rs
+++ b/src/route.rs
@@ -1,19 +1,18 @@
-#![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`)
-
-use std::{future::Future, 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;
 
 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
@@ -21,7 +20,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>>>,
 }
 
@@ -30,13 +29,15 @@ impl Route {
     #[allow(clippy::new_without_default)]
     pub fn new() -> Route {
         Route {
-            service: handler_service(HttpResponse::NotFound),
+            service: boxed::factory(fn_service(|req: ServiceRequest| async {
+                Ok(req.into_response(HttpResponse::NotFound()))
+            })),
             guards: Rc::new(Vec::new()),
         }
     }
 
     pub(crate) fn take_guards(&mut self) -> Vec<Box<dyn Guard>> {
-        std::mem::take(Rc::get_mut(&mut self.guards).unwrap())
+        mem::take(Rc::get_mut(&mut self.guards).unwrap())
     }
 }
 
@@ -181,6 +182,8 @@ impl Route {
         T: FromRequest + 'static,
         R: Future + 'static,
         R::Output: Responder + 'static,
+        <R::Output as Responder>::Body: MessageBody,
+        <<R::Output as Responder>::Body as MessageBody>::Error: Into<BoxError>,
     {
         self.service = handler_service(handler);
         self
diff --git a/src/scope.rs b/src/scope.rs
index c20b5d7c8..ff013671b 100644
--- a/src/scope.rs
+++ b/src/scope.rs
@@ -3,9 +3,8 @@ use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc};
 use actix_http::Extensions;
 use actix_router::{ResourceDef, Router};
 use actix_service::{
-    apply, apply_fn_factory,
-    boxed::{self, BoxService, BoxServiceFactory},
-    IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform,
+    apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory,
+    ServiceFactoryExt, Transform,
 };
 use futures_core::future::LocalBoxFuture;
 use futures_util::future::join_all;
@@ -13,16 +12,17 @@ use futures_util::future::join_all;
 use crate::{
     config::ServiceConfig,
     data::Data,
-    dev::{AppService, HttpServiceFactory},
+    dev::AppService,
     guard::Guard,
     rmap::ResourceMap,
-    service::{AppServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse},
+    service::{
+        AppServiceFactory, BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory,
+        ServiceFactoryWrapper, ServiceRequest, ServiceResponse,
+    },
     Error, Resource, Route,
 };
 
 type Guards = Vec<Box<dyn Guard>>;
-type HttpService = BoxService<ServiceRequest, ServiceResponse, Error>;
-type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>;
 
 /// Resources scope.
 ///
@@ -58,7 +58,7 @@ pub struct Scope<T = ScopeEndpoint> {
     app_data: Option<Extensions>,
     services: Vec<Box<dyn AppServiceFactory>>,
     guards: Vec<Box<dyn Guard>>,
-    default: Option<Rc<HttpNewService>>,
+    default: Option<Rc<BoxedHttpServiceFactory>>,
     external: Vec<ResourceDef>,
     factory_ref: Rc<RefCell<Option<ScopeFactory>>>,
 }
@@ -470,8 +470,14 @@ where
 }
 
 pub struct ScopeFactory {
-    services: Rc<[(ResourceDef, HttpNewService, RefCell<Option<Guards>>)]>,
-    default: Rc<HttpNewService>,
+    services: Rc<
+        [(
+            ResourceDef,
+            BoxedHttpServiceFactory,
+            RefCell<Option<Guards>>,
+        )],
+    >,
+    default: Rc<BoxedHttpServiceFactory>,
 }
 
 impl ServiceFactory<ServiceRequest> for ScopeFactory {
@@ -518,8 +524,8 @@ impl ServiceFactory<ServiceRequest> for ScopeFactory {
 }
 
 pub struct ScopeService {
-    router: Router<HttpService, Vec<Box<dyn Guard>>>,
-    default: HttpService,
+    router: Router<BoxedHttpService, Vec<Box<dyn Guard>>>,
+    default: BoxedHttpService,
 }
 
 impl Service<ServiceRequest> for ScopeService {
@@ -580,12 +586,11 @@ mod tests {
     use bytes::Bytes;
 
     use crate::{
-        dev::AnyBody,
         guard,
         http::{header, HeaderValue, Method, StatusCode},
         middleware::DefaultHeaders,
         service::{ServiceRequest, ServiceResponse},
-        test::{call_service, init_service, read_body, TestRequest},
+        test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
         web, App, HttpMessage, HttpRequest, HttpResponse,
     };
 
@@ -748,20 +753,13 @@ mod tests {
         .await;
 
         let req = TestRequest::with_uri("/ab-project1/path1").to_request();
-        let resp = srv.call(req).await.unwrap();
-        assert_eq!(resp.status(), StatusCode::OK);
-
-        match resp.response().body() {
-            AnyBody::Bytes(ref b) => {
-                let bytes = b.clone();
-                assert_eq!(bytes, Bytes::from_static(b"project: project1"));
-            }
-            _ => panic!(),
-        }
+        let res = srv.call(req).await.unwrap();
+        assert_eq!(res.status(), StatusCode::OK);
+        assert_body_eq!(res, b"project: project1");
 
         let req = TestRequest::with_uri("/aa-project1/path1").to_request();
-        let resp = srv.call(req).await.unwrap();
-        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+        let res = srv.call(req).await.unwrap();
+        assert_eq!(res.status(), StatusCode::NOT_FOUND);
     }
 
     #[actix_rt::test]
@@ -849,16 +847,9 @@ mod tests {
         .await;
 
         let req = TestRequest::with_uri("/app/project_1/path1").to_request();
-        let resp = srv.call(req).await.unwrap();
-        assert_eq!(resp.status(), StatusCode::CREATED);
-
-        match resp.response().body() {
-            AnyBody::Bytes(ref b) => {
-                let bytes = b.clone();
-                assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
-            }
-            _ => panic!(),
-        }
+        let res = srv.call(req).await.unwrap();
+        assert_eq!(res.status(), StatusCode::CREATED);
+        assert_body_eq!(res, b"project: project_1");
     }
 
     #[actix_rt::test]
@@ -877,20 +868,13 @@ mod tests {
         .await;
 
         let req = TestRequest::with_uri("/app/test/1/path1").to_request();
-        let resp = srv.call(req).await.unwrap();
-        assert_eq!(resp.status(), StatusCode::CREATED);
-
-        match resp.response().body() {
-            AnyBody::Bytes(ref b) => {
-                let bytes = b.clone();
-                assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
-            }
-            _ => panic!(),
-        }
+        let res = srv.call(req).await.unwrap();
+        assert_eq!(res.status(), StatusCode::CREATED);
+        assert_body_eq!(res, b"project: test - 1");
 
         let req = TestRequest::with_uri("/app/test/1/path2").to_request();
-        let resp = srv.call(req).await.unwrap();
-        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+        let res = srv.call(req).await.unwrap();
+        assert_eq!(res.status(), StatusCode::NOT_FOUND);
     }
 
     #[actix_rt::test]
diff --git a/src/server.rs b/src/server.rs
index 1bf56655b..3db849410 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -1,8 +1,6 @@
 use std::{
     any::Any,
-    cmp,
-    error::Error as StdError,
-    fmt, io,
+    cmp, fmt, io,
     marker::PhantomData,
     net,
     sync::{Arc, Mutex},
@@ -75,15 +73,13 @@ where
     I: IntoServiceFactory<S, Request>,
 
     S: ServiceFactory<Request, Config = AppConfig> + 'static,
-    // S::Future: 'static,
     S::Error: Into<Error> + 'static,
     S::InitError: fmt::Debug,
     S::Response: Into<Response<B>> + 'static,
     <S::Service as Service<Request>>::Future: 'static,
     S::Service: 'static,
-    // S::Service: 'static,
+
     B: MessageBody + 'static,
-    B::Error: Into<Box<dyn StdError>>,
 {
     /// Create new HTTP server with application factory
     pub fn new(factory: F) -> Self {
@@ -656,8 +652,8 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result<net::T
     Ok(net::TcpListener::from(socket))
 }
 
-#[cfg(feature = "openssl")]
 /// Configure `SslAcceptorBuilder` with custom server flags.
+#[cfg(feature = "openssl")]
 fn openssl_acceptor(mut builder: SslAcceptorBuilder) -> io::Result<SslAcceptor> {
     builder.set_alpn_select_callback(|_, protocols| {
         const H2: &[u8] = b"\x02h2";
diff --git a/src/service.rs b/src/service.rs
index 8ba38df43..df9e809e4 100644
--- a/src/service.rs
+++ b/src/service.rs
@@ -1,14 +1,19 @@
-use std::cell::{Ref, RefMut};
-use std::rc::Rc;
-use std::{fmt, net};
+use std::{
+    cell::{Ref, RefMut},
+    fmt, net,
+    rc::Rc,
+};
 
 use actix_http::{
-    body::{AnyBody, MessageBody},
+    body::{BoxBody, EitherBody, MessageBody},
     http::{HeaderMap, Method, StatusCode, Uri, Version},
     Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead,
 };
 use actix_router::{IntoPatterns, Path, Patterns, Resource, ResourceDef, Url};
-use actix_service::{IntoServiceFactory, ServiceFactory};
+use actix_service::{
+    boxed::{BoxService, BoxServiceFactory},
+    IntoServiceFactory, ServiceFactory,
+};
 #[cfg(feature = "cookies")]
 use cookie::{Cookie, ParseError as CookieParseError};
 
@@ -21,6 +26,10 @@ use crate::{
     Error, HttpRequest, HttpResponse,
 };
 
+pub(crate) type BoxedHttpService = BoxService<ServiceRequest, ServiceResponse<BoxBody>, Error>;
+pub(crate) type BoxedHttpServiceFactory =
+    BoxServiceFactory<(), ServiceRequest, ServiceResponse<BoxBody>, Error, ()>;
+
 pub trait HttpServiceFactory {
     fn register(self, config: &mut AppService);
 }
@@ -326,12 +335,12 @@ impl fmt::Debug for ServiceRequest {
 }
 
 /// A service level response wrapper.
-pub struct ServiceResponse<B = AnyBody> {
+pub struct ServiceResponse<B = BoxBody> {
     request: HttpRequest,
     response: HttpResponse<B>,
 }
 
-impl ServiceResponse<AnyBody> {
+impl ServiceResponse<BoxBody> {
     /// Create service response from the error
     pub fn from_err<E: Into<Error>>(err: E, request: HttpRequest) -> Self {
         let response = HttpResponse::from_error(err);
@@ -401,6 +410,7 @@ impl<B> ServiceResponse<B> {
 
 impl<B> ServiceResponse<B> {
     /// Set a new body
+    #[inline]
     pub fn map_body<F, B2>(self, f: F) -> ServiceResponse<B2>
     where
         F: FnOnce(&mut ResponseHead, B) -> B2,
@@ -412,6 +422,24 @@ impl<B> ServiceResponse<B> {
             request: self.request,
         }
     }
+
+    #[inline]
+    pub fn map_into_left_body<R>(self) -> ServiceResponse<EitherBody<B, R>> {
+        self.map_body(|_, body| EitherBody::left(body))
+    }
+
+    #[inline]
+    pub fn map_into_right_body<L>(self) -> ServiceResponse<EitherBody<L, B>> {
+        self.map_body(|_, body| EitherBody::right(body))
+    }
+
+    #[inline]
+    pub fn map_into_boxed_body(self) -> ServiceResponse<BoxBody>
+    where
+        B: MessageBody + 'static,
+    {
+        self.map_body(|_, body| BoxBody::new(body))
+    }
 }
 
 impl<B> From<ServiceResponse<B>> for HttpResponse<B> {
diff --git a/src/test.rs b/src/test.rs
index 77765e267..2cd01039d 100644
--- a/src/test.rs
+++ b/src/test.rs
@@ -4,7 +4,6 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc};
 
 pub use actix_http::test::TestBuffer;
 use actix_http::{
-    body,
     http::{header::IntoHeaderPair, Method, StatusCode, Uri, Version},
     test::TestRequest as HttpTestRequest,
     Extensions, Request,
@@ -20,9 +19,10 @@ use serde::{de::DeserializeOwned, Serialize};
 use crate::cookie::{Cookie, CookieJar};
 use crate::{
     app_service::AppInitServiceState,
+    body::{self, BoxBody, MessageBody},
     config::AppConfig,
     data::Data,
-    dev::{AnyBody, MessageBody, Payload},
+    dev::Payload,
     http::header::ContentType,
     rmap::ResourceMap,
     service::{ServiceRequest, ServiceResponse},
@@ -32,14 +32,14 @@ use crate::{
 
 /// Create service that always responds with `HttpResponse::Ok()` and no body.
 pub fn ok_service(
-) -> impl Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error> {
+) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
     default_service(StatusCode::OK)
 }
 
 /// Create service that always responds with given status code and no body.
 pub fn default_service(
     status_code: StatusCode,
-) -> impl Service<ServiceRequest, Response = ServiceResponse<AnyBody>, Error = Error> {
+) -> impl Service<ServiceRequest, Response = ServiceResponse<BoxBody>, Error = Error> {
     (move |req: ServiceRequest| {
         ok(req.into_response(HttpResponseBuilder::new(status_code).finish()))
     })
@@ -632,6 +632,22 @@ impl TestRequest {
     }
 }
 
+/// Reduces boilerplate code when testing expected response payloads.
+#[cfg(test)]
+macro_rules! assert_body_eq {
+    ($res:ident, $expected:expr) => {
+        assert_eq!(
+            ::actix_http::body::to_bytes($res.into_body())
+                .await
+                .expect("body read should have succeeded"),
+            Bytes::from_static($expected),
+        )
+    };
+}
+
+#[cfg(test)]
+pub(crate) use assert_body_eq;
+
 #[cfg(test)]
 mod tests {
     use std::time::SystemTime;
diff --git a/src/types/either.rs b/src/types/either.rs
index 0a8a90133..3c759736e 100644
--- a/src/types/either.rs
+++ b/src/types/either.rs
@@ -12,7 +12,7 @@ use futures_core::ready;
 use pin_project_lite::pin_project;
 
 use crate::{
-    dev,
+    body, dev,
     web::{Form, Json},
     Error, FromRequest, HttpRequest, HttpResponse, Responder,
 };
@@ -146,10 +146,12 @@ where
     L: Responder,
     R: Responder,
 {
-    fn respond_to(self, req: &HttpRequest) -> HttpResponse {
+    type Body = body::EitherBody<L::Body, R::Body>;
+
+    fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
         match self {
-            Either::Left(a) => a.respond_to(req),
-            Either::Right(b) => b.respond_to(req),
+            Either::Left(a) => a.respond_to(req).map_into_left_body(),
+            Either::Right(b) => b.respond_to(req).map_into_right_body(),
         }
     }
 }
diff --git a/src/types/form.rs b/src/types/form.rs
index 098a864de..9c09c6b73 100644
--- a/src/types/form.rs
+++ b/src/types/form.rs
@@ -20,8 +20,9 @@ use serde::{de::DeserializeOwned, Serialize};
 #[cfg(feature = "__compress")]
 use crate::dev::Decompress;
 use crate::{
-    error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, Error,
-    HttpMessage, HttpRequest, HttpResponse, Responder,
+    body::EitherBody, error::UrlencodedError, extract::FromRequest,
+    http::header::CONTENT_LENGTH, web, Error, HttpMessage, HttpRequest, HttpResponse,
+    Responder,
 };
 
 /// URL encoded payload extractor and responder.
@@ -180,12 +181,21 @@ impl<T: fmt::Display> fmt::Display for Form<T> {
 
 /// See [here](#responder) for example of usage as a handler return type.
 impl<T: Serialize> Responder for Form<T> {
-    fn respond_to(self, _: &HttpRequest) -> HttpResponse {
+    type Body = EitherBody<String>;
+
+    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
         match serde_urlencoded::to_string(&self.0) {
-            Ok(body) => HttpResponse::Ok()
+            Ok(body) => match HttpResponse::Ok()
                 .content_type(mime::APPLICATION_WWW_FORM_URLENCODED)
-                .body(body),
-            Err(err) => HttpResponse::from_error(UrlencodedError::Serialize(err)),
+                .message_body(body)
+            {
+                Ok(res) => res.map_into_left_body(),
+                Err(err) => HttpResponse::from_error(err).map_into_right_body(),
+            },
+
+            Err(err) => {
+                HttpResponse::from_error(UrlencodedError::Serialize(err)).map_into_right_body()
+            }
         }
     }
 }
@@ -408,11 +418,14 @@ mod tests {
     use serde::{Deserialize, Serialize};
 
     use super::*;
-    use crate::http::{
-        header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
-        StatusCode,
-    };
     use crate::test::TestRequest;
+    use crate::{
+        http::{
+            header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
+            StatusCode,
+        },
+        test::assert_body_eq,
+    };
 
     #[derive(Deserialize, Serialize, Debug, PartialEq)]
     struct Info {
@@ -520,15 +533,13 @@ mod tests {
             hello: "world".to_string(),
             counter: 123,
         });
-        let resp = form.respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
+        let res = form.respond_to(&req);
+        assert_eq!(res.status(), StatusCode::OK);
         assert_eq!(
-            resp.headers().get(CONTENT_TYPE).unwrap(),
+            res.headers().get(CONTENT_TYPE).unwrap(),
             HeaderValue::from_static("application/x-www-form-urlencoded")
         );
-
-        use crate::responder::tests::BodyTest;
-        assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123");
+        assert_body_eq!(res, b"hello=world&counter=123");
     }
 
     #[actix_rt::test]
diff --git a/src/types/json.rs b/src/types/json.rs
index 6d07fe45a..2b4d220e2 100644
--- a/src/types/json.rs
+++ b/src/types/json.rs
@@ -19,6 +19,7 @@ use actix_http::Payload;
 #[cfg(feature = "__compress")]
 use crate::dev::Decompress;
 use crate::{
+    body::EitherBody,
     error::{Error, JsonPayloadError},
     extract::FromRequest,
     http::header::CONTENT_LENGTH,
@@ -116,12 +117,21 @@ impl<T: Serialize> Serialize for Json<T> {
 ///
 /// If serialization failed
 impl<T: Serialize> Responder for Json<T> {
-    fn respond_to(self, _: &HttpRequest) -> HttpResponse {
+    type Body = EitherBody<String>;
+
+    fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
         match serde_json::to_string(&self.0) {
-            Ok(body) => HttpResponse::Ok()
+            Ok(body) => match HttpResponse::Ok()
                 .content_type(mime::APPLICATION_JSON)
-                .body(body),
-            Err(err) => HttpResponse::from_error(JsonPayloadError::Serialize(err)),
+                .message_body(body)
+            {
+                Ok(res) => res.map_into_left_body(),
+                Err(err) => HttpResponse::from_error(err).map_into_right_body(),
+            },
+
+            Err(err) => {
+                HttpResponse::from_error(JsonPayloadError::Serialize(err)).map_into_right_body()
+            }
         }
     }
 }
@@ -444,7 +454,7 @@ mod tests {
             header::{self, CONTENT_LENGTH, CONTENT_TYPE},
             StatusCode,
         },
-        test::{load_body, TestRequest},
+        test::{assert_body_eq, load_body, TestRequest},
     };
 
     #[derive(Serialize, Deserialize, PartialEq, Debug)]
@@ -472,15 +482,13 @@ mod tests {
         let j = Json(MyObject {
             name: "test".to_string(),
         });
-        let resp = j.respond_to(&req);
-        assert_eq!(resp.status(), StatusCode::OK);
+        let res = j.respond_to(&req);
+        assert_eq!(res.status(), StatusCode::OK);
         assert_eq!(
-            resp.headers().get(header::CONTENT_TYPE).unwrap(),
+            res.headers().get(header::CONTENT_TYPE).unwrap(),
             header::HeaderValue::from_static("application/json")
         );
-
-        use crate::responder::tests::BodyTest;
-        assert_eq!(resp.body().bin_ref(), b"{\"name\":\"test\"}");
+        assert_body_eq!(res, b"{\"name\":\"test\"}");
     }
 
     #[actix_rt::test]
diff --git a/src/types/path.rs b/src/types/path.rs
index cd24deb81..4b60d27c0 100644
--- a/src/types/path.rs
+++ b/src/types/path.rs
@@ -90,7 +90,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
     }
 }
 
-/// See [here](#usage) for example of usage as an extractor.
+/// See [here](#Examples) for example of usage as an extractor.
 impl<T> FromRequest for Path<T>
 where
     T: de::DeserializeOwned,
diff --git a/src/types/payload.rs b/src/types/payload.rs
index 00047e8b1..73987def5 100644
--- a/src/types/payload.rs
+++ b/src/types/payload.rs
@@ -43,12 +43,12 @@ use crate::{
 ///     Ok(format!("Request Body Bytes:\n{:?}", bytes))
 /// }
 /// ```
-pub struct Payload(crate::dev::Payload);
+pub struct Payload(dev::Payload);
 
 impl Payload {
     /// Unwrap to inner Payload type.
     #[inline]
-    pub fn into_inner(self) -> crate::dev::Payload {
+    pub fn into_inner(self) -> dev::Payload {
         self.0
     }
 }
@@ -62,7 +62,7 @@ impl Stream for Payload {
     }
 }
 
-/// See [here](#usage) for example of usage as an extractor.
+/// See [here](#Examples) for example of usage as an extractor.
 impl FromRequest for Payload {
     type Error = Error;
     type Future = Ready<Result<Payload, Error>>;
diff --git a/src/types/query.rs b/src/types/query.rs
index ba2034bfc..9fac21173 100644
--- a/src/types/query.rs
+++ b/src/types/query.rs
@@ -105,7 +105,7 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
     }
 }
 
-/// See [here](#usage) for example of usage as an extractor.
+/// See [here](#Examples) for example of usage as an extractor.
 impl<T: DeserializeOwned> FromRequest for Query<T> {
     type Error = Error;
     type Future = Ready<Result<Self, Error>>;
diff --git a/src/web.rs b/src/web.rs
index e9f5c8518..b58adc2f8 100644
--- a/src/web.rs
+++ b/src/web.rs
@@ -1,14 +1,14 @@
 //! Essentials helper functions and types for application registration.
 
-use std::future::Future;
+use std::{error::Error as StdError, future::Future};
 
 use actix_http::http::Method;
 use actix_router::IntoPatterns;
 pub use bytes::{Buf, BufMut, Bytes, BytesMut};
 
 use crate::{
-    error::BlockingError, extract::FromRequest, handler::Handler, resource::Resource,
-    responder::Responder, route::Route, scope::Scope, service::WebService,
+    body::MessageBody, error::BlockingError, extract::FromRequest, handler::Handler,
+    resource::Resource, responder::Responder, route::Route, scope::Scope, service::WebService,
 };
 
 pub use crate::config::ServiceConfig;
@@ -145,6 +145,8 @@ where
     I: FromRequest + 'static,
     R: Future + 'static,
     R::Output: Responder + 'static,
+    <R::Output as Responder>::Body: MessageBody + 'static,
+    <<R::Output as Responder>::Body as MessageBody>::Error: Into<Box<dyn StdError + 'static>>,
 {
     Route::new().to(handler)
 }
diff --git a/tests/test_server.rs b/tests/test_server.rs
index 3f0fbfccc..a850f228d 100644
--- a/tests/test_server.rs
+++ b/tests/test_server.rs
@@ -200,13 +200,10 @@ async fn test_body_encoding_override() {
                     .body(STR)
             })))
             .service(web::resource("/raw").route(web::to(|| {
-                let body = actix_web::dev::AnyBody::Bytes(STR.into());
                 let mut response =
-                    HttpResponse::with_body(actix_web::http::StatusCode::OK, body);
-
+                    HttpResponse::with_body(actix_web::http::StatusCode::OK, STR);
                 response.encoding(ContentEncoding::Deflate);
-
-                response
+                response.map_into_boxed_body()
             })))
     });