From 4df1cd78b7534cba98aaa930e3a6aaca2ca36ea3 Mon Sep 17 00:00:00 2001
From: Rob Ede <robjtede@icloud.com>
Date: Tue, 16 Nov 2021 09:21:10 +0000
Subject: [PATCH] simplify `AnyBody` and `BodySize` (#2446)

---
 actix-http/CHANGES.md               | 11 +++++++++++
 actix-http/src/body/body.rs         | 24 +++++++++++-------------
 actix-http/src/body/message_body.rs |  2 +-
 actix-http/src/body/mod.rs          | 14 +++++++-------
 actix-http/src/body/size.rs         | 12 ++++--------
 actix-http/src/encoding/encoder.rs  |  3 +--
 actix-http/src/h1/dispatcher.rs     |  8 ++++----
 actix-http/src/h1/encoder.rs        | 15 ++++++---------
 actix-http/src/h2/dispatcher.rs     |  4 +++-
 actix-http/src/response.rs          |  2 +-
 actix-http/src/response_builder.rs  |  4 ++--
 awc/src/client/h1proto.rs           |  4 ++--
 awc/src/client/h2proto.rs           | 18 +++++++++---------
 awc/src/middleware/redirect.rs      |  2 +-
 awc/src/sender.rs                   |  2 +-
 src/response/builder.rs             |  4 ++--
 src/response/http_codes.rs          |  3 +--
 17 files changed, 67 insertions(+), 65 deletions(-)

diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md
index 9ec75b4b..2beda3dc 100644
--- a/actix-http/CHANGES.md
+++ b/actix-http/CHANGES.md
@@ -1,6 +1,17 @@
 # Changes
 
 ## Unreleased - 2021-xx-xx
+### Added
+* `AnyBody::empty` for quickly creating an empty body. [#2446]
+
+### Changed
+* Rename `AnyBody::{Message => Stream}`. [#2446]
+
+### Removed
+* `AnyBody::Empty`; an empty body can now only be represented as a zero-length `Bytes` variant. [#2446]
+* `BodySize::Empty`; an empty body can now only be represented as a `Sized(0)` variant. [#2446]
+
+[#2446]: https://github.com/actix/actix-web/pull/2446
 
 
 ## 3.0.0-beta.12 - 2021-11-15
diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs
index cd3e4c5c..32b46448 100644
--- a/actix-http/src/body/body.rs
+++ b/actix-http/src/body/body.rs
@@ -20,17 +20,19 @@ pub enum AnyBody {
     /// Empty response. `Content-Length` header is not set.
     None,
 
-    /// Zero sized response body. `Content-Length` header is set to `0`.
-    Empty,
-
     /// Specific response body.
     Bytes(Bytes),
 
     /// Generic message body.
-    Message(BoxAnyBody),
+    Stream(BoxAnyBody),
 }
 
 impl AnyBody {
+    /// Constructs a new, empty body.
+    pub fn empty() -> Self {
+        Self::Bytes(Bytes::new())
+    }
+
     /// Create body from slice (copy)
     pub fn from_slice(s: &[u8]) -> Self {
         Self::Bytes(Bytes::copy_from_slice(s))
@@ -42,7 +44,7 @@ impl AnyBody {
         B: MessageBody + 'static,
         B::Error: Into<Box<dyn StdError + 'static>>,
     {
-        Self::Message(BoxAnyBody::from_body(body))
+        Self::Stream(BoxAnyBody::from_body(body))
     }
 }
 
@@ -52,9 +54,8 @@ impl MessageBody for AnyBody {
     fn size(&self) -> BodySize {
         match self {
             AnyBody::None => BodySize::None,
-            AnyBody::Empty => BodySize::Empty,
             AnyBody::Bytes(ref bin) => BodySize::Sized(bin.len() as u64),
-            AnyBody::Message(ref body) => body.size(),
+            AnyBody::Stream(ref body) => body.size(),
         }
     }
 
@@ -64,7 +65,6 @@ impl MessageBody for AnyBody {
     ) -> Poll<Option<Result<Bytes, Self::Error>>> {
         match self.get_mut() {
             AnyBody::None => Poll::Ready(None),
-            AnyBody::Empty => Poll::Ready(None),
             AnyBody::Bytes(ref mut bin) => {
                 let len = bin.len();
                 if len == 0 {
@@ -74,7 +74,7 @@ impl MessageBody for AnyBody {
                 }
             }
 
-            AnyBody::Message(body) => body
+            AnyBody::Stream(body) => body
                 .as_pin_mut()
                 .poll_next(cx)
                 .map_err(|err| Error::new_body().with_cause(err)),
@@ -86,12 +86,11 @@ impl PartialEq for AnyBody {
     fn eq(&self, other: &Body) -> bool {
         match *self {
             AnyBody::None => matches!(*other, AnyBody::None),
-            AnyBody::Empty => matches!(*other, AnyBody::Empty),
             AnyBody::Bytes(ref b) => match *other {
                 AnyBody::Bytes(ref b2) => b == b2,
                 _ => false,
             },
-            AnyBody::Message(_) => false,
+            AnyBody::Stream(_) => false,
         }
     }
 }
@@ -100,9 +99,8 @@ impl fmt::Debug for AnyBody {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match *self {
             AnyBody::None => write!(f, "AnyBody::None"),
-            AnyBody::Empty => write!(f, "AnyBody::Empty"),
             AnyBody::Bytes(ref b) => write!(f, "AnyBody::Bytes({:?})", b),
-            AnyBody::Message(_) => write!(f, "AnyBody::Message(_)"),
+            AnyBody::Stream(_) => write!(f, "AnyBody::Message(_)"),
         }
     }
 }
diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs
index edb4c550..62a7e9b1 100644
--- a/actix-http/src/body/message_body.rs
+++ b/actix-http/src/body/message_body.rs
@@ -31,7 +31,7 @@ impl MessageBody for () {
     type Error = Infallible;
 
     fn size(&self) -> BodySize {
-        BodySize::Empty
+        BodySize::Sized(0)
     }
 
     fn poll_next(
diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs
index a60a8895..0d5b0f07 100644
--- a/actix-http/src/body/mod.rs
+++ b/actix-http/src/body/mod.rs
@@ -33,7 +33,7 @@ pub use self::sized_stream::SizedStream;
 /// use bytes::Bytes;
 ///
 /// # async fn test_to_bytes() {
-/// let body = Body::Empty;
+/// let body = Body::None;
 /// let bytes = to_bytes(body).await.unwrap();
 /// assert!(bytes.is_empty());
 ///
@@ -44,8 +44,9 @@ pub use self::sized_stream::SizedStream;
 /// ```
 pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
     let cap = match body.size() {
-        BodySize::None | BodySize::Empty | BodySize::Sized(0) => return Ok(Bytes::new()),
+        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,
     };
 
@@ -184,7 +185,7 @@ mod tests {
 
     #[actix_rt::test]
     async fn test_unit() {
-        assert_eq!(().size(), BodySize::Empty);
+        assert_eq!(().size(), BodySize::Sized(0));
         assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx))
             .await
             .is_none());
@@ -194,11 +195,11 @@ mod tests {
     async fn test_box_and_pin() {
         let val = Box::new(());
         pin!(val);
-        assert_eq!(val.size(), BodySize::Empty);
+        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::Empty);
+        assert_eq!(val.size(), BodySize::Sized(0));
         assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none());
     }
 
@@ -214,7 +215,6 @@ mod tests {
     #[actix_rt::test]
     async fn test_body_debug() {
         assert!(format!("{:?}", Body::None).contains("Body::None"));
-        assert!(format!("{:?}", Body::Empty).contains("Body::Empty"));
         assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains('1'));
     }
 
@@ -252,7 +252,7 @@ mod tests {
 
     #[actix_rt::test]
     async fn test_to_bytes() {
-        let body = Body::Empty;
+        let body = Body::empty();
         let bytes = to_bytes(body).await.unwrap();
         assert!(bytes.is_empty());
 
diff --git a/actix-http/src/body/size.rs b/actix-http/src/body/size.rs
index 775d5b8f..e238eada 100644
--- a/actix-http/src/body/size.rs
+++ b/actix-http/src/body/size.rs
@@ -6,14 +6,9 @@ pub enum BodySize {
     /// Will skip writing Content-Length header.
     None,
 
-    /// Zero size body.
-    ///
-    /// Will write `Content-Length: 0` header.
-    Empty,
-
     /// Known size body.
     ///
-    /// Will write `Content-Length: N` header. `Sized(0)` is treated the same as `Empty`.
+    /// Will write `Content-Length: N` header.
     Sized(u64),
 
     /// Unknown size body.
@@ -25,16 +20,17 @@ pub enum BodySize {
 impl BodySize {
     /// Returns true if size hint indicates no or empty body.
     ///
+    /// Streams will return false because it cannot be known without reading the stream.
+    ///
     /// ```
     /// # use actix_http::body::BodySize;
     /// assert!(BodySize::None.is_eof());
-    /// assert!(BodySize::Empty.is_eof());
     /// assert!(BodySize::Sized(0).is_eof());
     ///
     /// assert!(!BodySize::Sized(64).is_eof());
     /// assert!(!BodySize::Stream.is_eof());
     /// ```
     pub fn is_eof(&self) -> bool {
-        matches!(self, BodySize::None | BodySize::Empty | BodySize::Sized(0))
+        matches!(self, BodySize::None | BodySize::Sized(0))
     }
 }
diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs
index abd8cedb..6cb034b7 100644
--- a/actix-http/src/encoding/encoder.rs
+++ b/actix-http/src/encoding/encoder.rs
@@ -61,7 +61,6 @@ impl<B: MessageBody> Encoder<B> {
         let body = match body {
             ResponseBody::Other(b) => match b {
                 Body::None => return ResponseBody::Other(Body::None),
-                Body::Empty => return ResponseBody::Other(Body::Empty),
                 Body::Bytes(buf) => {
                     if can_encode {
                         EncoderBody::Bytes(buf)
@@ -69,7 +68,7 @@ impl<B: MessageBody> Encoder<B> {
                         return ResponseBody::Other(Body::Bytes(buf));
                     }
                 }
-                Body::Message(stream) => EncoderBody::BoxedStream(stream),
+                Body::Stream(stream) => EncoderBody::BoxedStream(stream),
             },
             ResponseBody::Body(stream) => EncoderBody::Stream(stream),
         };
diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs
index 69530ed1..844bc61e 100644
--- a/actix-http/src/h1/dispatcher.rs
+++ b/actix-http/src/h1/dispatcher.rs
@@ -325,7 +325,7 @@ where
     ) -> Result<(), DispatchError> {
         let size = self.as_mut().send_response_inner(message, &body)?;
         let state = match size {
-            BodySize::None | BodySize::Empty => State::None,
+            BodySize::None | BodySize::Sized(0) => State::None,
             _ => State::SendPayload(body),
         };
         self.project().state.set(state);
@@ -339,7 +339,7 @@ where
     ) -> Result<(), DispatchError> {
         let size = self.as_mut().send_response_inner(message, &body)?;
         let state = match size {
-            BodySize::None | BodySize::Empty => State::None,
+            BodySize::None | BodySize::Sized(0) => State::None,
             _ => State::SendErrorPayload(body),
         };
         self.project().state.set(state);
@@ -380,7 +380,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, AnyBody::empty())?;
                     }
 
                     // return with upgrade request and poll it exclusively.
@@ -772,7 +772,7 @@ where
                                 trace!("Slow request timeout");
                                 let _ = self.as_mut().send_error_response(
                                     Response::with_body(StatusCode::REQUEST_TIMEOUT, ()),
-                                    AnyBody::Empty,
+                                    AnyBody::empty(),
                                 );
                                 this = self.project();
                                 this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs
index ead14206..e07c3295 100644
--- a/actix-http/src/h1/encoder.rs
+++ b/actix-http/src/h1/encoder.rs
@@ -93,13 +93,10 @@ pub(crate) trait MessageType: Sized {
                     dst.put_slice(b"\r\n");
                 }
             }
-            BodySize::Empty => {
-                if camel_case {
-                    dst.put_slice(b"\r\nContent-Length: 0\r\n");
-                } else {
-                    dst.put_slice(b"\r\ncontent-length: 0\r\n");
-                }
+            BodySize::Sized(0) if camel_case => {
+                dst.put_slice(b"\r\nContent-Length: 0\r\n")
             }
+            BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"),
             BodySize::Sized(len) => helpers::write_content_length(len, dst),
             BodySize::None => dst.put_slice(b"\r\n"),
         }
@@ -336,7 +333,7 @@ impl<T: MessageType> MessageEncoder<T> {
         // transfer encoding
         if !head {
             self.te = match length {
-                BodySize::Empty => TransferEncoding::empty(),
+                BodySize::Sized(0) => TransferEncoding::empty(),
                 BodySize::Sized(len) => TransferEncoding::length(len),
                 BodySize::Stream => {
                     if message.chunked() && !stream {
@@ -553,7 +550,7 @@ mod tests {
         let _ = head.encode_headers(
             &mut bytes,
             Version::HTTP_11,
-            BodySize::Empty,
+            BodySize::Sized(0),
             ConnectionType::Close,
             &ServiceConfig::default(),
         );
@@ -624,7 +621,7 @@ mod tests {
         let _ = head.encode_headers(
             &mut bytes,
             Version::HTTP_11,
-            BodySize::Empty,
+            BodySize::Sized(0),
             ConnectionType::Close,
             &ServiceConfig::default(),
         );
diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs
index 7326dfff..8b922b2c 100644
--- a/actix-http/src/h2/dispatcher.rs
+++ b/actix-http/src/h2/dispatcher.rs
@@ -285,9 +285,11 @@ fn prepare_response(
 
     let _ = match size {
         BodySize::None | BodySize::Stream => None,
-        BodySize::Empty => res
+
+        BodySize::Sized(0) => res
             .headers_mut()
             .insert(CONTENT_LENGTH, HeaderValue::from_static("0")),
+
         BodySize::Sized(len) => {
             let mut buf = itoa::Buffer::new();
 
diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs
index 2aa38c15..47f1c37e 100644
--- a/actix-http/src/response.rs
+++ b/actix-http/src/response.rs
@@ -28,7 +28,7 @@ impl Response<AnyBody> {
     pub fn new(status: StatusCode) -> Self {
         Response {
             head: BoxedResponseHead::new(status),
-            body: AnyBody::Empty,
+            body: AnyBody::empty(),
         }
     }
 
diff --git a/actix-http/src/response_builder.rs b/actix-http/src/response_builder.rs
index e46d9a28..a1cb1a42 100644
--- a/actix-http/src/response_builder.rs
+++ b/actix-http/src/response_builder.rs
@@ -270,7 +270,7 @@ impl ResponseBuilder {
     /// This `ResponseBuilder` will be left in a useless state.
     #[inline]
     pub fn finish(&mut self) -> Response<AnyBody> {
-        self.body(AnyBody::Empty)
+        self.body(AnyBody::empty())
     }
 
     /// Create an owned `ResponseBuilder`, leaving the original in a useless state.
@@ -390,7 +390,7 @@ mod tests {
     fn test_content_type() {
         let resp = Response::build(StatusCode::OK)
             .content_type("text/plain")
-            .body(Body::Empty);
+            .body(Body::empty());
         assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
     }
 
diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs
index 3c2bb7cc..7f3ba1b6 100644
--- a/awc/src/client/h1proto.rs
+++ b/awc/src/client/h1proto.rs
@@ -70,7 +70,7 @@ where
     // RFC: https://tools.ietf.org/html/rfc7231#section-5.1.1
     let is_expect = if head.as_ref().headers.contains_key(EXPECT) {
         match body.size() {
-            BodySize::None | BodySize::Empty | BodySize::Sized(0) => {
+            BodySize::None | BodySize::Sized(0) => {
                 let keep_alive = framed.codec_ref().keepalive();
                 framed.io_mut().on_release(keep_alive);
 
@@ -104,7 +104,7 @@ where
     if do_send {
         // send request body
         match body.size() {
-            BodySize::None | BodySize::Empty | BodySize::Sized(0) => {}
+            BodySize::None | BodySize::Sized(0) => {}
             _ => send_body(body, pin_framed.as_mut()).await?,
         };
 
diff --git a/awc/src/client/h2proto.rs b/awc/src/client/h2proto.rs
index feb2dbd0..2618e190 100644
--- a/awc/src/client/h2proto.rs
+++ b/awc/src/client/h2proto.rs
@@ -36,10 +36,7 @@ where
 
     let head_req = head.as_ref().method == Method::HEAD;
     let length = body.size();
-    let eof = matches!(
-        length,
-        BodySize::None | BodySize::Empty | BodySize::Sized(0)
-    );
+    let eof = matches!(length, BodySize::None | BodySize::Sized(0));
 
     let mut req = Request::new(());
     *req.uri_mut() = head.as_ref().uri.clone();
@@ -52,13 +49,11 @@ where
     // Content length
     let _ = match length {
         BodySize::None => None,
-        BodySize::Stream => {
-            skip_len = false;
-            None
-        }
-        BodySize::Empty => req
+
+        BodySize::Sized(0) => req
             .headers_mut()
             .insert(CONTENT_LENGTH, HeaderValue::from_static("0")),
+
         BodySize::Sized(len) => {
             let mut buf = itoa::Buffer::new();
 
@@ -67,6 +62,11 @@ where
                 HeaderValue::from_str(buf.format(len)).unwrap(),
             )
         }
+
+        BodySize::Stream => {
+            skip_len = false;
+            None
+        }
     };
 
     // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate.
diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs
index 8a79a659..f01136d1 100644
--- a/awc/src/middleware/redirect.rs
+++ b/awc/src/middleware/redirect.rs
@@ -194,7 +194,7 @@ where
                             match body {
                                 Some(ref bytes) => Body::Bytes(bytes.clone()),
                                 // TODO: should this be Body::Empty or Body::None.
-                                _ => Body::Empty,
+                                _ => Body::empty(),
                             }
                         } else {
                             body = None;
diff --git a/awc/src/sender.rs b/awc/src/sender.rs
index c0639606..fcd0c71a 100644
--- a/awc/src/sender.rs
+++ b/awc/src/sender.rs
@@ -297,7 +297,7 @@ impl RequestSender {
         timeout: Option<Duration>,
         config: &ClientConfig,
     ) -> SendClientRequest {
-        self.send_body(addr, response_decompress, timeout, config, Body::Empty)
+        self.send_body(addr, response_decompress, timeout, config, Body::empty())
     }
 
     fn set_header_if_none<V>(&mut self, key: HeaderName, value: V) -> Result<(), HttpError>
diff --git a/src/response/builder.rs b/src/response/builder.rs
index 56d30d9d..f6099a01 100644
--- a/src/response/builder.rs
+++ b/src/response/builder.rs
@@ -387,7 +387,7 @@ impl HttpResponseBuilder {
     /// `HttpResponseBuilder` can not be used after this call.
     #[inline]
     pub fn finish(&mut self) -> HttpResponse {
-        self.body(AnyBody::Empty)
+        self.body(AnyBody::empty())
     }
 
     /// This method construct new `HttpResponseBuilder`
@@ -475,7 +475,7 @@ mod tests {
     fn test_content_type() {
         let resp = HttpResponseBuilder::new(StatusCode::OK)
             .content_type("text/plain")
-            .body(Body::Empty);
+            .body(Body::empty());
         assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain")
     }
 
diff --git a/src/response/http_codes.rs b/src/response/http_codes.rs
index d67ef3f9..44ddb78f 100644
--- a/src/response/http_codes.rs
+++ b/src/response/http_codes.rs
@@ -87,13 +87,12 @@ impl HttpResponse {
 
 #[cfg(test)]
 mod tests {
-    use crate::dev::Body;
     use crate::http::StatusCode;
     use crate::HttpResponse;
 
     #[test]
     fn test_build() {
-        let resp = HttpResponse::Ok().body(Body::Empty);
+        let resp = HttpResponse::Ok().finish();
         assert_eq!(resp.status(), StatusCode::OK);
     }
 }