http-body

repository·master·Indexed 41 days ago

https://github.com/hyperium/http-body

A Rust crate providing a trait for asynchronous operations on HTTP bodies, designed to handle streaming data in asynchronous environments. Version 1.1.0 includes the http-body-util crate, which offers utility extension traits (BodyExt), concrete implementations like Channel, Empty, and ErrorBody, and combinators such as Fuse, Either, and BoxBody for body manipulation, transformation, and composition.

Tokens
7.5K
Snippets
30
Records
40
Agent score
48%

What's inside http-body

  1. The Body trait

    master

    The Body trait represents an asynchronous streaming body for an HTTP Request or Response. It yields individual Frame<Self::Data> instances via the poll_frame method.

    Key Concepts

    • Frames: The unit of streaming. A frame can contain a data buffer of type Self::Data or an optional set of trailers (common in HTTP/2.0).
    • Streaming Lifecycle: You pull frames using poll_frame. The stream ends when poll_frame returns Poll::Ready(None).
    • Error Handling: If poll_frame returns Poll::Ready(Some(Err(error))), the body should be considered broken and discarded.
    • Optimization Hints:
      • is_end_stream(): Returns true if the end of the stream has been reached. This allows consumers to avoid unnecessary calls to poll_frame.
      • size_hint(): Provides an estimate of the remaining bytes in the stream. If the exact length is known, the upper and lower bounds in the SizeHint will be equal.
    pub trait Body {
        type Data: Buf;
        type Error;
    
        fn poll_frame(
            self: Pin<&mut Self>,
            cx: &mut Context<'_>,
        ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>>;
    
        fn is_end_stream(&self) -> bool;
    
        fn size_hint(&self) -> SizeHint;
    }
  2. Use the Either type to handle two possible body types

    master

    The Either<L, R> enum is a sum type used when a value (specifically an HTTP body) can be one of two distinct types. It allows you to wrap two different types into a single type that implements the Body trait, provided both types share the same Data type and their errors can be converted into Box<dyn Error + Send + Sync>.

    Variants

    • Either::Left(L): Holds a value of type L.
    • Either::Right(R): Holds a value of type R.

    Implementation of Body

    When both L and R implement Body with the same Data type, Either<L, R> also implements Body. This is useful for middleware or combinators that need to return a body that might originate from different sources.

    // Example: Wrapping two different body types into one
    let body: Either<Full<Bytes>, Empty<Bytes>> = Either::Left(Full::new(bytes));
  3. Use `SizeHint` to manage body length information

    master

    The SizeHint struct provides a way to represent the estimated size of data in an HTTP body using a lower bound and an optional upper bound.

    • Default state: A new SizeHint (via new() or Default::default()) has a lower bound of 0 and an upper bound of None.
    • Exact size: If both the lower and upper bounds are equal, the size is considered exact. You can set this using with_exact(value) or set_exact(value).
    • Bounds:
      • lower() returns the minimum guaranteed bytes.
      • upper() returns the maximum possible bytes as an Option<u64> (None if unknown).
    • Arithmetic: You can add two SizeHint instances together using the + operator. The resulting SizeHint will have a lower bound equal to the sum of the two lower bounds, and an upper bound that is the sum of the two upper bounds (if both are Some). If either upper bound is None, the resulting upper bound is None.
    use http_body::SizeHint;
    
    // Create a hint with an exact size
    let hint = SizeHint::with_exact(1024);
    assert_eq!(hint.lower(), 1024);
    assert_eq!(hint.upper(), Some(1024));
    assert_eq!(hint.exact(), Some(1024));
    
    // Combine hints
    let hint1 = SizeHint::with_exact(100);
    let hint2 = SizeHint::with_exact(200);
    let combined = hint1 + hint2;
    assert_eq!(combined.exact(), Some(300));
  4. Use the Fuse combinator to prevent polling after completion

    master

    The Fuse<B> struct is a combinator that wraps an underlying Body to ensure it is not polled again after it has finished.

    This is useful for implementations that do not guarantee they will continue to return Poll::Ready(None) indefinitely after the end of the stream is reached. Once the underlying body yields Poll::Ready(None) or a single error Poll::Ready(Some(Err(_))), Fuse<B> will yield Poll::Ready(None) forever and will stop polling the inner body.

    This behavior is analogous to std::iter::Iterator::fuse() for iterators.

    // Example conceptual usage
    let fused_body = Fuse::new(original_body);
  5. Use String as a Body

    master

    The String type implements Body for convenience. It treats the entire string as a single data frame and returns Infallible as the error type.

    • Data Type: bytes::Bytes
    • Error Type: std::convert::Infallible
    • Behavior: poll_frame returns the string content as one frame and then returns None on subsequent calls.
    // String implements Body
    // Data = Bytes, Error = Infallible
  6. Use the Collected body type to access buffered data and trailers

    master

    The Collected<B> struct is produced by calling BodyExt::collect on an HTTP body. It buffers all DATA frames and any trailers sent by the body. This is useful when you need to consume the entire body at once or inspect the HTTP trailers.

    Key methods:

    • to_bytes(): Consumes the Collected body and returns the aggregated data as a single Bytes object.
    • aggregate(): Consumes the Collected body and returns the underlying buffers as an object implementing the Buf trait.
    • trailers(): Returns a reference to the HeaderMap containing the body's trailers, if any.
    • take_trailers(): Removes and returns the HeaderMap containing the body's trailers.
    // Example: Collecting a body and converting to Bytes
    let buffered = body.collect().await.unwrap();
    let mut buf = buffered.to_bytes();
  7. Use the BodyExt trait to manipulate HTTP bodies

    master

    The BodyExt trait provides a suite of extension methods for any type implementing http_body::Body. It allows you to transform, inspect, and combine bodies using functional combinators. Common operations include mapping errors, mapping frames, boxing the body for type erasure, and limiting the body size.

    use http_body_util::{BodyExt, Full};
    use bytes::Bytes;
    
    // Example: Chaining two bodies together
    let first = Full::new(Bytes::from("hello "));
    let second = Full::new(Bytes::from("world!"));
    let chained = first.chain(second);
    
    // Example: Limiting body size
    let limited_body = first.limited(1024);
  8. Wrap a Body with BoxBody for dynamic dispatch

    master

    Use BoxBody<D, E> to erase the specific type of an HTTP body while preserving its Data (D) and Error (E) types. This is useful when you need to return different body implementations from a single function or store them in a collection. BoxBody requires the underlying body to be Send + Sync + 'static.

    To create a new BoxBody, use BoxBody::new(body) where body implements Body and Data implements Buf.

    use http_body_util::BoxBody;
    use http_body::Body;
    
    // Assuming 'my_body' is some type implementing Body<Data = Bytes, Error = MyError>
    let boxed_body = BoxBody::new(my_body);
  9. Create a failing body with ErrorBody

    master

    The ErrorBody<D, E> struct implements the Body trait and is designed to yield an error of type E immediately when polled. It is primarily used for testing error-handling logic in asynchronous code.

    To use it, provide the error value via ErrorBody::new(error). The generic parameter D represents the type of data the body would have yielded if it hadn't failed, allowing it to satisfy type requirements for functions expecting specific body data types.

    use bytes::Bytes;
    use http_body_util::{BodyExt, ErrorBody};
    
    #[tokio::main]
    async fn main() {
        let mut body = ErrorBody::<Bytes, &str>::new("problem");
        let frame = body.frame().await;
        assert_eq!(
            frame.unwrap().unwrap_err(),
            "problem",
        );
    }
  10. Create a fused body with Fuse::new

    master

    To create a fused body, use Fuse::new(body).

    If the provided body already reports that the stream has ended via is_end_stream(), the resulting Fuse<B> will immediately report is_end_stream() == true and will not poll the inner body.

    impl<B> Fuse<B>
    where
        B: Body,
    {
        /// Returns a fused body.
        pub fn new(body: B) -> Self {
            Self {
                inner: if body.is_end_stream() {
                    None
                } else {
                    Some(body)
                },
            }
        }
    }