Skip to main content

topcoat_router/
body.rs

1use std::{
2    any::Any,
3    convert::Infallible,
4    fmt,
5    panic::{AssertUnwindSafe, catch_unwind},
6    pin::Pin,
7    task::{Context, Poll},
8};
9
10use bytes::Bytes;
11use http_body::{Frame, SizeHint};
12use http_body_util::{
13    BodyExt, BodyStream, Empty, Full, LengthLimitError, Limited, combinators::UnsyncBoxBody,
14};
15use topcoat_core::error::Result;
16
17use crate::error::{bad_request, content_too_large};
18
19/// A boxed error type used by the response body machinery.
20pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
21
22/// The HTTP body type used for both requests and responses.
23///
24/// A panic while producing a frame ends the body with a [`BodyPanicError`]
25/// instead of unwinding into the connection serving it.
26#[must_use]
27pub struct Body(UnsyncBoxBody<Bytes, BoxError>);
28
29impl Body {
30    /// Creates an empty body.
31    pub fn empty() -> Self {
32        Self::from(Bytes::new())
33    }
34
35    /// Wraps any [`http_body::Body`] that yields [`Bytes`].
36    pub fn new<B>(body: B) -> Self
37    where
38        B: http_body::Body<Data = Bytes> + Send + 'static,
39        B::Error: Into<BoxError>,
40    {
41        Self(body.map_err(Into::into).boxed_unsync())
42    }
43
44    /// Consumes the body, returning a [`Stream`](futures_core::Stream) of its
45    /// data frames.
46    pub fn into_data_stream(self) -> BodyDataStream {
47        BodyDataStream(BodyStream::new(self))
48    }
49}
50
51impl Default for Body {
52    fn default() -> Self {
53        Self::empty()
54    }
55}
56
57impl From<Bytes> for Body {
58    fn from(bytes: Bytes) -> Self {
59        Self(
60            Full::new(bytes)
61                .map_err(|never: Infallible| match never {})
62                .boxed_unsync(),
63        )
64    }
65}
66
67impl From<()> for Body {
68    fn from((): ()) -> Self {
69        Self(
70            Empty::new()
71                .map_err(|never: Infallible| match never {})
72                .boxed_unsync(),
73        )
74    }
75}
76
77impl From<Vec<u8>> for Body {
78    fn from(value: Vec<u8>) -> Self {
79        Self::from(Bytes::from(value))
80    }
81}
82
83impl From<&'static [u8]> for Body {
84    fn from(value: &'static [u8]) -> Self {
85        Self::from(Bytes::from_static(value))
86    }
87}
88
89impl From<String> for Body {
90    fn from(value: String) -> Self {
91        Self::from(Bytes::from(value))
92    }
93}
94
95impl From<&'static str> for Body {
96    fn from(value: &'static str) -> Self {
97        Self::from(Bytes::from_static(value.as_bytes()))
98    }
99}
100
101impl http_body::Body for Body {
102    type Data = Bytes;
103    type Error = BoxError;
104
105    fn poll_frame(
106        mut self: Pin<&mut Self>,
107        cx: &mut Context<'_>,
108    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
109        // A panic while producing a frame is caught here rather than in the
110        // connection task polling the body, which would take down the
111        // connection and every other request on it.
112        match catch_unwind(AssertUnwindSafe(|| Pin::new(&mut self.0).poll_frame(cx))) {
113            Ok(poll) => poll,
114            Err(payload) => {
115                // The body is poisoned: polling it again would panic again.
116                // Swapping in an ended body drops it and reports the end
117                // through `is_end_stream` and `size_hint` as well.
118                self.0 = Self::from(()).0;
119                Poll::Ready(Some(Err(BodyPanicError::new(payload).into())))
120            }
121        }
122    }
123
124    fn size_hint(&self) -> SizeHint {
125        self.0.size_hint()
126    }
127
128    fn is_end_stream(&self) -> bool {
129        self.0.is_end_stream()
130    }
131}
132
133impl fmt::Debug for Body {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        f.debug_struct("Body").finish_non_exhaustive()
136    }
137}
138
139/// The error a [`Body`] ends with when producing one of its frames panicked.
140///
141/// The panic is caught at the body, so the connection carrying it stays up
142/// and the stream terminates with this error instead. Carries the panic's
143/// message when it had one.
144#[derive(Debug)]
145pub struct BodyPanicError {
146    message: Option<Box<str>>,
147}
148
149impl BodyPanicError {
150    /// Builds the error from a caught panic's payload.
151    fn new(payload: Box<dyn Any + Send>) -> Self {
152        let message = match payload.downcast::<String>() {
153            Ok(message) => Some(message.into_boxed_str()),
154            Err(payload) => payload
155                .downcast::<&'static str>()
156                .ok()
157                .map(|message| Box::from(*message)),
158        };
159        Self { message }
160    }
161
162    /// The panic's message, if it carried one.
163    #[must_use]
164    pub fn message(&self) -> Option<&str> {
165        self.message.as_deref()
166    }
167}
168
169impl fmt::Display for BodyPanicError {
170    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171        match &self.message {
172            Some(message) => write!(f, "the body panicked: {message}"),
173            None => f.write_str("the body panicked"),
174        }
175    }
176}
177
178impl std::error::Error for BodyPanicError {}
179
180/// A [`Stream`](futures_core::Stream) over the data frames of a [`Body`],
181/// yielding the raw [`Bytes`] of each frame.
182#[must_use]
183pub struct BodyDataStream(BodyStream<Body>);
184
185impl futures_core::Stream for BodyDataStream {
186    type Item = Result<Bytes, BoxError>;
187
188    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
189        loop {
190            return match Pin::new(&mut self.0).poll_next(cx) {
191                Poll::Ready(Some(Ok(frame))) => match frame.into_data() {
192                    Ok(data) => Poll::Ready(Some(Ok(data))),
193                    Err(_frame) => continue,
194                },
195                Poll::Ready(Some(Err(error))) => Poll::Ready(Some(Err(error))),
196                Poll::Ready(None) => Poll::Ready(None),
197                Poll::Pending => Poll::Pending,
198            };
199        }
200    }
201}
202
203/// Collects an entire [`Body`] into [`Bytes`], failing if it exceeds `limit`.
204///
205/// Pass [`usize::MAX`] to read the body without enforcing a limit. When
206/// reading a request body, pass the request's
207/// [`body_limit`](crate::body_limit) so the configured limit applies.
208///
209/// # Errors
210///
211/// Returns a [`ContentTooLargeError`](crate::error::ContentTooLargeError) when
212/// the body exceeds `limit` bytes, and a
213/// [`BadRequestError`](crate::error::BadRequestError) when reading it fails.
214/// Both render themselves as a response, so a
215/// [`FromRequest`](crate::request::FromRequest) implementation can propagate them with
216/// `?` rather than mapping them by hand.
217pub async fn to_bytes(body: Body, limit: usize) -> Result<Bytes> {
218    let collected = if limit == usize::MAX {
219        body.collect().await
220    } else {
221        Limited::new(body, limit).collect().await
222    };
223
224    match collected {
225        Ok(collected) => Ok(collected.to_bytes()),
226        Err(error) => Err(match error.downcast::<LengthLimitError>() {
227            Ok(_) => content_too_large().into(),
228            Err(error) => bad_request(format!("failed to read the body: {error}")).into(),
229        }),
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use http_body::Body as _;
236
237    use super::*;
238    use crate::error::{BadRequestError, ContentTooLargeError};
239
240    /// A body that fails on its first frame, standing in for a connection that
241    /// breaks mid-stream.
242    struct FailingBody;
243
244    impl http_body::Body for FailingBody {
245        type Data = Bytes;
246        type Error = BoxError;
247
248        fn poll_frame(
249            self: Pin<&mut Self>,
250            _cx: &mut Context<'_>,
251        ) -> Poll<Option<Result<Frame<Bytes>, BoxError>>> {
252            Poll::Ready(Some(Err("body stream broke".into())))
253        }
254    }
255
256    /// A body that panics on its first frame.
257    struct PanickingBody;
258
259    impl http_body::Body for PanickingBody {
260        type Data = Bytes;
261        type Error = BoxError;
262
263        fn poll_frame(
264            self: Pin<&mut Self>,
265            _cx: &mut Context<'_>,
266        ) -> Poll<Option<Result<Frame<Bytes>, BoxError>>> {
267            panic!("frame {}", 1);
268        }
269    }
270
271    #[tokio::test]
272    async fn a_body_within_the_limit_reads_in_full() {
273        let bytes = to_bytes(Body::from("hello"), 1024).await.unwrap();
274        assert_eq!(bytes, Bytes::from_static(b"hello"));
275    }
276
277    #[tokio::test]
278    async fn usize_max_reads_without_enforcing_a_limit() {
279        let bytes = to_bytes(Body::from("hello"), usize::MAX).await.unwrap();
280        assert_eq!(bytes, Bytes::from_static(b"hello"));
281    }
282
283    #[tokio::test]
284    async fn a_body_over_the_limit_is_content_too_large() {
285        let error = to_bytes(Body::from("hello"), 4).await.unwrap_err();
286        assert!(error.is::<ContentTooLargeError>());
287    }
288
289    #[tokio::test]
290    async fn a_panicking_body_ends_its_stream_with_an_error() {
291        let mut body = Body::new(PanickingBody);
292        assert!(!body.is_end_stream());
293
294        let error = body.frame().await.unwrap().unwrap_err();
295        let error = error
296            .downcast::<BodyPanicError>()
297            .expect("a panic ends the stream with its own error");
298        assert_eq!(error.message(), Some("frame 1"));
299        assert_eq!(error.to_string(), "the body panicked: frame 1");
300
301        // The panicked body is gone; the stream reports its end instead of
302        // polling it again.
303        assert!(body.is_end_stream());
304        assert!(body.frame().await.is_none());
305    }
306
307    #[tokio::test]
308    async fn a_read_failure_is_a_bad_request_carrying_the_cause() {
309        // Both branches are covered: a limit installs `Limited` around the
310        // body, which boxes the failure a second time.
311        for limit in [usize::MAX, 1024] {
312            let error = to_bytes(Body::new(FailingBody), limit).await.unwrap_err();
313            let error = error
314                .downcast::<BadRequestError>()
315                .expect("a read failure is a bad request");
316            assert_eq!(
317                error.description(),
318                "failed to read the body: body stream broke"
319            );
320        }
321    }
322}