Skip to main content

trillium_http/
body.rs

1mod framed;
2#[cfg(feature = "unstable")]
3use crate::h3::H3Body;
4use crate::{Headers, HttpConfig, h2::H2Body};
5use BodyType::{Empty, Static, Streaming};
6pub use framed::BodyFraming;
7use futures_lite::{AsyncRead, 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                // A declared length may come from a peer, so it bounds the preallocation but
216                // doesn't dictate it.
217                let mut buf = len
218                    .and_then(|len| usize::try_from(len).ok())
219                    .map_or_default(|len| {
220                        Vec::with_capacity(
221                            len.min(HttpConfig::DEFAULT.received_body_max_preallocate),
222                        )
223                    });
224
225                crate::util::read_to_end(&mut async_read, &mut buf).await?;
226
227                Ok(Cow::Owned(buf))
228            }
229
230            Empty => Ok(Cow::Borrowed(b"")),
231
232            Streaming { .. } => Err(Error::other("body already read to completion")),
233        }
234    }
235
236    /// Retrieve the number of bytes that have been read from this
237    /// body
238    pub fn bytes_read(&self) -> u64 {
239        self.0.bytes_read()
240    }
241
242    /// returns the content length of this body, if known and
243    /// available.
244    pub fn len(&self) -> Option<u64> {
245        self.0.len()
246    }
247
248    /// determine if the this body represents no data
249    pub fn is_empty(&self) -> bool {
250        self.0.is_empty()
251    }
252
253    /// determine if the this body represents static content
254    pub fn is_static(&self) -> bool {
255        matches!(self.0, Static { .. })
256    }
257
258    /// determine if the this body represents streaming content
259    pub fn is_streaming(&self) -> bool {
260        matches!(self.0, Streaming { .. })
261    }
262
263    /// Consume this body and return its underlying [`BodySource`], if it is a streaming body.
264    ///
265    /// Returns `None` for static and empty bodies, whose content is already in memory and whose
266    /// length is already known. This is the extraction point a sender needs to buffer a
267    /// streaming body while preserving its trailer-producing source — unlike
268    /// [`into_reader`](Self::into_reader), which erases the source to a plain `AsyncRead`.
269    #[cfg(feature = "unstable")]
270    #[doc(hidden)]
271    pub fn into_body_source(self) -> Option<Pin<Box<dyn BodySource>>> {
272        match self.0 {
273            Streaming { async_read, .. } => Some(async_read.into_inner()),
274            _ => None,
275        }
276    }
277
278    /// Attempt to clone this body. Returns `None` for streaming bodies, which are one-shot.
279    ///
280    /// Static bodies clone cheaply — a `Cow` clone, which is a pointer copy for borrowed
281    /// `&'static` content and a `Vec` clone for owned content. The clone resets read
282    /// progress, so it can be sent again from the beginning. Empty bodies always clone
283    /// successfully.
284    #[doc(hidden)]
285    #[cfg(feature = "unstable")]
286    pub fn try_clone(&self) -> Option<Self> {
287        match &self.0 {
288            Empty => Some(Self::default()),
289            Static { content, .. } => Some(Self(Static {
290                content: content.clone(),
291                cursor: 0,
292            })),
293            Streaming { .. } => None,
294        }
295    }
296
297    /// Convert this body into an `H3Body` for reading.
298    ///
299    /// Superseded by [`write_into`][Self::write_into] with [`BodyFraming::H3Data`], which
300    /// frames directly into the send buffer.
301    // Retained only so an older `trillium-client` that predates `write_into` still builds
302    // against this crate; remove it at the next breaking release.
303    #[cfg(feature = "unstable")]
304    pub fn into_h3(self) -> H3Body {
305        H3Body::new(self)
306    }
307
308    /// Convert this body into an [`H2Body`] for reading by the h2 send pump.
309    ///
310    /// h2 frames DATA at the connection layer, so the body bytes that reach the send pump
311    /// must be plain payload — not chunk-encoded. [`H2Body`] strips the chunked-transfer
312    /// wrapping that [`Body::poll_read`] applies for the h1 path on streaming bodies of
313    /// unknown length, and forwards trailers so the send pump can emit trailing HEADERS.
314    pub(crate) fn into_h2(self) -> H2Body {
315        H2Body::new(self)
316    }
317}
318
319#[allow(
320    clippy::cast_sign_loss,
321    clippy::cast_possible_truncation,
322    clippy::cast_precision_loss,
323    reason = "buffers are well below petabyte scale; log2/4 of a usize stays in f64 range, and \
324              the subtraction always yields a non-negative usize-representable value"
325)]
326fn max_bytes_to_read(buf_len: usize) -> usize {
327    assert!(
328        buf_len >= 6,
329        "buffers of length {buf_len} are too small for this implementation.
330            if this is a problem for you, please open an issue"
331    );
332
333    let bytes_remaining_after_two_cr_lns = (buf_len - 4) as f64;
334    // maximum number of bytes the hex representation of the remaining bytes might take
335    let max_bytes_of_hex_framing = (bytes_remaining_after_two_cr_lns).log2() / 4f64;
336    (bytes_remaining_after_two_cr_lns - max_bytes_of_hex_framing.ceil()) as usize
337}
338
339impl AsyncRead for Body {
340    fn poll_read(
341        mut self: Pin<&mut Self>,
342        cx: &mut Context<'_>,
343        buf: &mut [u8],
344    ) -> Poll<Result<usize>> {
345        match &mut self.0 {
346            Empty => Poll::Ready(Ok(0)),
347            Static { content, cursor } => {
348                let length = content.len();
349                if length == *cursor {
350                    return Poll::Ready(Ok(0));
351                }
352                let bytes = (length - *cursor).min(buf.len());
353                buf[0..bytes].copy_from_slice(&content[*cursor..*cursor + bytes]);
354                *cursor += bytes;
355                Poll::Ready(Ok(bytes))
356            }
357
358            Streaming {
359                async_read,
360                len: Some(len),
361                done,
362                progress,
363                ..
364            } => {
365                if *done {
366                    return Poll::Ready(Ok(0));
367                }
368
369                let max_bytes_to_read = (*len - *progress)
370                    .try_into()
371                    .unwrap_or(buf.len())
372                    .min(buf.len());
373
374                let bytes = ready!(
375                    async_read
376                        .get_mut()
377                        .as_mut()
378                        .poll_read(cx, &mut buf[..max_bytes_to_read])
379                )?;
380
381                if bytes == 0 {
382                    *done = true;
383                } else {
384                    *progress += bytes as u64;
385                }
386
387                Poll::Ready(Ok(bytes))
388            }
389
390            Streaming {
391                async_read,
392                len: None,
393                done,
394                progress,
395                chunked_framing,
396                keep_open,
397            } => {
398                if *done {
399                    return Poll::Ready(Ok(0));
400                }
401
402                if !*chunked_framing {
403                    let bytes = ready!(async_read.get_mut().as_mut().poll_read(cx, buf))?;
404                    if bytes == 0 {
405                        *done = true;
406                    } else {
407                        *progress += bytes as u64;
408                    }
409                    return Poll::Ready(Ok(bytes));
410                }
411
412                let max_bytes_to_read = max_bytes_to_read(buf.len());
413
414                let bytes = ready!(
415                    async_read
416                        .get_mut()
417                        .as_mut()
418                        .poll_read(cx, &mut buf[..max_bytes_to_read])
419                )?;
420
421                if bytes == 0 {
422                    *done = true;
423                    if *keep_open {
424                        // The outbound stream continues into an upgrade; the upgrade owns
425                        // the terminator. Emit no last-chunk marker.
426                        return Poll::Ready(Ok(0));
427                    }
428                    // Last-chunk marker only; the caller emits the trailer-section
429                    // (possibly empty) followed by the terminating `\r\n`. Trailers come
430                    // from `BodySource::trailers()` as structured `Headers`, not bytes,
431                    // and the caller writes them in one shot so this path doesn't need
432                    // a multi-poll state machine spanning buffers.
433                    buf[..3].copy_from_slice(b"0\r\n");
434                    return Poll::Ready(Ok(3));
435                }
436
437                *progress += bytes as u64;
438
439                let start = format!("{bytes:X}\r\n");
440                let start_length = start.len();
441                let total = bytes + start_length + 2;
442                buf.copy_within(..bytes, start_length);
443                buf[..start_length].copy_from_slice(start.as_bytes());
444                buf[total - 2..total].copy_from_slice(b"\r\n");
445                Poll::Ready(Ok(total))
446            }
447        }
448    }
449}
450
451struct SyncAsyncReader(SyncWrapper<Pin<Box<dyn BodySource>>>);
452impl Debug for SyncAsyncReader {
453    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
454        f.debug_struct("SyncAsyncReader").finish()
455    }
456}
457impl AsyncRead for SyncAsyncReader {
458    fn poll_read(
459        self: Pin<&mut Self>,
460        cx: &mut Context<'_>,
461        buf: &mut [u8],
462    ) -> Poll<Result<usize>> {
463        self.get_mut().0.get_mut().as_mut().poll_read(cx, buf)
464    }
465}
466
467/// In-memory fixed-length body content. Each variant is cheap to clone: borrowed and
468/// shared variants copy a pointer, and the owned `Cow` variant clones its `Vec`.
469#[derive(Clone)]
470pub(crate) enum StaticContent {
471    Cow(Cow<'static, [u8]>),
472    Bytes(Arc<[u8]>),
473    Str(Arc<str>),
474}
475
476impl std::ops::Deref for StaticContent {
477    type Target = [u8];
478
479    fn deref(&self) -> &[u8] {
480        match self {
481            StaticContent::Cow(content) => content,
482            StaticContent::Bytes(content) => content,
483            StaticContent::Str(content) => content.as_bytes(),
484        }
485    }
486}
487
488impl AsRef<[u8]> for StaticContent {
489    fn as_ref(&self) -> &[u8] {
490        self
491    }
492}
493
494impl StaticContent {
495    /// Materialize as an owned `Cow`. The `Cow` variant passes through without copying;
496    /// the shared variants copy their bytes into a `Vec`.
497    fn into_cow(self) -> Cow<'static, [u8]> {
498        match self {
499            StaticContent::Cow(content) => content,
500            other => Cow::Owned(other.to_vec()),
501        }
502    }
503}
504
505#[derive(Default)]
506pub(crate) enum BodyType {
507    #[default]
508    Empty,
509
510    Static {
511        content: StaticContent,
512        cursor: usize,
513    },
514
515    Streaming {
516        async_read: SyncWrapper<Pin<Box<dyn BodySource>>>,
517        progress: u64,
518        len: Option<u64>,
519        done: bool,
520        /// When true (the default), [`Body`]'s [`AsyncRead`] impl emits chunked
521        /// framing for the `len: None` case; when false (via
522        /// [`Body::without_chunked_framing`]), it passes through raw bytes.
523        chunked_framing: bool,
524        /// When true (via [`Body::keep_open`]), the chunked `len: None` read does not
525        /// emit the `0\r\n` last-chunk marker at EOF — the outbound stream is left open
526        /// for a following upgrade to terminate. Default false.
527        keep_open: bool,
528    },
529}
530
531impl Debug for BodyType {
532    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
533        match self {
534            Empty => f.debug_tuple("BodyType::Empty").finish(),
535            Static { content, cursor } => f
536                .debug_struct("BodyType::Static")
537                .field("content", &String::from_utf8_lossy(content))
538                .field("cursor", cursor)
539                .finish(),
540            Streaming {
541                len,
542                done,
543                progress,
544                ..
545            } => f
546                .debug_struct("BodyType::Streaming")
547                .field("async_read", &format_args!(".."))
548                .field("len", &len)
549                .field("done", &done)
550                .field("progress", &progress)
551                .finish(),
552        }
553    }
554}
555
556impl BodyType {
557    fn is_empty(&self) -> bool {
558        match *self {
559            Empty => true,
560            Static { ref content, .. } => content.is_empty(),
561            Streaming { len, .. } => len == Some(0),
562        }
563    }
564
565    fn len(&self) -> Option<u64> {
566        match *self {
567            Empty => Some(0),
568            Static { ref content, .. } => Some(content.len() as u64),
569            Streaming { len, .. } => len,
570        }
571    }
572
573    fn bytes_read(&self) -> u64 {
574        match *self {
575            Empty => 0,
576            Static { cursor, .. } => cursor as u64,
577            Streaming { progress, .. } => progress,
578        }
579    }
580}
581
582impl From<String> for Body {
583    fn from(s: String) -> Self {
584        s.into_bytes().into()
585    }
586}
587
588impl From<&'static str> for Body {
589    fn from(s: &'static str) -> Self {
590        s.as_bytes().into()
591    }
592}
593
594impl From<&'static [u8]> for Body {
595    fn from(content: &'static [u8]) -> Self {
596        Self::new_static(content)
597    }
598}
599
600impl From<Vec<u8>> for Body {
601    fn from(content: Vec<u8>) -> Self {
602        Self::new_static(content)
603    }
604}
605
606impl From<Cow<'static, [u8]>> for Body {
607    fn from(value: Cow<'static, [u8]>) -> Self {
608        Self::new_static(value)
609    }
610}
611
612impl From<Cow<'static, str>> for Body {
613    fn from(value: Cow<'static, str>) -> Self {
614        match value {
615            Cow::Borrowed(b) => b.into(),
616            Cow::Owned(o) => o.into(),
617        }
618    }
619}
620
621impl From<Arc<[u8]>> for Body {
622    fn from(content: Arc<[u8]>) -> Self {
623        Self(Static {
624            content: StaticContent::Bytes(content),
625            cursor: 0,
626        })
627    }
628}
629
630impl From<Arc<str>> for Body {
631    fn from(content: Arc<str>) -> Self {
632        Self(Static {
633            content: StaticContent::Str(content),
634            cursor: 0,
635        })
636    }
637}
638
639#[cfg(test)]
640mod test_shared_content {
641    use super::Body;
642    use futures_lite::future::block_on;
643    use std::sync::Arc;
644
645    #[test]
646    fn arc_bytes_roundtrips() {
647        let arc: Arc<[u8]> = Arc::from(&b"shared bytes"[..]);
648        let body = Body::from(Arc::clone(&arc));
649        assert_eq!(body.len(), Some(12));
650        assert_eq!(body.static_bytes(), Some(&b"shared bytes"[..]));
651        assert_eq!(
652            block_on(body.into_bytes()).unwrap().as_ref(),
653            b"shared bytes"
654        );
655        // the source Arc is still usable — the body shared, not consumed, the buffer
656        assert_eq!(&*arc, b"shared bytes");
657    }
658
659    #[test]
660    fn arc_str_roundtrips() {
661        let arc: Arc<str> = Arc::from("shared str");
662        let body = Body::from(arc);
663        assert_eq!(body.len(), Some(10));
664        assert_eq!(body.static_bytes(), Some(&b"shared str"[..]));
665        assert_eq!(block_on(body.into_bytes()).unwrap().as_ref(), b"shared str");
666    }
667
668    #[cfg(feature = "unstable")]
669    #[test]
670    fn shared_body_clones_without_copying_the_arc() {
671        let arc: Arc<[u8]> = Arc::from(&b"abc"[..]);
672        let body = Body::from(Arc::clone(&arc));
673        let clone = body.try_clone().expect("static bodies clone");
674        assert_eq!(clone.static_bytes(), Some(&b"abc"[..]));
675        // original + body + clone all reference the same allocation
676        assert_eq!(Arc::strong_count(&arc), 3);
677    }
678}
679
680#[cfg(test)]
681mod test_bytes_to_read {
682    #[test]
683    fn simple_check_of_known_values() {
684        // the marked rows are the most important part of this test,
685        // and a nonobvious but intentional consequence of the
686        // implementation. in order to avoid overflowing, we must use
687        // one fewer than the available buffer bytes because
688        // increasing the read size increase the number of framed
689        // bytes by two. This occurs when the hex representation of
690        // the content bytes is near an increase in order of magnitude
691        // (F->10, FF->100, FFF-> 1000, etc)
692        let values = vec![
693            (6, 1),       // 1
694            (7, 2),       // 2
695            (20, 15),     // F
696            (21, 15),     // F <-
697            (22, 16),     // 10
698            (23, 17),     // 11
699            (260, 254),   // FE
700            (261, 254),   // FE <-
701            (262, 255),   // FF <-
702            (263, 256),   // 100
703            (4100, 4093), // FFD
704            (4101, 4093), // FFD <-
705            (4102, 4094), // FFE <-
706            (4103, 4095), // FFF <-
707            (4104, 4096), // 1000
708        ];
709
710        for (input, expected) in values {
711            let actual = super::max_bytes_to_read(input);
712            assert_eq!(
713                actual, expected,
714                "\n\nexpected max_bytes_to_read({input}) to be {expected}, but it was {actual}"
715            );
716
717            // testing the test:
718            let used_bytes = expected + 4 + format!("{expected:X}").len();
719            assert!(
720                used_bytes == input || used_bytes == input - 1,
721                "\n\nfor an input of {}, expected used bytes to be {} or {}, but was {}",
722                input,
723                input,
724                input - 1,
725                used_bytes
726            );
727        }
728    }
729}
730
731#[cfg(test)]
732mod test_into_bytes {
733    use super::Body;
734    use futures_lite::{future::block_on, io::Cursor};
735
736    #[test]
737    fn a_streaming_body_fills_its_known_length_exactly() {
738        let body = Body::new_streaming(Cursor::new(vec![b'x'; 5000]), Some(5000));
739        let bytes = block_on(body.into_bytes()).unwrap().into_owned();
740        assert_eq!(bytes.len(), 5000);
741        assert_eq!(
742            bytes.capacity(),
743            5000,
744            "the body was reallocated after it was complete"
745        );
746    }
747
748    #[test]
749    fn a_declared_length_does_not_dictate_the_allocation() {
750        let body = Body::new_streaming(Cursor::new(b"hello".to_vec()), Some(u64::MAX));
751        assert_eq!(block_on(body.into_bytes()).unwrap().as_ref(), b"hello");
752    }
753}