Skip to main content

trillium_http/
body.rs

1mod framed;
2#[cfg(feature = "unstable")]
3use crate::h3::H3Body;
4use crate::{Headers, h2::H2Body};
5use BodyType::{Empty, Static, Streaming};
6pub use framed::BodyFraming;
7use futures_lite::{AsyncRead, AsyncReadExt, io::Cursor, ready};
8use pin_project_lite::pin_project;
9use std::{
10    borrow::Cow,
11    fmt::{self, Debug, Formatter},
12    io::{Error, Result},
13    pin::Pin,
14    sync::Arc,
15    task::{Context, Poll},
16};
17use sync_wrapper::SyncWrapper;
18
19/// Streaming body source that can optionally produce trailers.
20///
21/// Implement this on types that compute trailer headers dynamically as the body
22/// is read — for example, a hashing wrapper that produces a `Digest` trailer
23/// after all bytes have been streamed. For plain [`AsyncRead`] sources with no
24/// trailers, [`Body::new_streaming`] is simpler.
25pub trait BodySource: AsyncRead + Send + 'static {
26    /// Returns the trailers for this body, called after the body has been fully read.
27    ///
28    /// Implementations may clear internal state on this call; the result is
29    /// only meaningful after [`AsyncRead::poll_read`] has returned `Ok(0)`.
30    fn trailers(self: Pin<&mut Self>) -> Option<Headers>;
31}
32
33pin_project! {
34    struct PlainBody<T> {
35        #[pin]
36        async_read: T,
37    }
38}
39
40impl<T: AsyncRead> AsyncRead for PlainBody<T> {
41    fn poll_read(
42        self: Pin<&mut Self>,
43        cx: &mut Context<'_>,
44        buf: &mut [u8],
45    ) -> Poll<Result<usize>> {
46        self.project().async_read.poll_read(cx, buf)
47    }
48}
49
50impl<T: AsyncRead + Send + 'static> BodySource for PlainBody<T> {
51    fn trailers(self: Pin<&mut Self>) -> Option<Headers> {
52        None
53    }
54}
55
56/// The trillium representation of a http body. This can contain
57/// either `&'static [u8]` content, `Vec<u8>` content, or a boxed
58/// [`AsyncRead`]/[`BodySource`] type.
59#[derive(Debug, Default)]
60pub struct Body(pub(crate) BodyType);
61
62impl Body {
63    /// Construct a new body from a streaming [`AsyncRead`] source. If
64    /// you have the body content in memory already, prefer
65    /// [`Body::new_static`] or one of the From conversions.
66    pub fn new_streaming(async_read: impl AsyncRead + Send + 'static, len: Option<u64>) -> Self {
67        Self::new_with_trailers(PlainBody { async_read }, len)
68    }
69
70    /// Construct a new body from a [`BodySource`] that can produce trailers after
71    /// the body has been fully read.
72    ///
73    /// Use this when trailers must be computed dynamically from the body bytes,
74    /// for example to append a content hash.
75    pub fn new_with_trailers(body: impl BodySource, len: Option<u64>) -> Self {
76        Self(Streaming {
77            async_read: SyncWrapper::new(Box::pin(body)),
78            len,
79            done: false,
80            progress: 0,
81            chunked_framing: true,
82            keep_open: false,
83        })
84    }
85
86    /// Disable chunked-encoding framing emitted by [`AsyncRead`] for streaming bodies
87    /// of unknown length.
88    ///
89    /// By default, when a streaming body has no known length, this type's [`AsyncRead`]
90    /// implementation emits chunked framing. That framing is wrong for any consumer that
91    /// wants raw body bytes. This only affects reading the `Body` directly as an
92    /// [`AsyncRead`]; the send paths choose framing explicitly via `write_into`.
93    #[doc(hidden)]
94    #[cfg(feature = "unstable")]
95    #[must_use]
96    pub fn without_chunked_framing(mut self) -> Self {
97        if let Streaming {
98            ref mut chunked_framing,
99            ..
100        } = self.0
101        {
102            *chunked_framing = false;
103        }
104        self
105    }
106
107    /// Normalize this body to an open, chunk-framed stream: its content goes out as
108    /// chunked-transfer chunks with **no** terminating `0\r\n`, leaving the outbound
109    /// stream open for a following upgrade to continue and eventually close.
110    ///
111    /// Fixed-length content (`Static`, or a streaming body with a known length) is
112    /// re-sourced through the chunked path so it too flows as ordinary chunks rather than
113    /// raw bytes — the caller doesn't have to hand-wrap it in a length-less streaming body.
114    /// An empty body stays empty (it contributes no bytes; the upgrade owns the whole
115    /// stream).
116    ///
117    /// The send site that consumes the body is responsible for *not* writing the
118    /// trailer-section terminator either; trailers (if any) ride onto the upgrade.
119    ///
120    /// Superseded by `write_into` with `BodyFraming::Chunked { keep_open: true }`, which
121    /// expresses this as framing instead of body state.
122    // Retained only so an older `trillium-client` that predates `write_into` still builds
123    // against this crate; remove it at the next breaking release.
124    #[doc(hidden)]
125    #[cfg(feature = "unstable")]
126    #[must_use]
127    pub fn keep_open(mut self) -> Self {
128        // Re-source fixed content through the chunked streaming path so it goes out as
129        // chunks instead of raw bytes. Streaming bodies are left in place (preserving their
130        // `BodySource`, hence any trailers) and just have their framing flags flipped below.
131        if matches!(self.0, Static { .. }) {
132            let reader = std::mem::take(&mut self).into_reader();
133            self = Self::new_streaming(reader, None);
134        }
135
136        if let Streaming {
137            ref mut len,
138            ref mut chunked_framing,
139            ref mut keep_open,
140            ..
141        } = self.0
142        {
143            *len = None;
144            *chunked_framing = true;
145            *keep_open = true;
146        }
147        self
148    }
149
150    /// Returns trailers from the body source, if any.
151    ///
152    /// Only meaningful after the body has been fully read (i.e., [`AsyncRead::poll_read`]
153    /// has returned `Ok(0)`). Returns `None` for bodies constructed with
154    /// [`Body::new_streaming`] or [`Body::new_static`].
155    #[doc(hidden)]
156    pub fn trailers(&mut self) -> Option<Headers> {
157        match &mut self.0 {
158            Streaming {
159                async_read, done, ..
160            } if *done => async_read.get_mut().as_mut().trailers(),
161            _ => None,
162        }
163    }
164
165    /// Construct a fixed-length Body from a `Vec<u8>` or `&'static
166    /// [u8]`.
167    pub fn new_static(content: impl Into<Cow<'static, [u8]>>) -> Self {
168        Self(Static {
169            content: StaticContent::Cow(content.into()),
170            cursor: 0,
171        })
172    }
173
174    /// Retrieve a borrow of the static content in this body. If this
175    /// body is a streaming body or an empty body, this will return
176    /// None.
177    pub fn static_bytes(&self) -> Option<&[u8]> {
178        match &self.0 {
179            Static { content, .. } => Some(content.as_ref()),
180            _ => None,
181        }
182    }
183
184    /// Transform this Body into a dyn [`AsyncRead`], wrapping static content in
185    /// a [`Cursor`]. Unlike reading from the Body directly, this does not apply
186    /// chunked encoding.
187    pub fn into_reader(self) -> Pin<Box<dyn AsyncRead + Send + Sync + 'static>> {
188        match self.0 {
189            Streaming { async_read, .. } => Box::pin(SyncAsyncReader(async_read)),
190            Static { content, .. } => Box::pin(Cursor::new(content)),
191            Empty => Box::pin(Cursor::new("")),
192        }
193    }
194
195    /// Consume this body and return the full content. If the body was constructed
196    /// with [`Body::new_streaming`], this will read the entire streaming body into
197    /// memory, awaiting the streaming source's completion.
198    ///
199    /// # Errors
200    ///
201    /// Returns an error if the underlying transport errors, or if a streaming body
202    /// has already been partially or fully read.
203    pub async fn into_bytes(self) -> Result<Cow<'static, [u8]>> {
204        match self.0 {
205            Static { content, .. } => Ok(content.into_cow()),
206
207            Streaming {
208                async_read,
209                len,
210                progress: 0,
211                done: false,
212                ..
213            } => {
214                let mut async_read = async_read.into_inner();
215                let mut buf = len
216                    .and_then(|c| c.try_into().ok())
217                    .map(Vec::with_capacity)
218                    .unwrap_or_default();
219
220                async_read.read_to_end(&mut buf).await?;
221
222                Ok(Cow::Owned(buf))
223            }
224
225            Empty => Ok(Cow::Borrowed(b"")),
226
227            Streaming { .. } => Err(Error::other("body already read to completion")),
228        }
229    }
230
231    /// Retrieve the number of bytes that have been read from this
232    /// body
233    pub fn bytes_read(&self) -> u64 {
234        self.0.bytes_read()
235    }
236
237    /// returns the content length of this body, if known and
238    /// available.
239    pub fn len(&self) -> Option<u64> {
240        self.0.len()
241    }
242
243    /// determine if the this body represents no data
244    pub fn is_empty(&self) -> bool {
245        self.0.is_empty()
246    }
247
248    /// determine if the this body represents static content
249    pub fn is_static(&self) -> bool {
250        matches!(self.0, Static { .. })
251    }
252
253    /// determine if the this body represents streaming content
254    pub fn is_streaming(&self) -> bool {
255        matches!(self.0, Streaming { .. })
256    }
257
258    /// Consume this body and return its underlying [`BodySource`], if it is a streaming body.
259    ///
260    /// Returns `None` for static and empty bodies, whose content is already in memory and whose
261    /// length is already known. This is the extraction point a sender needs to buffer a
262    /// streaming body while preserving its trailer-producing source — unlike
263    /// [`into_reader`](Self::into_reader), which erases the source to a plain `AsyncRead`.
264    #[cfg(feature = "unstable")]
265    #[doc(hidden)]
266    pub fn into_body_source(self) -> Option<Pin<Box<dyn BodySource>>> {
267        match self.0 {
268            Streaming { async_read, .. } => Some(async_read.into_inner()),
269            _ => None,
270        }
271    }
272
273    /// Attempt to clone this body. Returns `None` for streaming bodies, which are one-shot.
274    ///
275    /// Static bodies clone cheaply — a `Cow` clone, which is a pointer copy for borrowed
276    /// `&'static` content and a `Vec` clone for owned content. The clone resets read
277    /// progress, so it can be sent again from the beginning. Empty bodies always clone
278    /// successfully.
279    #[doc(hidden)]
280    #[cfg(feature = "unstable")]
281    pub fn try_clone(&self) -> Option<Self> {
282        match &self.0 {
283            Empty => Some(Self::default()),
284            Static { content, .. } => Some(Self(Static {
285                content: content.clone(),
286                cursor: 0,
287            })),
288            Streaming { .. } => None,
289        }
290    }
291
292    /// Convert this body into an `H3Body` for reading.
293    ///
294    /// Superseded by [`write_into`][Self::write_into] with [`BodyFraming::H3Data`], which
295    /// frames directly into the send buffer.
296    // Retained only so an older `trillium-client` that predates `write_into` still builds
297    // against this crate; remove it at the next breaking release.
298    #[cfg(feature = "unstable")]
299    pub fn into_h3(self) -> H3Body {
300        H3Body::new(self)
301    }
302
303    /// Convert this body into an [`H2Body`] for reading by the h2 send pump.
304    ///
305    /// h2 frames DATA at the connection layer, so the body bytes that reach the send pump
306    /// must be plain payload — not chunk-encoded. [`H2Body`] strips the chunked-transfer
307    /// wrapping that [`Body::poll_read`] applies for the h1 path on streaming bodies of
308    /// unknown length, and forwards trailers so the send pump can emit trailing HEADERS.
309    pub(crate) fn into_h2(self) -> H2Body {
310        H2Body::new(self)
311    }
312}
313
314#[allow(
315    clippy::cast_sign_loss,
316    clippy::cast_possible_truncation,
317    clippy::cast_precision_loss,
318    reason = "buffers are well below petabyte scale; log2/4 of a usize stays in f64 range, and \
319              the subtraction always yields a non-negative usize-representable value"
320)]
321fn max_bytes_to_read(buf_len: usize) -> usize {
322    assert!(
323        buf_len >= 6,
324        "buffers of length {buf_len} are too small for this implementation.
325            if this is a problem for you, please open an issue"
326    );
327
328    let bytes_remaining_after_two_cr_lns = (buf_len - 4) as f64;
329    // maximum number of bytes the hex representation of the remaining bytes might take
330    let max_bytes_of_hex_framing = (bytes_remaining_after_two_cr_lns).log2() / 4f64;
331    (bytes_remaining_after_two_cr_lns - max_bytes_of_hex_framing.ceil()) as usize
332}
333
334impl AsyncRead for Body {
335    fn poll_read(
336        mut self: Pin<&mut Self>,
337        cx: &mut Context<'_>,
338        buf: &mut [u8],
339    ) -> Poll<Result<usize>> {
340        match &mut self.0 {
341            Empty => Poll::Ready(Ok(0)),
342            Static { content, cursor } => {
343                let length = content.len();
344                if length == *cursor {
345                    return Poll::Ready(Ok(0));
346                }
347                let bytes = (length - *cursor).min(buf.len());
348                buf[0..bytes].copy_from_slice(&content[*cursor..*cursor + bytes]);
349                *cursor += bytes;
350                Poll::Ready(Ok(bytes))
351            }
352
353            Streaming {
354                async_read,
355                len: Some(len),
356                done,
357                progress,
358                ..
359            } => {
360                if *done {
361                    return Poll::Ready(Ok(0));
362                }
363
364                let max_bytes_to_read = (*len - *progress)
365                    .try_into()
366                    .unwrap_or(buf.len())
367                    .min(buf.len());
368
369                let bytes = ready!(
370                    async_read
371                        .get_mut()
372                        .as_mut()
373                        .poll_read(cx, &mut buf[..max_bytes_to_read])
374                )?;
375
376                if bytes == 0 {
377                    *done = true;
378                } else {
379                    *progress += bytes as u64;
380                }
381
382                Poll::Ready(Ok(bytes))
383            }
384
385            Streaming {
386                async_read,
387                len: None,
388                done,
389                progress,
390                chunked_framing,
391                keep_open,
392            } => {
393                if *done {
394                    return Poll::Ready(Ok(0));
395                }
396
397                if !*chunked_framing {
398                    let bytes = ready!(async_read.get_mut().as_mut().poll_read(cx, buf))?;
399                    if bytes == 0 {
400                        *done = true;
401                    } else {
402                        *progress += bytes as u64;
403                    }
404                    return Poll::Ready(Ok(bytes));
405                }
406
407                let max_bytes_to_read = max_bytes_to_read(buf.len());
408
409                let bytes = ready!(
410                    async_read
411                        .get_mut()
412                        .as_mut()
413                        .poll_read(cx, &mut buf[..max_bytes_to_read])
414                )?;
415
416                if bytes == 0 {
417                    *done = true;
418                    if *keep_open {
419                        // The outbound stream continues into an upgrade; the upgrade owns
420                        // the terminator. Emit no last-chunk marker.
421                        return Poll::Ready(Ok(0));
422                    }
423                    // Last-chunk marker only; the caller emits the trailer-section
424                    // (possibly empty) followed by the terminating `\r\n`. Trailers come
425                    // from `BodySource::trailers()` as structured `Headers`, not bytes,
426                    // and the caller writes them in one shot so this path doesn't need
427                    // a multi-poll state machine spanning buffers.
428                    buf[..3].copy_from_slice(b"0\r\n");
429                    return Poll::Ready(Ok(3));
430                }
431
432                *progress += bytes as u64;
433
434                let start = format!("{bytes:X}\r\n");
435                let start_length = start.len();
436                let total = bytes + start_length + 2;
437                buf.copy_within(..bytes, start_length);
438                buf[..start_length].copy_from_slice(start.as_bytes());
439                buf[total - 2..total].copy_from_slice(b"\r\n");
440                Poll::Ready(Ok(total))
441            }
442        }
443    }
444}
445
446struct SyncAsyncReader(SyncWrapper<Pin<Box<dyn BodySource>>>);
447impl Debug for SyncAsyncReader {
448    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
449        f.debug_struct("SyncAsyncReader").finish()
450    }
451}
452impl AsyncRead for SyncAsyncReader {
453    fn poll_read(
454        self: Pin<&mut Self>,
455        cx: &mut Context<'_>,
456        buf: &mut [u8],
457    ) -> Poll<Result<usize>> {
458        self.get_mut().0.get_mut().as_mut().poll_read(cx, buf)
459    }
460}
461
462/// In-memory fixed-length body content. Each variant is cheap to clone: borrowed and
463/// shared variants copy a pointer, and the owned `Cow` variant clones its `Vec`.
464#[derive(Clone)]
465pub(crate) enum StaticContent {
466    Cow(Cow<'static, [u8]>),
467    Bytes(Arc<[u8]>),
468    Str(Arc<str>),
469}
470
471impl std::ops::Deref for StaticContent {
472    type Target = [u8];
473
474    fn deref(&self) -> &[u8] {
475        match self {
476            StaticContent::Cow(content) => content,
477            StaticContent::Bytes(content) => content,
478            StaticContent::Str(content) => content.as_bytes(),
479        }
480    }
481}
482
483impl AsRef<[u8]> for StaticContent {
484    fn as_ref(&self) -> &[u8] {
485        self
486    }
487}
488
489impl StaticContent {
490    /// Materialize as an owned `Cow`. The `Cow` variant passes through without copying;
491    /// the shared variants copy their bytes into a `Vec`.
492    fn into_cow(self) -> Cow<'static, [u8]> {
493        match self {
494            StaticContent::Cow(content) => content,
495            other => Cow::Owned(other.to_vec()),
496        }
497    }
498}
499
500#[derive(Default)]
501pub(crate) enum BodyType {
502    #[default]
503    Empty,
504
505    Static {
506        content: StaticContent,
507        cursor: usize,
508    },
509
510    Streaming {
511        async_read: SyncWrapper<Pin<Box<dyn BodySource>>>,
512        progress: u64,
513        len: Option<u64>,
514        done: bool,
515        /// When true (the default), [`Body`]'s [`AsyncRead`] impl emits chunked
516        /// framing for the `len: None` case; when false (via
517        /// [`Body::without_chunked_framing`]), it passes through raw bytes.
518        chunked_framing: bool,
519        /// When true (via [`Body::keep_open`]), the chunked `len: None` read does not
520        /// emit the `0\r\n` last-chunk marker at EOF — the outbound stream is left open
521        /// for a following upgrade to terminate. Default false.
522        keep_open: bool,
523    },
524}
525
526impl Debug for BodyType {
527    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
528        match self {
529            Empty => f.debug_tuple("BodyType::Empty").finish(),
530            Static { content, cursor } => f
531                .debug_struct("BodyType::Static")
532                .field("content", &String::from_utf8_lossy(content))
533                .field("cursor", cursor)
534                .finish(),
535            Streaming {
536                len,
537                done,
538                progress,
539                ..
540            } => f
541                .debug_struct("BodyType::Streaming")
542                .field("async_read", &format_args!(".."))
543                .field("len", &len)
544                .field("done", &done)
545                .field("progress", &progress)
546                .finish(),
547        }
548    }
549}
550
551impl BodyType {
552    fn is_empty(&self) -> bool {
553        match *self {
554            Empty => true,
555            Static { ref content, .. } => content.is_empty(),
556            Streaming { len, .. } => len == Some(0),
557        }
558    }
559
560    fn len(&self) -> Option<u64> {
561        match *self {
562            Empty => Some(0),
563            Static { ref content, .. } => Some(content.len() as u64),
564            Streaming { len, .. } => len,
565        }
566    }
567
568    fn bytes_read(&self) -> u64 {
569        match *self {
570            Empty => 0,
571            Static { cursor, .. } => cursor as u64,
572            Streaming { progress, .. } => progress,
573        }
574    }
575}
576
577impl From<String> for Body {
578    fn from(s: String) -> Self {
579        s.into_bytes().into()
580    }
581}
582
583impl From<&'static str> for Body {
584    fn from(s: &'static str) -> Self {
585        s.as_bytes().into()
586    }
587}
588
589impl From<&'static [u8]> for Body {
590    fn from(content: &'static [u8]) -> Self {
591        Self::new_static(content)
592    }
593}
594
595impl From<Vec<u8>> for Body {
596    fn from(content: Vec<u8>) -> Self {
597        Self::new_static(content)
598    }
599}
600
601impl From<Cow<'static, [u8]>> for Body {
602    fn from(value: Cow<'static, [u8]>) -> Self {
603        Self::new_static(value)
604    }
605}
606
607impl From<Cow<'static, str>> for Body {
608    fn from(value: Cow<'static, str>) -> Self {
609        match value {
610            Cow::Borrowed(b) => b.into(),
611            Cow::Owned(o) => o.into(),
612        }
613    }
614}
615
616impl From<Arc<[u8]>> for Body {
617    fn from(content: Arc<[u8]>) -> Self {
618        Self(Static {
619            content: StaticContent::Bytes(content),
620            cursor: 0,
621        })
622    }
623}
624
625impl From<Arc<str>> for Body {
626    fn from(content: Arc<str>) -> Self {
627        Self(Static {
628            content: StaticContent::Str(content),
629            cursor: 0,
630        })
631    }
632}
633
634#[cfg(test)]
635mod test_shared_content {
636    use super::Body;
637    use futures_lite::future::block_on;
638    use std::sync::Arc;
639
640    #[test]
641    fn arc_bytes_roundtrips() {
642        let arc: Arc<[u8]> = Arc::from(&b"shared bytes"[..]);
643        let body = Body::from(Arc::clone(&arc));
644        assert_eq!(body.len(), Some(12));
645        assert_eq!(body.static_bytes(), Some(&b"shared bytes"[..]));
646        assert_eq!(
647            block_on(body.into_bytes()).unwrap().as_ref(),
648            b"shared bytes"
649        );
650        // the source Arc is still usable — the body shared, not consumed, the buffer
651        assert_eq!(&*arc, b"shared bytes");
652    }
653
654    #[test]
655    fn arc_str_roundtrips() {
656        let arc: Arc<str> = Arc::from("shared str");
657        let body = Body::from(arc);
658        assert_eq!(body.len(), Some(10));
659        assert_eq!(body.static_bytes(), Some(&b"shared str"[..]));
660        assert_eq!(block_on(body.into_bytes()).unwrap().as_ref(), b"shared str");
661    }
662
663    #[cfg(feature = "unstable")]
664    #[test]
665    fn shared_body_clones_without_copying_the_arc() {
666        let arc: Arc<[u8]> = Arc::from(&b"abc"[..]);
667        let body = Body::from(Arc::clone(&arc));
668        let clone = body.try_clone().expect("static bodies clone");
669        assert_eq!(clone.static_bytes(), Some(&b"abc"[..]));
670        // original + body + clone all reference the same allocation
671        assert_eq!(Arc::strong_count(&arc), 3);
672    }
673}
674
675#[cfg(test)]
676mod test_bytes_to_read {
677    #[test]
678    fn simple_check_of_known_values() {
679        // the marked rows are the most important part of this test,
680        // and a nonobvious but intentional consequence of the
681        // implementation. in order to avoid overflowing, we must use
682        // one fewer than the available buffer bytes because
683        // increasing the read size increase the number of framed
684        // bytes by two. This occurs when the hex representation of
685        // the content bytes is near an increase in order of magnitude
686        // (F->10, FF->100, FFF-> 1000, etc)
687        let values = vec![
688            (6, 1),       // 1
689            (7, 2),       // 2
690            (20, 15),     // F
691            (21, 15),     // F <-
692            (22, 16),     // 10
693            (23, 17),     // 11
694            (260, 254),   // FE
695            (261, 254),   // FE <-
696            (262, 255),   // FF <-
697            (263, 256),   // 100
698            (4100, 4093), // FFD
699            (4101, 4093), // FFD <-
700            (4102, 4094), // FFE <-
701            (4103, 4095), // FFF <-
702            (4104, 4096), // 1000
703        ];
704
705        for (input, expected) in values {
706            let actual = super::max_bytes_to_read(input);
707            assert_eq!(
708                actual, expected,
709                "\n\nexpected max_bytes_to_read({input}) to be {expected}, but it was {actual}"
710            );
711
712            // testing the test:
713            let used_bytes = expected + 4 + format!("{expected:X}").len();
714            assert!(
715                used_bytes == input || used_bytes == input - 1,
716                "\n\nfor an input of {}, expected used bytes to be {} or {}, but was {}",
717                input,
718                input,
719                input - 1,
720                used_bytes
721            );
722        }
723    }
724}