Skip to main content

rainier_http/
body.rs

1//! Response bodies — [`Body`].
2//!
3//! Requests and responses are deliberately asymmetric here:
4//!
5//! - **Request** bodies are always buffered, by the time a [`Request`] exists
6//!   (the server reads them under a size limit). That is what lets
7//!   [`Request::input`] be a plain synchronous call — a lazily-streamed
8//!   request body would
9//!   force `.await` into every accessor and every validation rule.
10//! - **Response** bodies may stream, because file downloads and long-lived
11//!   event streams genuinely need it, and nothing about a response has to be
12//!   inspected synchronously after the fact.
13//!
14//! [`Request`]: crate::Request
15//! [`Request::input`]: crate::Request::input
16
17use std::pin::Pin;
18use std::task::{Context, Poll};
19
20use bytes::Bytes;
21use futures_core::Stream;
22use rainier_support::Error;
23
24/// A boxed stream of body chunks.
25pub type BodyStream = Pin<Box<dyn Stream<Item = Result<Bytes, Error>> + Send>>;
26
27/// An HTTP response body: nothing, some bytes, or a stream of chunks.
28#[derive(Default)]
29pub enum Body {
30    /// No body at all — distinct from a zero-length one, so `204 No Content`
31    /// can omit `Content-Length` entirely.
32    #[default]
33    Empty,
34    /// A body already in memory. Its length is known, so `Content-Length` can
35    /// be set.
36    Bytes(Bytes),
37    /// A body produced incrementally. Its length is unknown, so it goes out
38    /// chunked.
39    Stream(BodyStream),
40}
41
42impl Body {
43    /// An empty body.
44    pub fn empty() -> Self {
45        Body::Empty
46    }
47
48    /// A body from anything byte-like.
49    pub fn from_bytes(bytes: impl Into<Bytes>) -> Self {
50        Body::Bytes(bytes.into())
51    }
52
53    /// A streaming body.
54    pub fn from_stream<S>(stream: S) -> Self
55    where
56        S: Stream<Item = Result<Bytes, Error>> + Send + 'static,
57    {
58        Body::Stream(Box::pin(stream))
59    }
60
61    /// The body's length in bytes, when it is known up front. `None` for a
62    /// stream.
63    pub fn size_hint(&self) -> Option<u64> {
64        match self {
65            Body::Empty => Some(0),
66            Body::Bytes(bytes) => Some(bytes.len() as u64),
67            Body::Stream(_) => None,
68        }
69    }
70
71    /// Whether this body is definitely empty. `false` for a stream, which may
72    /// yet yield nothing — we cannot know without polling it.
73    pub fn is_empty(&self) -> bool {
74        match self {
75            Body::Empty => true,
76            Body::Bytes(bytes) => bytes.is_empty(),
77            Body::Stream(_) => false,
78        }
79    }
80
81    /// Collect the whole body into memory.
82    ///
83    /// Drains a stream to completion, so only call it on a body you know is
84    /// bounded — in tests, or on a response you are about to assert against.
85    pub async fn collect(self) -> Result<Bytes, Error> {
86        match self {
87            Body::Empty => Ok(Bytes::new()),
88            Body::Bytes(bytes) => Ok(bytes),
89            Body::Stream(mut stream) => {
90                let mut collected = Vec::new();
91                // Poll the stream by hand rather than take a `futures-util`
92                // dependency for one `next()`.
93                std::future::poll_fn(|cx| loop {
94                    match stream.as_mut().poll_next(cx) {
95                        Poll::Ready(Some(Ok(chunk))) => collected.extend_from_slice(&chunk),
96                        Poll::Ready(Some(Err(e))) => return Poll::Ready(Err(e)),
97                        Poll::Ready(None) => return Poll::Ready(Ok(())),
98                        Poll::Pending => return Poll::Pending,
99                    }
100                })
101                .await?;
102                Ok(Bytes::from(collected))
103            }
104        }
105    }
106}
107
108impl std::fmt::Debug for Body {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        match self {
111            Body::Empty => f.write_str("Body::Empty"),
112            Body::Bytes(bytes) => write!(f, "Body::Bytes({} bytes)", bytes.len()),
113            Body::Stream(_) => f.write_str("Body::Stream(..)"),
114        }
115    }
116}
117
118impl From<()> for Body {
119    fn from(_: ()) -> Self {
120        Body::Empty
121    }
122}
123
124impl From<Bytes> for Body {
125    fn from(bytes: Bytes) -> Self {
126        Body::Bytes(bytes)
127    }
128}
129
130impl From<Vec<u8>> for Body {
131    fn from(bytes: Vec<u8>) -> Self {
132        Body::Bytes(Bytes::from(bytes))
133    }
134}
135
136impl From<String> for Body {
137    fn from(text: String) -> Self {
138        Body::Bytes(Bytes::from(text))
139    }
140}
141
142impl From<&'static str> for Body {
143    fn from(text: &'static str) -> Self {
144        Body::Bytes(Bytes::from_static(text.as_bytes()))
145    }
146}
147
148/// Lets a [`Body`] be handed straight to hyper, which writes responses through
149/// the `http-body` trait.
150impl http_body::Body for Body {
151    type Data = Bytes;
152    type Error = Error;
153
154    fn poll_frame(
155        self: Pin<&mut Self>,
156        cx: &mut Context<'_>,
157    ) -> Poll<Option<Result<http_body::Frame<Bytes>, Error>>> {
158        // No `unsafe` needed: every variant is `Unpin` (`Bytes` holds no
159        // self-references, and the stream variant is already a `Pin<Box<..>>`,
160        // which is `Unpin` whatever it points at), so `Body` is too.
161        let this = self.get_mut();
162        match this {
163            Body::Empty => Poll::Ready(None),
164            Body::Bytes(bytes) => {
165                if bytes.is_empty() {
166                    Poll::Ready(None)
167                } else {
168                    let chunk = std::mem::take(bytes);
169                    Poll::Ready(Some(Ok(http_body::Frame::data(chunk))))
170                }
171            }
172            Body::Stream(stream) => match stream.as_mut().poll_next(cx) {
173                Poll::Ready(Some(Ok(chunk))) => {
174                    Poll::Ready(Some(Ok(http_body::Frame::data(chunk))))
175                }
176                Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
177                Poll::Ready(None) => Poll::Ready(None),
178                Poll::Pending => Poll::Pending,
179            },
180        }
181    }
182
183    fn is_end_stream(&self) -> bool {
184        match self {
185            Body::Empty => true,
186            Body::Bytes(bytes) => bytes.is_empty(),
187            Body::Stream(_) => false,
188        }
189    }
190
191    fn size_hint(&self) -> http_body::SizeHint {
192        match Body::size_hint(self) {
193            Some(len) => http_body::SizeHint::with_exact(len),
194            None => http_body::SizeHint::default(),
195        }
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use futures_util::stream;
203
204    #[tokio::test]
205    async fn collects_in_memory_bodies() {
206        assert_eq!(Body::empty().collect().await.unwrap(), Bytes::new());
207        assert_eq!(Body::from("hello").collect().await.unwrap(), Bytes::from("hello"));
208    }
209
210    #[tokio::test]
211    async fn collects_a_stream() {
212        let body =
213            Body::from_stream(stream::iter(vec![Ok(Bytes::from("ab")), Ok(Bytes::from("cd"))]));
214        assert_eq!(body.collect().await.unwrap(), Bytes::from("abcd"));
215    }
216
217    #[tokio::test]
218    async fn a_stream_error_surfaces_from_collect() {
219        let body = Body::from_stream(stream::iter(vec![
220            Ok(Bytes::from("ab")),
221            Err(Error::internal("disk died")),
222        ]));
223        assert_eq!(body.collect().await.unwrap_err().message(), "disk died");
224    }
225
226    #[test]
227    fn size_hints_are_known_except_for_streams() {
228        assert_eq!(Body::empty().size_hint(), Some(0));
229        assert_eq!(Body::from("abc").size_hint(), Some(3));
230        assert_eq!(Body::from_stream(stream::empty()).size_hint(), None);
231    }
232
233    #[test]
234    fn emptiness_is_only_claimed_when_certain() {
235        assert!(Body::empty().is_empty());
236        assert!(Body::from("").is_empty());
237        assert!(!Body::from("x").is_empty());
238        // A stream might yield nothing, but we cannot know without polling.
239        assert!(!Body::from_stream(stream::empty()).is_empty());
240    }
241
242    #[tokio::test]
243    async fn drives_as_an_http_body() {
244        use http_body_util::BodyExt;
245
246        let collected = Body::from("payload").collect().await.unwrap();
247        assert_eq!(collected, Bytes::from("payload"));
248
249        // And through the http-body trait itself, which is how hyper reads it.
250        let body = Body::from("payload");
251        let aggregated = BodyExt::collect(body).await.unwrap().to_bytes();
252        assert_eq!(aggregated, Bytes::from("payload"));
253    }
254}