├── .github └── workflows │ └── CI.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── http-body-util ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE └── src │ ├── channel.rs │ ├── collected.rs │ ├── combinators │ ├── box_body.rs │ ├── collect.rs │ ├── frame.rs │ ├── fuse.rs │ ├── map_err.rs │ ├── map_frame.rs │ ├── mod.rs │ └── with_trailers.rs │ ├── either.rs │ ├── empty.rs │ ├── full.rs │ ├── lib.rs │ ├── limited.rs │ ├── stream.rs │ └── util.rs └── http-body ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── src ├── frame.rs ├── lib.rs └── size_hint.rs └── tests └── is_end_stream.rs /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | ci-pass: 10 | name: CI is green 11 | runs-on: ubuntu-latest 12 | needs: 13 | - style 14 | - test 15 | - msrv 16 | - minimal-versions 17 | - clippy 18 | - doc 19 | steps: 20 | - run: exit 0 21 | 22 | 23 | style: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: dtolnay/rust-toolchain@stable 28 | with: 29 | components: rustfmt 30 | - name: Check formatting 31 | run: cargo fmt --all --check 32 | 33 | test: 34 | needs: [style] 35 | runs-on: ubuntu-latest 36 | strategy: 37 | matrix: 38 | rust: 39 | - stable 40 | - beta 41 | steps: 42 | - uses: actions/checkout@v4 43 | - uses: dtolnay/rust-toolchain@master 44 | with: 45 | toolchain: ${{ matrix.rust }} 46 | - name: Run tests 47 | run: cargo test --workspace 48 | 49 | msrv: 50 | needs: [style] 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v4 54 | - uses: taiki-e/install-action@cargo-hack 55 | - run: cargo hack --rust-version --no-dev-deps check 56 | 57 | minimal-versions: 58 | needs: [style] 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v4 62 | - uses: dtolnay/rust-toolchain@nightly 63 | - uses: dtolnay/rust-toolchain@stable 64 | - uses: taiki-e/install-action@cargo-hack 65 | - uses: taiki-e/install-action@cargo-minimal-versions 66 | - run: cargo minimal-versions check 67 | 68 | clippy: 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v4 72 | - name: Run Clippy 73 | run: cargo clippy --all-targets --all-features 74 | 75 | doc: 76 | needs: [style] 77 | runs-on: ubuntu-latest 78 | steps: 79 | - uses: actions/checkout@v4 80 | - uses: dtolnay/rust-toolchain@stable 81 | - run: cargo doc --no-deps 82 | env: 83 | RUSTDOCFLAGS: -D rustdoc::broken-intra-doc-links 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["http-body", "http-body-util"] 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2025 Sean McArthur & Hyper Contributors 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP Body 2 | 3 | A trait representing asynchronous operations on an HTTP body. 4 | 5 | [![crates.io][crates-badge]][crates-url] 6 | [![documentation][docs-badge]][docs-url] 7 | [![MIT License][mit-badge]][mit-url] 8 | [![CI Status][ci-badge]][ci-url] 9 | 10 | [crates-badge]: https://img.shields.io/crates/v/http-body.svg 11 | [crates-url]: https://crates.io/crates/http-body 12 | [docs-badge]: https://docs.rs/http-body/badge.svg 13 | [docs-url]: https://docs.rs/http-body 14 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 15 | [mit-url]: LICENSE 16 | [ci-badge]: https://github.com/hyperium/http-body/workflows/CI/badge.svg 17 | [ci-url]: https://github.com/hyperium/http-body/actions?query=workflow%3ACI 18 | 19 | ## License 20 | 21 | This project is licensed under the [MIT license](LICENSE). 22 | 23 | ### Contribution 24 | 25 | Unless you explicitly state otherwise, any contribution intentionally submitted 26 | for inclusion in `http-body` by you, shall be licensed as MIT, without any additional 27 | terms or conditions. 28 | -------------------------------------------------------------------------------- /http-body-util/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v0.1.3 2 | 3 | - Add `Channel`, a body type backed by an async channel. 4 | - Make `Empty::new()` to be `const fn`. 5 | 6 | # v0.1.2 7 | 8 | - Add `BodyDataStream` type to convert a body to a stream of its data. 9 | 10 | # v0.1.1 11 | 12 | - Add `BodyExt::with_trailers()` combinator. 13 | - Improve performance of `BodyExt::collect().to_bytes()`. 14 | 15 | # v0.1.0 16 | 17 | - Update `http` to 1.0. 18 | - Update `http-body` to 1.0. 19 | 20 | # v0.1.0-rc.3 21 | 22 | - Fix `BodyExt::collect()` from panicking on an empty frame. 23 | 24 | # v0.1.0-rc.2 25 | 26 | - Update to `http-body` rc.2. 27 | 28 | # v0.1.0-rc.1 29 | 30 | - Initial release, split from http-body 0.4.5. 31 | -------------------------------------------------------------------------------- /http-body-util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-body-util" 3 | # When releasing to crates.io: 4 | # - Remove path dependencies 5 | # - Update html_root_url. 6 | # - Update doc url 7 | # - Cargo.toml 8 | # - README.md 9 | # - Update CHANGELOG.md. 10 | # - Create "http-body-util-x.y.z" git tag. 11 | version = "0.1.3" 12 | authors = [ 13 | "Carl Lerche ", 14 | "Lucio Franco ", 15 | "Sean McArthur ", 16 | ] 17 | edition = "2018" 18 | readme = "../README.md" 19 | documentation = "https://docs.rs/http-body-util" 20 | repository = "https://github.com/hyperium/http-body" 21 | license = "MIT" 22 | description = """ 23 | Combinators and adapters for HTTP request or response bodies. 24 | """ 25 | keywords = ["http"] 26 | categories = ["web-programming"] 27 | rust-version = "1.61" 28 | 29 | [features] 30 | default = [] 31 | channel = ["dep:tokio"] 32 | full = ["channel"] 33 | 34 | [dependencies] 35 | bytes = "1" 36 | futures-core = { version = "0.3", default-features = false } 37 | http = "1" 38 | http-body = { version = "1", path = "../http-body" } 39 | pin-project-lite = "0.2" 40 | 41 | # optional dependencies 42 | tokio = { version = "1", features = ["sync"], optional = true } 43 | 44 | [dev-dependencies] 45 | futures-util = { version = "0.3", default-features = false } 46 | tokio = { version = "1", features = ["macros", "rt", "sync", "rt-multi-thread"] } 47 | 48 | [package.metadata.docs.rs] 49 | all-features = true 50 | rustdoc-args = ["--cfg", "docsrs"] 51 | -------------------------------------------------------------------------------- /http-body-util/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /http-body-util/src/channel.rs: -------------------------------------------------------------------------------- 1 | //! A body backed by a channel. 2 | 3 | use std::{ 4 | fmt::Display, 5 | pin::Pin, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | use bytes::Buf; 10 | use http::HeaderMap; 11 | use http_body::{Body, Frame}; 12 | use pin_project_lite::pin_project; 13 | use tokio::sync::{mpsc, oneshot}; 14 | 15 | pin_project! { 16 | /// A body backed by a channel. 17 | pub struct Channel { 18 | rx_frame: mpsc::Receiver>, 19 | #[pin] 20 | rx_error: oneshot::Receiver, 21 | } 22 | } 23 | 24 | impl Channel { 25 | /// Create a new channel body. 26 | /// 27 | /// The channel will buffer up to the provided number of messages. Once the buffer is full, 28 | /// attempts to send new messages will wait until a message is received from the channel. The 29 | /// provided buffer capacity must be at least 1. 30 | pub fn new(buffer: usize) -> (Sender, Self) { 31 | let (tx_frame, rx_frame) = mpsc::channel(buffer); 32 | let (tx_error, rx_error) = oneshot::channel(); 33 | (Sender { tx_frame, tx_error }, Self { rx_frame, rx_error }) 34 | } 35 | } 36 | 37 | impl Body for Channel 38 | where 39 | D: Buf, 40 | { 41 | type Data = D; 42 | type Error = E; 43 | 44 | fn poll_frame( 45 | self: Pin<&mut Self>, 46 | cx: &mut Context<'_>, 47 | ) -> Poll, Self::Error>>> { 48 | let this = self.project(); 49 | 50 | match this.rx_frame.poll_recv(cx) { 51 | Poll::Ready(frame @ Some(_)) => return Poll::Ready(frame.map(Ok)), 52 | Poll::Ready(None) | Poll::Pending => {} 53 | } 54 | 55 | use core::future::Future; 56 | match this.rx_error.poll(cx) { 57 | Poll::Ready(Ok(error)) => return Poll::Ready(Some(Err(error))), 58 | Poll::Ready(Err(_)) => return Poll::Ready(None), 59 | Poll::Pending => {} 60 | } 61 | 62 | Poll::Pending 63 | } 64 | } 65 | 66 | impl std::fmt::Debug for Channel { 67 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 68 | f.debug_struct("Channel") 69 | .field("rx_frame", &self.rx_frame) 70 | .field("rx_error", &self.rx_error) 71 | .finish() 72 | } 73 | } 74 | 75 | /// A sender half created through [`Channel::new`]. 76 | pub struct Sender { 77 | tx_frame: mpsc::Sender>, 78 | tx_error: oneshot::Sender, 79 | } 80 | 81 | impl Sender { 82 | /// Send a frame on the channel. 83 | pub async fn send(&mut self, frame: Frame) -> Result<(), SendError> { 84 | self.tx_frame.send(frame).await.map_err(|_| SendError) 85 | } 86 | 87 | /// Send data on data channel. 88 | pub async fn send_data(&mut self, buf: D) -> Result<(), SendError> { 89 | self.send(Frame::data(buf)).await 90 | } 91 | 92 | /// Send trailers on trailers channel. 93 | pub async fn send_trailers(&mut self, trailers: HeaderMap) -> Result<(), SendError> { 94 | self.send(Frame::trailers(trailers)).await 95 | } 96 | 97 | /// Attempts to send a frame on this channel. 98 | /// 99 | /// This function returns the unsent frame back as an `Err(_)` if the channel could not 100 | /// (currently) accept another frame. 101 | /// 102 | /// # Note 103 | /// 104 | /// This is mostly useful for when trying to send a frame from outside of an asynchronous 105 | /// context. If in an async context, prefer [`Sender::send_data()`] instead. 106 | pub fn try_send(&mut self, frame: Frame) -> Result<(), Frame> { 107 | let Self { 108 | tx_frame, 109 | tx_error: _, 110 | } = self; 111 | 112 | tx_frame 113 | .try_send(frame) 114 | .map_err(tokio::sync::mpsc::error::TrySendError::into_inner) 115 | } 116 | 117 | /// Returns the current capacity of the channel. 118 | /// 119 | /// The capacity goes down when [`Frame`]s are sent. The capacity goes up when these frames 120 | /// are received by the corresponding [`Channel`]. This is distinct from 121 | /// [`max_capacity()`][Self::max_capacity], which always returns the buffer capacity initially 122 | /// specified when [`Channel::new()`][Channel::new] was called. 123 | /// 124 | /// # Examples 125 | /// 126 | /// ``` 127 | /// use bytes::Bytes; 128 | /// use http_body_util::{BodyExt, channel::Channel}; 129 | /// use std::convert::Infallible; 130 | /// 131 | /// #[tokio::main] 132 | /// async fn main() { 133 | /// let (mut tx, mut body) = Channel::::new(4); 134 | /// assert_eq!(tx.capacity(), 4); 135 | /// 136 | /// // Sending a value decreases the available capacity. 137 | /// tx.send_data(Bytes::from("Hel")).await.unwrap(); 138 | /// assert_eq!(tx.capacity(), 3); 139 | /// 140 | /// // Reading a value increases the available capacity. 141 | /// let _ = body.frame().await; 142 | /// assert_eq!(tx.capacity(), 4); 143 | /// } 144 | /// ``` 145 | pub fn capacity(&mut self) -> usize { 146 | self.tx_frame.capacity() 147 | } 148 | 149 | /// Returns the maximum capacity of the channel. 150 | /// 151 | /// This function always returns the buffer capacity initially specified when 152 | /// [`Channel::new()`][Channel::new] was called. This is distinct from 153 | /// [`capacity()`][Self::capacity], which returns the currently available capacity. 154 | /// 155 | /// # Examples 156 | /// 157 | /// ``` 158 | /// use bytes::Bytes; 159 | /// use http_body_util::{BodyExt, channel::Channel}; 160 | /// use std::convert::Infallible; 161 | /// 162 | /// #[tokio::main] 163 | /// async fn main() { 164 | /// let (mut tx, mut body) = Channel::::new(4); 165 | /// assert_eq!(tx.max_capacity(), 4); 166 | /// 167 | /// // Sending a value buffers it, but does not affect the maximum capacity reported. 168 | /// tx.send_data(Bytes::from("Hel")).await.unwrap(); 169 | /// assert_eq!(tx.max_capacity(), 4); 170 | /// } 171 | /// ``` 172 | pub fn max_capacity(&mut self) -> usize { 173 | self.tx_frame.max_capacity() 174 | } 175 | 176 | /// Aborts the body in an abnormal fashion. 177 | pub fn abort(self, error: E) { 178 | self.tx_error.send(error).ok(); 179 | } 180 | } 181 | 182 | impl std::fmt::Debug for Sender { 183 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 184 | f.debug_struct("Sender") 185 | .field("tx_frame", &self.tx_frame) 186 | .field("tx_error", &self.tx_error) 187 | .finish() 188 | } 189 | } 190 | 191 | /// The error returned if [`Sender`] fails to send because the receiver is closed. 192 | #[derive(Debug)] 193 | #[non_exhaustive] 194 | pub struct SendError; 195 | 196 | impl Display for SendError { 197 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 198 | write!(f, "failed to send frame") 199 | } 200 | } 201 | 202 | impl std::error::Error for SendError {} 203 | 204 | #[cfg(test)] 205 | mod tests { 206 | use bytes::Bytes; 207 | use http::{HeaderName, HeaderValue}; 208 | 209 | use crate::BodyExt; 210 | 211 | use super::*; 212 | 213 | #[tokio::test] 214 | async fn empty() { 215 | let (tx, body) = Channel::::new(1024); 216 | drop(tx); 217 | 218 | let collected = body.collect().await.unwrap(); 219 | assert!(collected.trailers().is_none()); 220 | assert!(collected.to_bytes().is_empty()); 221 | } 222 | 223 | #[tokio::test] 224 | async fn can_send_data() { 225 | let (mut tx, body) = Channel::::new(1024); 226 | 227 | tokio::spawn(async move { 228 | tx.send_data(Bytes::from("Hel")).await.unwrap(); 229 | tx.send_data(Bytes::from("lo!")).await.unwrap(); 230 | }); 231 | 232 | let collected = body.collect().await.unwrap(); 233 | assert!(collected.trailers().is_none()); 234 | assert_eq!(collected.to_bytes(), "Hello!"); 235 | } 236 | 237 | #[tokio::test] 238 | async fn can_send_trailers() { 239 | let (mut tx, body) = Channel::::new(1024); 240 | 241 | tokio::spawn(async move { 242 | let mut trailers = HeaderMap::new(); 243 | trailers.insert( 244 | HeaderName::from_static("foo"), 245 | HeaderValue::from_static("bar"), 246 | ); 247 | tx.send_trailers(trailers).await.unwrap(); 248 | }); 249 | 250 | let collected = body.collect().await.unwrap(); 251 | assert_eq!(collected.trailers().unwrap()["foo"], "bar"); 252 | assert!(collected.to_bytes().is_empty()); 253 | } 254 | 255 | #[tokio::test] 256 | async fn can_send_both_data_and_trailers() { 257 | let (mut tx, body) = Channel::::new(1024); 258 | 259 | tokio::spawn(async move { 260 | tx.send_data(Bytes::from("Hel")).await.unwrap(); 261 | tx.send_data(Bytes::from("lo!")).await.unwrap(); 262 | let mut trailers = HeaderMap::new(); 263 | trailers.insert( 264 | HeaderName::from_static("foo"), 265 | HeaderValue::from_static("bar"), 266 | ); 267 | tx.send_trailers(trailers).await.unwrap(); 268 | }); 269 | 270 | let collected = body.collect().await.unwrap(); 271 | assert_eq!(collected.trailers().unwrap()["foo"], "bar"); 272 | assert_eq!(collected.to_bytes(), "Hello!"); 273 | } 274 | 275 | #[tokio::test] 276 | async fn try_send_works() { 277 | let (mut tx, mut body) = Channel::::new(2); 278 | 279 | // Send two messages, filling the channel's buffer. 280 | tx.try_send(Frame::data(Bytes::from("one"))) 281 | .expect("can send one message"); 282 | tx.try_send(Frame::data(Bytes::from("two"))) 283 | .expect("can send two messages"); 284 | 285 | // Sending a value to a full channel should return it back to us. 286 | match tx.try_send(Frame::data(Bytes::from("three"))) { 287 | Err(frame) => assert_eq!(frame.into_data().unwrap(), "three"), 288 | Ok(()) => panic!("synchronously sending a value to a full channel should fail"), 289 | }; 290 | 291 | // Read the messages out of the body. 292 | assert_eq!( 293 | body.frame() 294 | .await 295 | .expect("yields result") 296 | .expect("yields frame") 297 | .into_data() 298 | .expect("yields data"), 299 | "one" 300 | ); 301 | assert_eq!( 302 | body.frame() 303 | .await 304 | .expect("yields result") 305 | .expect("yields frame") 306 | .into_data() 307 | .expect("yields data"), 308 | "two" 309 | ); 310 | 311 | // Drop the body. 312 | drop(body); 313 | 314 | // Sending a value to a closed channel should return it back to us. 315 | match tx.try_send(Frame::data(Bytes::from("closed"))) { 316 | Err(frame) => assert_eq!(frame.into_data().unwrap(), "closed"), 317 | Ok(()) => panic!("synchronously sending a value to a closed channel should fail"), 318 | }; 319 | } 320 | 321 | /// A stand-in for an error type, for unit tests. 322 | type Error = &'static str; 323 | /// An example error message. 324 | const MSG: Error = "oh no"; 325 | 326 | #[tokio::test] 327 | async fn aborts_before_trailers() { 328 | let (mut tx, body) = Channel::::new(1024); 329 | 330 | tokio::spawn(async move { 331 | tx.send_data(Bytes::from("Hel")).await.unwrap(); 332 | tx.send_data(Bytes::from("lo!")).await.unwrap(); 333 | tx.abort(MSG); 334 | }); 335 | 336 | let err = body.collect().await.unwrap_err(); 337 | assert_eq!(err, MSG); 338 | } 339 | 340 | #[tokio::test] 341 | async fn aborts_after_trailers() { 342 | let (mut tx, body) = Channel::::new(1024); 343 | 344 | tokio::spawn(async move { 345 | tx.send_data(Bytes::from("Hel")).await.unwrap(); 346 | tx.send_data(Bytes::from("lo!")).await.unwrap(); 347 | let mut trailers = HeaderMap::new(); 348 | trailers.insert( 349 | HeaderName::from_static("foo"), 350 | HeaderValue::from_static("bar"), 351 | ); 352 | tx.send_trailers(trailers).await.unwrap(); 353 | tx.abort(MSG); 354 | }); 355 | 356 | let err = body.collect().await.unwrap_err(); 357 | assert_eq!(err, MSG); 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /http-body-util/src/collected.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::Infallible, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use bytes::{Buf, Bytes}; 8 | use http::HeaderMap; 9 | use http_body::{Body, Frame}; 10 | 11 | use crate::util::BufList; 12 | 13 | /// A collected body produced by [`BodyExt::collect`] which collects all the DATA frames 14 | /// and trailers. 15 | /// 16 | /// [`BodyExt::collect`]: crate::BodyExt::collect 17 | #[derive(Debug)] 18 | pub struct Collected { 19 | bufs: BufList, 20 | trailers: Option, 21 | } 22 | 23 | impl Collected { 24 | /// If there is a trailers frame buffered, returns a reference to it. 25 | /// 26 | /// Returns `None` if the body contained no trailers. 27 | pub fn trailers(&self) -> Option<&HeaderMap> { 28 | self.trailers.as_ref() 29 | } 30 | 31 | /// Aggregate this buffered into a [`Buf`]. 32 | pub fn aggregate(self) -> impl Buf { 33 | self.bufs 34 | } 35 | 36 | /// Convert this body into a [`Bytes`]. 37 | pub fn to_bytes(mut self) -> Bytes { 38 | self.bufs.copy_to_bytes(self.bufs.remaining()) 39 | } 40 | 41 | pub(crate) fn push_frame(&mut self, frame: Frame) { 42 | let frame = match frame.into_data() { 43 | Ok(data) => { 44 | // Only push this frame if it has some data in it, to avoid crashing on 45 | // `BufList::push`. 46 | if data.has_remaining() { 47 | self.bufs.push(data); 48 | } 49 | return; 50 | } 51 | Err(frame) => frame, 52 | }; 53 | 54 | if let Ok(trailers) = frame.into_trailers() { 55 | if let Some(current) = &mut self.trailers { 56 | current.extend(trailers); 57 | } else { 58 | self.trailers = Some(trailers); 59 | } 60 | }; 61 | } 62 | } 63 | 64 | impl Body for Collected { 65 | type Data = B; 66 | type Error = Infallible; 67 | 68 | fn poll_frame( 69 | mut self: Pin<&mut Self>, 70 | _: &mut Context<'_>, 71 | ) -> Poll, Self::Error>>> { 72 | let frame = if let Some(data) = self.bufs.pop() { 73 | Frame::data(data) 74 | } else if let Some(trailers) = self.trailers.take() { 75 | Frame::trailers(trailers) 76 | } else { 77 | return Poll::Ready(None); 78 | }; 79 | 80 | Poll::Ready(Some(Ok(frame))) 81 | } 82 | } 83 | 84 | impl Default for Collected { 85 | fn default() -> Self { 86 | Self { 87 | bufs: BufList::default(), 88 | trailers: None, 89 | } 90 | } 91 | } 92 | 93 | impl Unpin for Collected {} 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use std::convert::TryInto; 98 | 99 | use futures_util::stream; 100 | 101 | use crate::{BodyExt, Full, StreamBody}; 102 | 103 | use super::*; 104 | 105 | #[tokio::test] 106 | async fn full_body() { 107 | let body = Full::new(&b"hello"[..]); 108 | 109 | let buffered = body.collect().await.unwrap(); 110 | 111 | let mut buf = buffered.to_bytes(); 112 | 113 | assert_eq!(&buf.copy_to_bytes(buf.remaining())[..], &b"hello"[..]); 114 | } 115 | 116 | #[tokio::test] 117 | async fn segmented_body() { 118 | let bufs = [&b"hello"[..], &b"world"[..], &b"!"[..]]; 119 | let body = StreamBody::new(stream::iter(bufs.map(Frame::data).map(Ok::<_, Infallible>))); 120 | 121 | let buffered = body.collect().await.unwrap(); 122 | 123 | let mut buf = buffered.to_bytes(); 124 | 125 | assert_eq!(&buf.copy_to_bytes(buf.remaining())[..], b"helloworld!"); 126 | } 127 | 128 | #[tokio::test] 129 | async fn delayed_segments() { 130 | let one = stream::once(async { Ok::<_, Infallible>(Frame::data(&b"hello "[..])) }); 131 | let two = stream::once(async { 132 | // a yield just so its not ready immediately 133 | tokio::task::yield_now().await; 134 | Ok::<_, Infallible>(Frame::data(&b"world!"[..])) 135 | }); 136 | let stream = futures_util::StreamExt::chain(one, two); 137 | 138 | let body = StreamBody::new(stream); 139 | 140 | let buffered = body.collect().await.unwrap(); 141 | 142 | let mut buf = buffered.to_bytes(); 143 | 144 | assert_eq!(&buf.copy_to_bytes(buf.remaining())[..], b"hello world!"); 145 | } 146 | 147 | #[tokio::test] 148 | async fn trailers() { 149 | let mut trailers = HeaderMap::new(); 150 | trailers.insert("this", "a trailer".try_into().unwrap()); 151 | let bufs = [ 152 | Frame::data(&b"hello"[..]), 153 | Frame::data(&b"world!"[..]), 154 | Frame::trailers(trailers.clone()), 155 | ]; 156 | 157 | let body = StreamBody::new(stream::iter(bufs.map(Ok::<_, Infallible>))); 158 | 159 | let buffered = body.collect().await.unwrap(); 160 | 161 | assert_eq!(&trailers, buffered.trailers().unwrap()); 162 | 163 | let mut buf = buffered.to_bytes(); 164 | 165 | assert_eq!(&buf.copy_to_bytes(buf.remaining())[..], b"helloworld!"); 166 | } 167 | 168 | /// Test for issue [#88](https://github.com/hyperium/http-body/issues/88). 169 | #[tokio::test] 170 | async fn empty_frame() { 171 | let bufs: [&[u8]; 1] = [&[]]; 172 | 173 | let body = StreamBody::new(stream::iter(bufs.map(Frame::data).map(Ok::<_, Infallible>))); 174 | let buffered = body.collect().await.unwrap(); 175 | 176 | assert_eq!(buffered.to_bytes().len(), 0); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /http-body-util/src/combinators/box_body.rs: -------------------------------------------------------------------------------- 1 | use crate::BodyExt as _; 2 | 3 | use bytes::Buf; 4 | use http_body::{Body, Frame, SizeHint}; 5 | use std::{ 6 | fmt, 7 | pin::Pin, 8 | task::{Context, Poll}, 9 | }; 10 | 11 | /// A boxed [`Body`] trait object. 12 | pub struct BoxBody { 13 | inner: Pin + Send + Sync + 'static>>, 14 | } 15 | 16 | /// A boxed [`Body`] trait object that is !Sync. 17 | pub struct UnsyncBoxBody { 18 | inner: Pin + Send + 'static>>, 19 | } 20 | 21 | impl BoxBody { 22 | /// Create a new `BoxBody`. 23 | pub fn new(body: B) -> Self 24 | where 25 | B: Body + Send + Sync + 'static, 26 | D: Buf, 27 | { 28 | Self { 29 | inner: Box::pin(body), 30 | } 31 | } 32 | } 33 | 34 | impl fmt::Debug for BoxBody { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | f.debug_struct("BoxBody").finish() 37 | } 38 | } 39 | 40 | impl Body for BoxBody 41 | where 42 | D: Buf, 43 | { 44 | type Data = D; 45 | type Error = E; 46 | 47 | fn poll_frame( 48 | mut self: Pin<&mut Self>, 49 | cx: &mut Context<'_>, 50 | ) -> Poll, Self::Error>>> { 51 | self.inner.as_mut().poll_frame(cx) 52 | } 53 | 54 | fn is_end_stream(&self) -> bool { 55 | self.inner.is_end_stream() 56 | } 57 | 58 | fn size_hint(&self) -> SizeHint { 59 | self.inner.size_hint() 60 | } 61 | } 62 | 63 | impl Default for BoxBody 64 | where 65 | D: Buf + 'static, 66 | { 67 | fn default() -> Self { 68 | BoxBody::new(crate::Empty::new().map_err(|err| match err {})) 69 | } 70 | } 71 | 72 | // === UnsyncBoxBody === 73 | impl UnsyncBoxBody { 74 | /// Create a new `UnsyncBoxBody`. 75 | pub fn new(body: B) -> Self 76 | where 77 | B: Body + Send + 'static, 78 | D: Buf, 79 | { 80 | Self { 81 | inner: Box::pin(body), 82 | } 83 | } 84 | } 85 | 86 | impl fmt::Debug for UnsyncBoxBody { 87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 88 | f.debug_struct("UnsyncBoxBody").finish() 89 | } 90 | } 91 | 92 | impl Body for UnsyncBoxBody 93 | where 94 | D: Buf, 95 | { 96 | type Data = D; 97 | type Error = E; 98 | 99 | fn poll_frame( 100 | mut self: Pin<&mut Self>, 101 | cx: &mut Context<'_>, 102 | ) -> Poll, Self::Error>>> { 103 | self.inner.as_mut().poll_frame(cx) 104 | } 105 | 106 | fn is_end_stream(&self) -> bool { 107 | self.inner.is_end_stream() 108 | } 109 | 110 | fn size_hint(&self) -> SizeHint { 111 | self.inner.size_hint() 112 | } 113 | } 114 | 115 | impl Default for UnsyncBoxBody 116 | where 117 | D: Buf + 'static, 118 | { 119 | fn default() -> Self { 120 | UnsyncBoxBody::new(crate::Empty::new().map_err(|err| match err {})) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /http-body-util/src/combinators/collect.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use futures_core::ready; 8 | use http_body::Body; 9 | use pin_project_lite::pin_project; 10 | 11 | pin_project! { 12 | /// Future that resolves into a [`Collected`]. 13 | /// 14 | /// [`Collected`]: crate::Collected 15 | pub struct Collect 16 | where 17 | T: Body, 18 | T: ?Sized, 19 | { 20 | pub(crate) collected: Option>, 21 | #[pin] 22 | pub(crate) body: T, 23 | } 24 | } 25 | 26 | impl Future for Collect { 27 | type Output = Result, T::Error>; 28 | 29 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> std::task::Poll { 30 | let mut me = self.project(); 31 | 32 | loop { 33 | let frame = ready!(me.body.as_mut().poll_frame(cx)); 34 | 35 | let frame = if let Some(frame) = frame { 36 | frame? 37 | } else { 38 | return Poll::Ready(Ok(me.collected.take().expect("polled after complete"))); 39 | }; 40 | 41 | me.collected.as_mut().unwrap().push_frame(frame); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /http-body-util/src/combinators/frame.rs: -------------------------------------------------------------------------------- 1 | use http_body::Body; 2 | 3 | use core::future::Future; 4 | use core::pin::Pin; 5 | use core::task; 6 | 7 | #[must_use = "futures don't do anything unless polled"] 8 | #[derive(Debug)] 9 | /// Future that resolves to the next frame from a [`Body`]. 10 | pub struct Frame<'a, T: ?Sized>(pub(crate) &'a mut T); 11 | 12 | impl Future for Frame<'_, T> { 13 | type Output = Option, T::Error>>; 14 | 15 | fn poll(mut self: Pin<&mut Self>, ctx: &mut task::Context<'_>) -> task::Poll { 16 | Pin::new(&mut self.0).poll_frame(ctx) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /http-body-util/src/combinators/fuse.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | pin::Pin, 3 | task::{Context, Poll}, 4 | }; 5 | 6 | use http_body::{Body, Frame, SizeHint}; 7 | 8 | /// A "fused" [`Body`]. 9 | /// 10 | /// This [`Body`] yields [`Poll::Ready(None)`] forever after the underlying body yields 11 | /// [`Poll::Ready(None)`], or an error [`Poll::Ready(Some(Err(_)))`], once. 12 | /// 13 | /// Bodies should ideally continue to return [`Poll::Ready(None)`] indefinitely after the end of 14 | /// the stream is reached. [`Fuse`] avoids polling its underlying body `B` further after the 15 | /// underlying stream as ended, which can be useful for implementation that cannot uphold this 16 | /// guarantee. 17 | /// 18 | /// This is akin to the functionality that [`std::iter::Iterator::fuse()`] provides for 19 | /// [`Iterator`][std::iter::Iterator]s. 20 | #[derive(Debug)] 21 | pub struct Fuse { 22 | inner: Option, 23 | } 24 | 25 | impl Fuse 26 | where 27 | B: Body, 28 | { 29 | /// Returns a fused body. 30 | pub fn new(body: B) -> Self { 31 | Self { 32 | inner: if body.is_end_stream() { 33 | None 34 | } else { 35 | Some(body) 36 | }, 37 | } 38 | } 39 | } 40 | 41 | impl Body for Fuse 42 | where 43 | B: Body + Unpin, 44 | { 45 | type Data = B::Data; 46 | type Error = B::Error; 47 | 48 | fn poll_frame( 49 | self: Pin<&mut Self>, 50 | cx: &mut Context<'_>, 51 | ) -> Poll, B::Error>>> { 52 | let Self { inner } = self.get_mut(); 53 | 54 | let poll = inner 55 | .as_mut() 56 | .map(|mut inner| match Pin::new(&mut inner).poll_frame(cx) { 57 | frame @ Poll::Ready(Some(Ok(_))) => (frame, inner.is_end_stream()), 58 | end @ Poll::Ready(Some(Err(_)) | None) => (end, true), 59 | poll @ Poll::Pending => (poll, false), 60 | }); 61 | 62 | if let Some((frame, eos)) = poll { 63 | eos.then(|| inner.take()); 64 | frame 65 | } else { 66 | Poll::Ready(None) 67 | } 68 | } 69 | 70 | fn is_end_stream(&self) -> bool { 71 | self.inner.is_none() 72 | } 73 | 74 | fn size_hint(&self) -> SizeHint { 75 | self.inner 76 | .as_ref() 77 | .map(B::size_hint) 78 | .unwrap_or_else(|| SizeHint::with_exact(0)) 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | use bytes::Bytes; 86 | use std::collections::VecDeque; 87 | 88 | /// A value returned by a call to [`Body::poll_frame()`]. 89 | type PollFrame = Poll, Error>>>; 90 | 91 | type Error = &'static str; 92 | 93 | struct Mock<'count> { 94 | poll_count: &'count mut u8, 95 | polls: VecDeque, 96 | } 97 | 98 | #[test] 99 | fn empty_never_polls() { 100 | let mut count = 0_u8; 101 | let empty = Mock::new(&mut count, []); 102 | debug_assert!(empty.is_end_stream()); 103 | let fused = Fuse::new(empty); 104 | assert!(fused.inner.is_none()); 105 | drop(fused); 106 | assert_eq!(count, 0); 107 | } 108 | 109 | #[test] 110 | fn stops_polling_after_none() { 111 | let mut count = 0_u8; 112 | let empty = Mock::new(&mut count, [Poll::Ready(None)]); 113 | debug_assert!(!empty.is_end_stream()); 114 | let mut fused = Fuse::new(empty); 115 | assert!(fused.inner.is_some()); 116 | 117 | let waker = futures_util::task::noop_waker(); 118 | let mut cx = Context::from_waker(&waker); 119 | match Pin::new(&mut fused).poll_frame(&mut cx) { 120 | Poll::Ready(None) => {} 121 | other => panic!("unexpected poll outcome: {:?}", other), 122 | } 123 | 124 | assert!(fused.inner.is_none()); 125 | match Pin::new(&mut fused).poll_frame(&mut cx) { 126 | Poll::Ready(None) => {} 127 | other => panic!("unexpected poll outcome: {:?}", other), 128 | } 129 | 130 | drop(fused); 131 | assert_eq!(count, 1); 132 | } 133 | 134 | #[test] 135 | fn stops_polling_after_some_eos() { 136 | let mut count = 0_u8; 137 | let body = Mock::new( 138 | &mut count, 139 | [Poll::Ready(Some(Ok(Frame::data(Bytes::from_static( 140 | b"hello", 141 | )))))], 142 | ); 143 | debug_assert!(!body.is_end_stream()); 144 | let mut fused = Fuse::new(body); 145 | assert!(fused.inner.is_some()); 146 | 147 | let waker = futures_util::task::noop_waker(); 148 | let mut cx = Context::from_waker(&waker); 149 | 150 | match Pin::new(&mut fused).poll_frame(&mut cx) { 151 | Poll::Ready(Some(Ok(bytes))) => assert_eq!(bytes.into_data().expect("data"), "hello"), 152 | other => panic!("unexpected poll outcome: {:?}", other), 153 | } 154 | 155 | assert!(fused.inner.is_none()); 156 | match Pin::new(&mut fused).poll_frame(&mut cx) { 157 | Poll::Ready(None) => {} 158 | other => panic!("unexpected poll outcome: {:?}", other), 159 | } 160 | 161 | drop(fused); 162 | assert_eq!(count, 1); 163 | } 164 | 165 | #[test] 166 | fn stops_polling_after_some_error() { 167 | let mut count = 0_u8; 168 | let body = Mock::new( 169 | &mut count, 170 | [ 171 | Poll::Ready(Some(Ok(Frame::data(Bytes::from_static(b"hello"))))), 172 | Poll::Ready(Some(Err("oh no"))), 173 | Poll::Ready(Some(Ok(Frame::data(Bytes::from_static(b"world"))))), 174 | ], 175 | ); 176 | debug_assert!(!body.is_end_stream()); 177 | let mut fused = Fuse::new(body); 178 | assert!(fused.inner.is_some()); 179 | 180 | let waker = futures_util::task::noop_waker(); 181 | let mut cx = Context::from_waker(&waker); 182 | 183 | match Pin::new(&mut fused).poll_frame(&mut cx) { 184 | Poll::Ready(Some(Ok(bytes))) => assert_eq!(bytes.into_data().expect("data"), "hello"), 185 | other => panic!("unexpected poll outcome: {:?}", other), 186 | } 187 | 188 | assert!(fused.inner.is_some()); 189 | match Pin::new(&mut fused).poll_frame(&mut cx) { 190 | Poll::Ready(Some(Err("oh no"))) => {} 191 | other => panic!("unexpected poll outcome: {:?}", other), 192 | } 193 | 194 | assert!(fused.inner.is_none()); 195 | match Pin::new(&mut fused).poll_frame(&mut cx) { 196 | Poll::Ready(None) => {} 197 | other => panic!("unexpected poll outcome: {:?}", other), 198 | } 199 | 200 | drop(fused); 201 | assert_eq!(count, 2); 202 | } 203 | 204 | // === impl Mock === 205 | 206 | impl<'count> Mock<'count> { 207 | fn new(poll_count: &'count mut u8, polls: impl IntoIterator) -> Self { 208 | Self { 209 | poll_count, 210 | polls: polls.into_iter().collect(), 211 | } 212 | } 213 | } 214 | 215 | impl Body for Mock<'_> { 216 | type Data = Bytes; 217 | type Error = &'static str; 218 | 219 | fn poll_frame( 220 | self: Pin<&mut Self>, 221 | _cx: &mut Context<'_>, 222 | ) -> Poll, Self::Error>>> { 223 | let Self { poll_count, polls } = self.get_mut(); 224 | **poll_count = poll_count.saturating_add(1); 225 | polls.pop_front().unwrap_or(Poll::Ready(None)) 226 | } 227 | 228 | fn is_end_stream(&self) -> bool { 229 | self.polls.is_empty() 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /http-body-util/src/combinators/map_err.rs: -------------------------------------------------------------------------------- 1 | use http_body::{Body, Frame, SizeHint}; 2 | use pin_project_lite::pin_project; 3 | use std::{ 4 | any::type_name, 5 | fmt, 6 | pin::Pin, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | pin_project! { 11 | /// Body returned by the [`map_err`] combinator. 12 | /// 13 | /// [`map_err`]: crate::BodyExt::map_err 14 | #[derive(Clone, Copy)] 15 | pub struct MapErr { 16 | #[pin] 17 | inner: B, 18 | f: F 19 | } 20 | } 21 | 22 | impl MapErr { 23 | #[inline] 24 | pub(crate) fn new(body: B, f: F) -> Self { 25 | Self { inner: body, f } 26 | } 27 | 28 | /// Get a reference to the inner body 29 | pub fn get_ref(&self) -> &B { 30 | &self.inner 31 | } 32 | 33 | /// Get a mutable reference to the inner body 34 | pub fn get_mut(&mut self) -> &mut B { 35 | &mut self.inner 36 | } 37 | 38 | /// Get a pinned mutable reference to the inner body 39 | pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut B> { 40 | self.project().inner 41 | } 42 | 43 | /// Consume `self`, returning the inner body 44 | pub fn into_inner(self) -> B { 45 | self.inner 46 | } 47 | } 48 | 49 | impl Body for MapErr 50 | where 51 | B: Body, 52 | F: FnMut(B::Error) -> E, 53 | { 54 | type Data = B::Data; 55 | type Error = E; 56 | 57 | fn poll_frame( 58 | self: Pin<&mut Self>, 59 | cx: &mut Context<'_>, 60 | ) -> Poll, Self::Error>>> { 61 | let this = self.project(); 62 | match this.inner.poll_frame(cx) { 63 | Poll::Pending => Poll::Pending, 64 | Poll::Ready(None) => Poll::Ready(None), 65 | Poll::Ready(Some(Ok(frame))) => Poll::Ready(Some(Ok(frame))), 66 | Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err((this.f)(err)))), 67 | } 68 | } 69 | 70 | fn is_end_stream(&self) -> bool { 71 | self.inner.is_end_stream() 72 | } 73 | 74 | fn size_hint(&self) -> SizeHint { 75 | self.inner.size_hint() 76 | } 77 | } 78 | 79 | impl fmt::Debug for MapErr 80 | where 81 | B: fmt::Debug, 82 | { 83 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 84 | f.debug_struct("MapErr") 85 | .field("inner", &self.inner) 86 | .field("f", &type_name::()) 87 | .finish() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /http-body-util/src/combinators/map_frame.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use http_body::{Body, Frame}; 3 | use pin_project_lite::pin_project; 4 | use std::{ 5 | any::type_name, 6 | fmt, 7 | pin::Pin, 8 | task::{Context, Poll}, 9 | }; 10 | 11 | pin_project! { 12 | /// Body returned by the [`map_frame`] combinator. 13 | /// 14 | /// [`map_frame`]: crate::BodyExt::map_frame 15 | #[derive(Clone, Copy)] 16 | pub struct MapFrame { 17 | #[pin] 18 | inner: B, 19 | f: F 20 | } 21 | } 22 | 23 | impl MapFrame { 24 | #[inline] 25 | pub(crate) fn new(body: B, f: F) -> Self { 26 | Self { inner: body, f } 27 | } 28 | 29 | /// Get a reference to the inner body 30 | pub fn get_ref(&self) -> &B { 31 | &self.inner 32 | } 33 | 34 | /// Get a mutable reference to the inner body 35 | pub fn get_mut(&mut self) -> &mut B { 36 | &mut self.inner 37 | } 38 | 39 | /// Get a pinned mutable reference to the inner body 40 | pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut B> { 41 | self.project().inner 42 | } 43 | 44 | /// Consume `self`, returning the inner body 45 | pub fn into_inner(self) -> B { 46 | self.inner 47 | } 48 | } 49 | 50 | impl Body for MapFrame 51 | where 52 | B: Body, 53 | F: FnMut(Frame) -> Frame, 54 | B2: Buf, 55 | { 56 | type Data = B2; 57 | type Error = B::Error; 58 | 59 | fn poll_frame( 60 | self: Pin<&mut Self>, 61 | cx: &mut Context<'_>, 62 | ) -> Poll, Self::Error>>> { 63 | let this = self.project(); 64 | match this.inner.poll_frame(cx) { 65 | Poll::Pending => Poll::Pending, 66 | Poll::Ready(None) => Poll::Ready(None), 67 | Poll::Ready(Some(Ok(frame))) => Poll::Ready(Some(Ok((this.f)(frame)))), 68 | Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))), 69 | } 70 | } 71 | 72 | fn is_end_stream(&self) -> bool { 73 | self.inner.is_end_stream() 74 | } 75 | } 76 | 77 | impl fmt::Debug for MapFrame 78 | where 79 | B: fmt::Debug, 80 | { 81 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 82 | f.debug_struct("MapFrame") 83 | .field("inner", &self.inner) 84 | .field("f", &type_name::()) 85 | .finish() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /http-body-util/src/combinators/mod.rs: -------------------------------------------------------------------------------- 1 | //! Combinators for the `Body` trait. 2 | 3 | mod box_body; 4 | mod collect; 5 | mod frame; 6 | mod fuse; 7 | mod map_err; 8 | mod map_frame; 9 | mod with_trailers; 10 | 11 | pub use self::{ 12 | box_body::{BoxBody, UnsyncBoxBody}, 13 | collect::Collect, 14 | frame::Frame, 15 | fuse::Fuse, 16 | map_err::MapErr, 17 | map_frame::MapFrame, 18 | with_trailers::WithTrailers, 19 | }; 20 | -------------------------------------------------------------------------------- /http-body-util/src/combinators/with_trailers.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | future::Future, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use futures_core::ready; 8 | use http::HeaderMap; 9 | use http_body::{Body, Frame}; 10 | use pin_project_lite::pin_project; 11 | 12 | pin_project! { 13 | /// Adds trailers to a body. 14 | /// 15 | /// See [`BodyExt::with_trailers`] for more details. 16 | pub struct WithTrailers { 17 | #[pin] 18 | state: State, 19 | } 20 | } 21 | 22 | impl WithTrailers { 23 | pub(crate) fn new(body: T, trailers: F) -> Self { 24 | Self { 25 | state: State::PollBody { 26 | body, 27 | trailers: Some(trailers), 28 | }, 29 | } 30 | } 31 | } 32 | 33 | pin_project! { 34 | #[project = StateProj] 35 | enum State { 36 | PollBody { 37 | #[pin] 38 | body: T, 39 | trailers: Option, 40 | }, 41 | PollTrailers { 42 | #[pin] 43 | trailers: F, 44 | prev_trailers: Option, 45 | }, 46 | Done, 47 | } 48 | } 49 | 50 | impl Body for WithTrailers 51 | where 52 | T: Body, 53 | F: Future>>, 54 | { 55 | type Data = T::Data; 56 | type Error = T::Error; 57 | 58 | fn poll_frame( 59 | mut self: Pin<&mut Self>, 60 | cx: &mut Context<'_>, 61 | ) -> Poll, Self::Error>>> { 62 | loop { 63 | let mut this = self.as_mut().project(); 64 | 65 | match this.state.as_mut().project() { 66 | StateProj::PollBody { body, trailers } => match ready!(body.poll_frame(cx)?) { 67 | Some(frame) => match frame.into_trailers() { 68 | Ok(prev_trailers) => { 69 | let trailers = trailers.take().unwrap(); 70 | this.state.set(State::PollTrailers { 71 | trailers, 72 | prev_trailers: Some(prev_trailers), 73 | }); 74 | } 75 | Err(frame) => { 76 | return Poll::Ready(Some(Ok(frame))); 77 | } 78 | }, 79 | None => { 80 | let trailers = trailers.take().unwrap(); 81 | this.state.set(State::PollTrailers { 82 | trailers, 83 | prev_trailers: None, 84 | }); 85 | } 86 | }, 87 | StateProj::PollTrailers { 88 | trailers, 89 | prev_trailers, 90 | } => { 91 | let trailers = ready!(trailers.poll(cx)?); 92 | match (trailers, prev_trailers.take()) { 93 | (None, None) => return Poll::Ready(None), 94 | (None, Some(trailers)) | (Some(trailers), None) => { 95 | this.state.set(State::Done); 96 | return Poll::Ready(Some(Ok(Frame::trailers(trailers)))); 97 | } 98 | (Some(new_trailers), Some(mut prev_trailers)) => { 99 | prev_trailers.extend(new_trailers); 100 | this.state.set(State::Done); 101 | return Poll::Ready(Some(Ok(Frame::trailers(prev_trailers)))); 102 | } 103 | } 104 | } 105 | StateProj::Done => { 106 | return Poll::Ready(None); 107 | } 108 | } 109 | } 110 | } 111 | 112 | #[inline] 113 | fn size_hint(&self) -> http_body::SizeHint { 114 | match &self.state { 115 | State::PollBody { body, .. } => body.size_hint(), 116 | State::PollTrailers { .. } | State::Done => Default::default(), 117 | } 118 | } 119 | } 120 | 121 | #[cfg(test)] 122 | mod tests { 123 | use std::convert::Infallible; 124 | 125 | use bytes::Bytes; 126 | use http::{HeaderName, HeaderValue}; 127 | 128 | use crate::{BodyExt, Empty, Full}; 129 | 130 | #[allow(unused_imports)] 131 | use super::*; 132 | 133 | #[tokio::test] 134 | async fn works() { 135 | let mut trailers = HeaderMap::new(); 136 | trailers.insert( 137 | HeaderName::from_static("foo"), 138 | HeaderValue::from_static("bar"), 139 | ); 140 | 141 | let body = 142 | Full::::from("hello").with_trailers(std::future::ready(Some( 143 | Ok::<_, Infallible>(trailers.clone()), 144 | ))); 145 | 146 | futures_util::pin_mut!(body); 147 | let waker = futures_util::task::noop_waker(); 148 | let mut cx = Context::from_waker(&waker); 149 | 150 | let data = unwrap_ready(body.as_mut().poll_frame(&mut cx)) 151 | .unwrap() 152 | .unwrap() 153 | .into_data() 154 | .unwrap(); 155 | assert_eq!(data, "hello"); 156 | 157 | let body_trailers = unwrap_ready(body.as_mut().poll_frame(&mut cx)) 158 | .unwrap() 159 | .unwrap() 160 | .into_trailers() 161 | .unwrap(); 162 | assert_eq!(body_trailers, trailers); 163 | 164 | assert!(unwrap_ready(body.as_mut().poll_frame(&mut cx)).is_none()); 165 | } 166 | 167 | #[tokio::test] 168 | async fn merges_trailers() { 169 | let mut trailers_1 = HeaderMap::new(); 170 | trailers_1.insert( 171 | HeaderName::from_static("foo"), 172 | HeaderValue::from_static("bar"), 173 | ); 174 | 175 | let mut trailers_2 = HeaderMap::new(); 176 | trailers_2.insert( 177 | HeaderName::from_static("baz"), 178 | HeaderValue::from_static("qux"), 179 | ); 180 | 181 | let body = Empty::::new() 182 | .with_trailers(std::future::ready(Some(Ok::<_, Infallible>( 183 | trailers_1.clone(), 184 | )))) 185 | .with_trailers(std::future::ready(Some(Ok::<_, Infallible>( 186 | trailers_2.clone(), 187 | )))); 188 | 189 | futures_util::pin_mut!(body); 190 | let waker = futures_util::task::noop_waker(); 191 | let mut cx = Context::from_waker(&waker); 192 | 193 | let body_trailers = unwrap_ready(body.as_mut().poll_frame(&mut cx)) 194 | .unwrap() 195 | .unwrap() 196 | .into_trailers() 197 | .unwrap(); 198 | 199 | let mut all_trailers = HeaderMap::new(); 200 | all_trailers.extend(trailers_1); 201 | all_trailers.extend(trailers_2); 202 | assert_eq!(body_trailers, all_trailers); 203 | 204 | assert!(unwrap_ready(body.as_mut().poll_frame(&mut cx)).is_none()); 205 | } 206 | 207 | fn unwrap_ready(poll: Poll) -> T { 208 | match poll { 209 | Poll::Ready(t) => t, 210 | Poll::Pending => panic!("pending"), 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /http-body-util/src/either.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt::Debug; 3 | use std::pin::Pin; 4 | use std::task::{Context, Poll}; 5 | 6 | use bytes::Buf; 7 | use http_body::{Body, Frame, SizeHint}; 8 | use proj::EitherProj; 9 | 10 | /// Sum type with two cases: [`Left`] and [`Right`], used if a body can be one of 11 | /// two distinct types. 12 | /// 13 | /// [`Left`]: Either::Left 14 | /// [`Right`]: Either::Right 15 | #[derive(Debug, Clone, Copy)] 16 | pub enum Either { 17 | /// A value of type `L` 18 | Left(L), 19 | /// A value of type `R` 20 | Right(R), 21 | } 22 | 23 | impl Either { 24 | /// This function is part of the generated code from `pin-project-lite`, 25 | /// for a more in depth explanation and the rest of the generated code refer 26 | /// to the [`proj`] module. 27 | pub(crate) fn project(self: Pin<&mut Self>) -> EitherProj { 28 | unsafe { 29 | match self.get_unchecked_mut() { 30 | Self::Left(left) => EitherProj::Left(Pin::new_unchecked(left)), 31 | Self::Right(right) => EitherProj::Right(Pin::new_unchecked(right)), 32 | } 33 | } 34 | } 35 | } 36 | 37 | impl Either { 38 | /// Convert [`Either`] into the inner type, if both `Left` and `Right` are 39 | /// of the same type. 40 | pub fn into_inner(self) -> L { 41 | match self { 42 | Either::Left(left) => left, 43 | Either::Right(right) => right, 44 | } 45 | } 46 | } 47 | 48 | impl Body for Either 49 | where 50 | L: Body, 51 | R: Body, 52 | L::Error: Into>, 53 | R::Error: Into>, 54 | Data: Buf, 55 | { 56 | type Data = Data; 57 | type Error = Box; 58 | 59 | fn poll_frame( 60 | self: Pin<&mut Self>, 61 | cx: &mut Context<'_>, 62 | ) -> Poll, Self::Error>>> { 63 | match self.project() { 64 | EitherProj::Left(left) => left 65 | .poll_frame(cx) 66 | .map(|poll| poll.map(|opt| opt.map_err(Into::into))), 67 | EitherProj::Right(right) => right 68 | .poll_frame(cx) 69 | .map(|poll| poll.map(|opt| opt.map_err(Into::into))), 70 | } 71 | } 72 | 73 | fn is_end_stream(&self) -> bool { 74 | match self { 75 | Either::Left(left) => left.is_end_stream(), 76 | Either::Right(right) => right.is_end_stream(), 77 | } 78 | } 79 | 80 | fn size_hint(&self) -> SizeHint { 81 | match self { 82 | Either::Left(left) => left.size_hint(), 83 | Either::Right(right) => right.size_hint(), 84 | } 85 | } 86 | } 87 | 88 | pub(crate) mod proj { 89 | //! This code is the (cleaned output) generated by [pin-project-lite], as it 90 | //! does not support tuple variants. 91 | //! 92 | //! This is the altered expansion from the following snippet, expanded by 93 | //! `cargo-expand`: 94 | //! 95 | //! ```rust 96 | //! use pin_project_lite::pin_project; 97 | //! 98 | //! pin_project! { 99 | //! #[project = EitherProj] 100 | //! pub enum Either { 101 | //! Left {#[pin] left: L}, 102 | //! Right {#[pin] right: R} 103 | //! } 104 | //! } 105 | //! ``` 106 | //! 107 | //! [pin-project-lite]: https://docs.rs/pin-project-lite/latest/pin_project_lite/ 108 | use std::marker::PhantomData; 109 | use std::pin::Pin; 110 | 111 | use super::Either; 112 | 113 | #[allow(dead_code)] 114 | #[allow(single_use_lifetimes)] 115 | #[allow(unknown_lints)] 116 | #[allow(clippy::mut_mut)] 117 | #[allow(clippy::redundant_pub_crate)] 118 | #[allow(clippy::ref_option_ref)] 119 | #[allow(clippy::type_repetition_in_bounds)] 120 | pub(crate) enum EitherProj<'__pin, L, R> 121 | where 122 | Either: '__pin, 123 | { 124 | Left(Pin<&'__pin mut L>), 125 | Right(Pin<&'__pin mut R>), 126 | } 127 | 128 | #[allow(single_use_lifetimes)] 129 | #[allow(unknown_lints)] 130 | #[allow(clippy::used_underscore_binding)] 131 | #[allow(missing_debug_implementations)] 132 | const _: () = { 133 | #[allow(non_snake_case)] 134 | pub struct __Origin<'__pin, L, R> { 135 | __dummy_lifetime: PhantomData<&'__pin ()>, 136 | _Left: L, 137 | _Right: R, 138 | } 139 | impl<'__pin, L, R> Unpin for Either where __Origin<'__pin, L, R>: Unpin {} 140 | 141 | trait MustNotImplDrop {} 142 | #[allow(drop_bounds)] 143 | impl MustNotImplDrop for T {} 144 | impl MustNotImplDrop for Either {} 145 | }; 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | use super::*; 151 | use crate::{BodyExt, Empty, Full}; 152 | 153 | #[tokio::test] 154 | async fn data_left() { 155 | let full = Full::new(&b"hello"[..]); 156 | 157 | let mut value: Either<_, Empty<&[u8]>> = Either::Left(full); 158 | 159 | assert_eq!(value.size_hint().exact(), Some(b"hello".len() as u64)); 160 | assert_eq!( 161 | value.frame().await.unwrap().unwrap().into_data().unwrap(), 162 | &b"hello"[..] 163 | ); 164 | assert!(value.frame().await.is_none()); 165 | } 166 | 167 | #[tokio::test] 168 | async fn data_right() { 169 | let full = Full::new(&b"hello!"[..]); 170 | 171 | let mut value: Either, _> = Either::Right(full); 172 | 173 | assert_eq!(value.size_hint().exact(), Some(b"hello!".len() as u64)); 174 | assert_eq!( 175 | value.frame().await.unwrap().unwrap().into_data().unwrap(), 176 | &b"hello!"[..] 177 | ); 178 | assert!(value.frame().await.is_none()); 179 | } 180 | 181 | #[test] 182 | fn into_inner() { 183 | let a = Either::::Left(2); 184 | assert_eq!(a.into_inner(), 2) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /http-body-util/src/empty.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use http_body::{Body, Frame, SizeHint}; 3 | use std::{ 4 | convert::Infallible, 5 | fmt, 6 | marker::PhantomData, 7 | pin::Pin, 8 | task::{Context, Poll}, 9 | }; 10 | 11 | /// A body that is always empty. 12 | pub struct Empty { 13 | _marker: PhantomData D>, 14 | } 15 | 16 | impl Empty { 17 | /// Create a new `Empty`. 18 | pub const fn new() -> Self { 19 | Self { 20 | _marker: PhantomData, 21 | } 22 | } 23 | } 24 | 25 | impl Body for Empty { 26 | type Data = D; 27 | type Error = Infallible; 28 | 29 | #[inline] 30 | fn poll_frame( 31 | self: Pin<&mut Self>, 32 | _cx: &mut Context<'_>, 33 | ) -> Poll, Self::Error>>> { 34 | Poll::Ready(None) 35 | } 36 | 37 | fn is_end_stream(&self) -> bool { 38 | true 39 | } 40 | 41 | fn size_hint(&self) -> SizeHint { 42 | SizeHint::with_exact(0) 43 | } 44 | } 45 | 46 | impl fmt::Debug for Empty { 47 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 48 | f.debug_struct("Empty").finish() 49 | } 50 | } 51 | 52 | impl Default for Empty { 53 | fn default() -> Self { 54 | Self::new() 55 | } 56 | } 57 | 58 | impl Clone for Empty { 59 | fn clone(&self) -> Self { 60 | *self 61 | } 62 | } 63 | 64 | impl Copy for Empty {} 65 | -------------------------------------------------------------------------------- /http-body-util/src/full.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | use http_body::{Body, Frame, SizeHint}; 3 | use pin_project_lite::pin_project; 4 | use std::borrow::Cow; 5 | use std::convert::{Infallible, TryFrom}; 6 | use std::pin::Pin; 7 | use std::task::{Context, Poll}; 8 | 9 | pin_project! { 10 | /// A body that consists of a single chunk. 11 | #[derive(Clone, Copy, Debug)] 12 | pub struct Full { 13 | data: Option, 14 | } 15 | } 16 | 17 | impl Full 18 | where 19 | D: Buf, 20 | { 21 | /// Create a new `Full`. 22 | pub fn new(data: D) -> Self { 23 | let data = if data.has_remaining() { 24 | Some(data) 25 | } else { 26 | None 27 | }; 28 | Full { data } 29 | } 30 | } 31 | 32 | impl Body for Full 33 | where 34 | D: Buf, 35 | { 36 | type Data = D; 37 | type Error = Infallible; 38 | 39 | fn poll_frame( 40 | mut self: Pin<&mut Self>, 41 | _cx: &mut Context<'_>, 42 | ) -> Poll, Self::Error>>> { 43 | Poll::Ready(self.data.take().map(|d| Ok(Frame::data(d)))) 44 | } 45 | 46 | fn is_end_stream(&self) -> bool { 47 | self.data.is_none() 48 | } 49 | 50 | fn size_hint(&self) -> SizeHint { 51 | self.data 52 | .as_ref() 53 | .map(|data| SizeHint::with_exact(u64::try_from(data.remaining()).unwrap())) 54 | .unwrap_or_else(|| SizeHint::with_exact(0)) 55 | } 56 | } 57 | 58 | impl Default for Full 59 | where 60 | D: Buf, 61 | { 62 | /// Create an empty `Full`. 63 | fn default() -> Self { 64 | Full { data: None } 65 | } 66 | } 67 | 68 | impl From for Full 69 | where 70 | D: Buf + From, 71 | { 72 | fn from(bytes: Bytes) -> Self { 73 | Full::new(D::from(bytes)) 74 | } 75 | } 76 | 77 | impl From> for Full 78 | where 79 | D: Buf + From>, 80 | { 81 | fn from(vec: Vec) -> Self { 82 | Full::new(D::from(vec)) 83 | } 84 | } 85 | 86 | impl From<&'static [u8]> for Full 87 | where 88 | D: Buf + From<&'static [u8]>, 89 | { 90 | fn from(slice: &'static [u8]) -> Self { 91 | Full::new(D::from(slice)) 92 | } 93 | } 94 | 95 | impl From> for Full 96 | where 97 | D: Buf + From<&'static B> + From, 98 | B: ToOwned + ?Sized, 99 | { 100 | fn from(cow: Cow<'static, B>) -> Self { 101 | match cow { 102 | Cow::Borrowed(b) => Full::new(D::from(b)), 103 | Cow::Owned(o) => Full::new(D::from(o)), 104 | } 105 | } 106 | } 107 | 108 | impl From for Full 109 | where 110 | D: Buf + From, 111 | { 112 | fn from(s: String) -> Self { 113 | Full::new(D::from(s)) 114 | } 115 | } 116 | 117 | impl From<&'static str> for Full 118 | where 119 | D: Buf + From<&'static str>, 120 | { 121 | fn from(slice: &'static str) -> Self { 122 | Full::new(D::from(slice)) 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | use super::*; 129 | use crate::BodyExt; 130 | 131 | #[tokio::test] 132 | async fn full_returns_some() { 133 | let mut full = Full::new(&b"hello"[..]); 134 | assert_eq!(full.size_hint().exact(), Some(b"hello".len() as u64)); 135 | assert_eq!( 136 | full.frame().await.unwrap().unwrap().into_data().unwrap(), 137 | &b"hello"[..] 138 | ); 139 | assert!(full.frame().await.is_none()); 140 | } 141 | 142 | #[tokio::test] 143 | async fn empty_full_returns_none() { 144 | assert!(Full::<&[u8]>::default().frame().await.is_none()); 145 | assert!(Full::new(&b""[..]).frame().await.is_none()); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /http-body-util/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_debug_implementations, missing_docs, unreachable_pub)] 2 | #![cfg_attr(test, deny(warnings))] 3 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 4 | 5 | //! Utilities for [`http_body::Body`]. 6 | //! 7 | //! [`BodyExt`] adds extensions to the common trait. 8 | //! 9 | //! [`Empty`] and [`Full`] provide simple implementations. 10 | 11 | mod collected; 12 | pub mod combinators; 13 | mod either; 14 | mod empty; 15 | mod full; 16 | mod limited; 17 | mod stream; 18 | 19 | #[cfg(feature = "channel")] 20 | pub mod channel; 21 | 22 | mod util; 23 | 24 | use self::combinators::{BoxBody, MapErr, MapFrame, UnsyncBoxBody}; 25 | 26 | pub use self::collected::Collected; 27 | pub use self::either::Either; 28 | pub use self::empty::Empty; 29 | pub use self::full::Full; 30 | pub use self::limited::{LengthLimitError, Limited}; 31 | pub use self::stream::{BodyDataStream, BodyStream, StreamBody}; 32 | 33 | #[cfg(feature = "channel")] 34 | pub use self::channel::Channel; 35 | 36 | /// An extension trait for [`http_body::Body`] adding various combinators and adapters 37 | pub trait BodyExt: http_body::Body { 38 | /// Returns a future that resolves to the next [`Frame`], if any. 39 | /// 40 | /// [`Frame`]: combinators::Frame 41 | fn frame(&mut self) -> combinators::Frame<'_, Self> 42 | where 43 | Self: Unpin, 44 | { 45 | combinators::Frame(self) 46 | } 47 | 48 | /// Maps this body's frame to a different kind. 49 | fn map_frame(self, f: F) -> MapFrame 50 | where 51 | Self: Sized, 52 | F: FnMut(http_body::Frame) -> http_body::Frame, 53 | B: bytes::Buf, 54 | { 55 | MapFrame::new(self, f) 56 | } 57 | 58 | /// Maps this body's error value to a different value. 59 | fn map_err(self, f: F) -> MapErr 60 | where 61 | Self: Sized, 62 | F: FnMut(Self::Error) -> E, 63 | { 64 | MapErr::new(self, f) 65 | } 66 | 67 | /// Turn this body into a boxed trait object. 68 | fn boxed(self) -> BoxBody 69 | where 70 | Self: Sized + Send + Sync + 'static, 71 | { 72 | BoxBody::new(self) 73 | } 74 | 75 | /// Turn this body into a boxed trait object that is !Sync. 76 | fn boxed_unsync(self) -> UnsyncBoxBody 77 | where 78 | Self: Sized + Send + 'static, 79 | { 80 | UnsyncBoxBody::new(self) 81 | } 82 | 83 | /// Turn this body into [`Collected`] body which will collect all the DATA frames 84 | /// and trailers. 85 | fn collect(self) -> combinators::Collect 86 | where 87 | Self: Sized, 88 | { 89 | combinators::Collect { 90 | body: self, 91 | collected: Some(crate::Collected::default()), 92 | } 93 | } 94 | 95 | /// Add trailers to the body. 96 | /// 97 | /// The trailers will be sent when all previous frames have been sent and the `trailers` future 98 | /// resolves. 99 | /// 100 | /// # Example 101 | /// 102 | /// ``` 103 | /// use http::HeaderMap; 104 | /// use http_body_util::{Full, BodyExt}; 105 | /// use bytes::Bytes; 106 | /// 107 | /// # #[tokio::main] 108 | /// async fn main() { 109 | /// let (tx, rx) = tokio::sync::oneshot::channel::(); 110 | /// 111 | /// let body = Full::::from("Hello, World!") 112 | /// // add trailers via a future 113 | /// .with_trailers(async move { 114 | /// match rx.await { 115 | /// Ok(trailers) => Some(Ok(trailers)), 116 | /// Err(_err) => None, 117 | /// } 118 | /// }); 119 | /// 120 | /// // compute the trailers in the background 121 | /// tokio::spawn(async move { 122 | /// let _ = tx.send(compute_trailers().await); 123 | /// }); 124 | /// 125 | /// async fn compute_trailers() -> HeaderMap { 126 | /// // ... 127 | /// # unimplemented!() 128 | /// } 129 | /// # } 130 | /// ``` 131 | fn with_trailers(self, trailers: F) -> combinators::WithTrailers 132 | where 133 | Self: Sized, 134 | F: std::future::Future>>, 135 | { 136 | combinators::WithTrailers::new(self, trailers) 137 | } 138 | 139 | /// Turn this body into [`BodyDataStream`]. 140 | fn into_data_stream(self) -> BodyDataStream 141 | where 142 | Self: Sized, 143 | { 144 | BodyDataStream::new(self) 145 | } 146 | 147 | /// Creates a "fused" body. 148 | /// 149 | /// This [`Body`][http_body::Body] yields [`Poll::Ready(None)`] forever after the underlying 150 | /// body yields [`Poll::Ready(None)`], or an error [`Poll::Ready(Some(Err(_)))`], once. 151 | /// 152 | /// See [`Fuse`][combinators::Fuse] for more information. 153 | fn fuse(self) -> combinators::Fuse 154 | where 155 | Self: Sized, 156 | { 157 | combinators::Fuse::new(self) 158 | } 159 | } 160 | 161 | impl BodyExt for T where T: http_body::Body {} 162 | -------------------------------------------------------------------------------- /http-body-util/src/limited.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use http_body::{Body, Frame, SizeHint}; 3 | use pin_project_lite::pin_project; 4 | use std::error::Error; 5 | use std::fmt; 6 | use std::pin::Pin; 7 | use std::task::{Context, Poll}; 8 | 9 | pin_project! { 10 | /// A length limited body. 11 | /// 12 | /// This body will return an error if more than the configured number 13 | /// of bytes are returned on polling the wrapped body. 14 | #[derive(Clone, Copy, Debug)] 15 | pub struct Limited { 16 | remaining: usize, 17 | #[pin] 18 | inner: B, 19 | } 20 | } 21 | 22 | impl Limited { 23 | /// Create a new `Limited`. 24 | pub fn new(inner: B, limit: usize) -> Self { 25 | Self { 26 | remaining: limit, 27 | inner, 28 | } 29 | } 30 | } 31 | 32 | impl Body for Limited 33 | where 34 | B: Body, 35 | B::Error: Into>, 36 | { 37 | type Data = B::Data; 38 | type Error = Box; 39 | 40 | fn poll_frame( 41 | self: Pin<&mut Self>, 42 | cx: &mut Context<'_>, 43 | ) -> Poll, Self::Error>>> { 44 | let this = self.project(); 45 | let res = match this.inner.poll_frame(cx) { 46 | Poll::Pending => return Poll::Pending, 47 | Poll::Ready(None) => None, 48 | Poll::Ready(Some(Ok(frame))) => { 49 | if let Some(data) = frame.data_ref() { 50 | if data.remaining() > *this.remaining { 51 | *this.remaining = 0; 52 | Some(Err(LengthLimitError.into())) 53 | } else { 54 | *this.remaining -= data.remaining(); 55 | Some(Ok(frame)) 56 | } 57 | } else { 58 | Some(Ok(frame)) 59 | } 60 | } 61 | Poll::Ready(Some(Err(err))) => Some(Err(err.into())), 62 | }; 63 | 64 | Poll::Ready(res) 65 | } 66 | 67 | fn is_end_stream(&self) -> bool { 68 | self.inner.is_end_stream() 69 | } 70 | 71 | fn size_hint(&self) -> SizeHint { 72 | use std::convert::TryFrom; 73 | match u64::try_from(self.remaining) { 74 | Ok(n) => { 75 | let mut hint = self.inner.size_hint(); 76 | if hint.lower() >= n { 77 | hint.set_exact(n) 78 | } else if let Some(max) = hint.upper() { 79 | hint.set_upper(n.min(max)) 80 | } else { 81 | hint.set_upper(n) 82 | } 83 | hint 84 | } 85 | Err(_) => self.inner.size_hint(), 86 | } 87 | } 88 | } 89 | 90 | /// An error returned when body length exceeds the configured limit. 91 | #[derive(Debug)] 92 | #[non_exhaustive] 93 | pub struct LengthLimitError; 94 | 95 | impl fmt::Display for LengthLimitError { 96 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 97 | f.write_str("length limit exceeded") 98 | } 99 | } 100 | 101 | impl Error for LengthLimitError {} 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use super::*; 106 | use crate::{BodyExt, Full, StreamBody}; 107 | use bytes::Bytes; 108 | use std::convert::Infallible; 109 | 110 | #[tokio::test] 111 | async fn read_for_body_under_limit_returns_data() { 112 | const DATA: &[u8] = b"testing"; 113 | let inner = Full::new(Bytes::from(DATA)); 114 | let body = &mut Limited::new(inner, 8); 115 | 116 | let mut hint = SizeHint::new(); 117 | hint.set_upper(7); 118 | assert_eq!(body.size_hint().upper(), hint.upper()); 119 | 120 | let data = body.frame().await.unwrap().unwrap().into_data().unwrap(); 121 | assert_eq!(data, DATA); 122 | hint.set_upper(0); 123 | assert_eq!(body.size_hint().upper(), hint.upper()); 124 | 125 | assert!(body.frame().await.is_none()); 126 | } 127 | 128 | #[tokio::test] 129 | async fn read_for_body_over_limit_returns_error() { 130 | const DATA: &[u8] = b"testing a string that is too long"; 131 | let inner = Full::new(Bytes::from(DATA)); 132 | let body = &mut Limited::new(inner, 8); 133 | 134 | let mut hint = SizeHint::new(); 135 | hint.set_upper(8); 136 | assert_eq!(body.size_hint().upper(), hint.upper()); 137 | 138 | let error = body.frame().await.unwrap().unwrap_err(); 139 | assert!(matches!(error.downcast_ref(), Some(LengthLimitError))); 140 | } 141 | 142 | fn body_from_iter(into_iter: I) -> impl Body 143 | where 144 | I: IntoIterator, 145 | I::Item: Into + 'static, 146 | I::IntoIter: Send + 'static, 147 | { 148 | let iter = into_iter 149 | .into_iter() 150 | .map(|it| Frame::data(it.into())) 151 | .map(Ok::<_, Infallible>); 152 | 153 | StreamBody::new(futures_util::stream::iter(iter)) 154 | } 155 | 156 | #[tokio::test] 157 | async fn read_for_chunked_body_around_limit_returns_first_chunk_but_returns_error_on_over_limit_chunk( 158 | ) { 159 | const DATA: [&[u8]; 2] = [b"testing ", b"a string that is too long"]; 160 | let inner = body_from_iter(DATA); 161 | let body = &mut Limited::new(inner, 8); 162 | 163 | let mut hint = SizeHint::new(); 164 | hint.set_upper(8); 165 | assert_eq!(body.size_hint().upper(), hint.upper()); 166 | 167 | let data = body.frame().await.unwrap().unwrap().into_data().unwrap(); 168 | assert_eq!(data, DATA[0]); 169 | hint.set_upper(0); 170 | assert_eq!(body.size_hint().upper(), hint.upper()); 171 | 172 | let error = body.frame().await.unwrap().unwrap_err(); 173 | assert!(matches!(error.downcast_ref(), Some(LengthLimitError))); 174 | } 175 | 176 | #[tokio::test] 177 | async fn read_for_chunked_body_over_limit_on_first_chunk_returns_error() { 178 | const DATA: [&[u8]; 2] = [b"testing a string", b" that is too long"]; 179 | let inner = body_from_iter(DATA); 180 | let body = &mut Limited::new(inner, 8); 181 | 182 | let mut hint = SizeHint::new(); 183 | hint.set_upper(8); 184 | assert_eq!(body.size_hint().upper(), hint.upper()); 185 | 186 | let error = body.frame().await.unwrap().unwrap_err(); 187 | assert!(matches!(error.downcast_ref(), Some(LengthLimitError))); 188 | } 189 | 190 | #[tokio::test] 191 | async fn read_for_chunked_body_under_limit_is_okay() { 192 | const DATA: [&[u8]; 2] = [b"test", b"ing!"]; 193 | let inner = body_from_iter(DATA); 194 | let body = &mut Limited::new(inner, 8); 195 | 196 | let mut hint = SizeHint::new(); 197 | hint.set_upper(8); 198 | assert_eq!(body.size_hint().upper(), hint.upper()); 199 | 200 | let data = body.frame().await.unwrap().unwrap().into_data().unwrap(); 201 | assert_eq!(data, DATA[0]); 202 | hint.set_upper(4); 203 | assert_eq!(body.size_hint().upper(), hint.upper()); 204 | 205 | let data = body.frame().await.unwrap().unwrap().into_data().unwrap(); 206 | assert_eq!(data, DATA[1]); 207 | hint.set_upper(0); 208 | assert_eq!(body.size_hint().upper(), hint.upper()); 209 | 210 | assert!(body.frame().await.is_none()); 211 | } 212 | 213 | struct SomeTrailers; 214 | 215 | impl Body for SomeTrailers { 216 | type Data = Bytes; 217 | type Error = Infallible; 218 | 219 | fn poll_frame( 220 | self: Pin<&mut Self>, 221 | _cx: &mut Context<'_>, 222 | ) -> Poll, Self::Error>>> { 223 | Poll::Ready(Some(Ok(Frame::trailers(http::HeaderMap::new())))) 224 | } 225 | } 226 | 227 | #[tokio::test] 228 | async fn read_for_trailers_propagates_inner_trailers() { 229 | let body = &mut Limited::new(SomeTrailers, 8); 230 | let frame = body.frame().await.unwrap().unwrap(); 231 | assert!(frame.is_trailers()); 232 | } 233 | 234 | #[derive(Debug)] 235 | struct ErrorBodyError; 236 | 237 | impl fmt::Display for ErrorBodyError { 238 | fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result { 239 | Ok(()) 240 | } 241 | } 242 | 243 | impl Error for ErrorBodyError {} 244 | 245 | struct ErrorBody; 246 | 247 | impl Body for ErrorBody { 248 | type Data = &'static [u8]; 249 | type Error = ErrorBodyError; 250 | 251 | fn poll_frame( 252 | self: Pin<&mut Self>, 253 | _cx: &mut Context<'_>, 254 | ) -> Poll, Self::Error>>> { 255 | Poll::Ready(Some(Err(ErrorBodyError))) 256 | } 257 | } 258 | 259 | #[tokio::test] 260 | async fn read_for_body_returning_error_propagates_error() { 261 | let body = &mut Limited::new(ErrorBody, 8); 262 | let error = body.frame().await.unwrap().unwrap_err(); 263 | assert!(matches!(error.downcast_ref(), Some(ErrorBodyError))); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /http-body-util/src/stream.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use futures_core::{ready, stream::Stream}; 3 | use http_body::{Body, Frame}; 4 | use pin_project_lite::pin_project; 5 | use std::{ 6 | pin::Pin, 7 | task::{Context, Poll}, 8 | }; 9 | 10 | pin_project! { 11 | /// A body created from a [`Stream`]. 12 | #[derive(Clone, Copy, Debug)] 13 | pub struct StreamBody { 14 | #[pin] 15 | stream: S, 16 | } 17 | } 18 | 19 | impl StreamBody { 20 | /// Create a new `StreamBody`. 21 | pub fn new(stream: S) -> Self { 22 | Self { stream } 23 | } 24 | } 25 | 26 | impl Body for StreamBody 27 | where 28 | S: Stream, E>>, 29 | D: Buf, 30 | { 31 | type Data = D; 32 | type Error = E; 33 | 34 | fn poll_frame( 35 | self: Pin<&mut Self>, 36 | cx: &mut Context<'_>, 37 | ) -> Poll, Self::Error>>> { 38 | match self.project().stream.poll_next(cx) { 39 | Poll::Ready(Some(result)) => Poll::Ready(Some(result)), 40 | Poll::Ready(None) => Poll::Ready(None), 41 | Poll::Pending => Poll::Pending, 42 | } 43 | } 44 | } 45 | 46 | impl Stream for StreamBody { 47 | type Item = S::Item; 48 | 49 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 50 | self.project().stream.poll_next(cx) 51 | } 52 | 53 | fn size_hint(&self) -> (usize, Option) { 54 | self.stream.size_hint() 55 | } 56 | } 57 | 58 | pin_project! { 59 | /// A stream created from a [`Body`]. 60 | #[derive(Clone, Copy, Debug)] 61 | pub struct BodyStream { 62 | #[pin] 63 | body: B, 64 | } 65 | } 66 | 67 | impl BodyStream { 68 | /// Create a new `BodyStream`. 69 | pub fn new(body: B) -> Self { 70 | Self { body } 71 | } 72 | } 73 | 74 | impl Body for BodyStream 75 | where 76 | B: Body, 77 | { 78 | type Data = B::Data; 79 | type Error = B::Error; 80 | 81 | fn poll_frame( 82 | self: Pin<&mut Self>, 83 | cx: &mut Context<'_>, 84 | ) -> Poll, Self::Error>>> { 85 | self.project().body.poll_frame(cx) 86 | } 87 | } 88 | 89 | impl Stream for BodyStream 90 | where 91 | B: Body, 92 | { 93 | type Item = Result, B::Error>; 94 | 95 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 96 | match self.project().body.poll_frame(cx) { 97 | Poll::Ready(Some(frame)) => Poll::Ready(Some(frame)), 98 | Poll::Ready(None) => Poll::Ready(None), 99 | Poll::Pending => Poll::Pending, 100 | } 101 | } 102 | } 103 | 104 | pin_project! { 105 | /// A data stream created from a [`Body`]. 106 | #[derive(Clone, Copy, Debug)] 107 | pub struct BodyDataStream { 108 | #[pin] 109 | body: B, 110 | } 111 | } 112 | 113 | impl BodyDataStream { 114 | /// Create a new `BodyDataStream` 115 | pub fn new(body: B) -> Self { 116 | Self { body } 117 | } 118 | } 119 | 120 | impl Stream for BodyDataStream 121 | where 122 | B: Body, 123 | { 124 | type Item = Result; 125 | 126 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 127 | loop { 128 | return match ready!(self.as_mut().project().body.poll_frame(cx)) { 129 | Some(Ok(frame)) => match frame.into_data() { 130 | Ok(bytes) => Poll::Ready(Some(Ok(bytes))), 131 | Err(_) => continue, 132 | }, 133 | Some(Err(err)) => Poll::Ready(Some(Err(err))), 134 | None => Poll::Ready(None), 135 | }; 136 | } 137 | } 138 | } 139 | 140 | #[cfg(test)] 141 | mod tests { 142 | use crate::{BodyExt, BodyStream, StreamBody}; 143 | use bytes::Bytes; 144 | use futures_util::StreamExt; 145 | use http_body::Frame; 146 | use std::convert::Infallible; 147 | 148 | #[tokio::test] 149 | async fn body_from_stream() { 150 | let chunks: Vec> = vec![ 151 | Ok(Frame::data(Bytes::from(vec![1]))), 152 | Ok(Frame::data(Bytes::from(vec![2]))), 153 | Ok(Frame::data(Bytes::from(vec![3]))), 154 | ]; 155 | let stream = futures_util::stream::iter(chunks); 156 | let mut body = StreamBody::new(stream); 157 | 158 | assert_eq!( 159 | body.frame() 160 | .await 161 | .unwrap() 162 | .unwrap() 163 | .into_data() 164 | .unwrap() 165 | .as_ref(), 166 | [1] 167 | ); 168 | assert_eq!( 169 | body.frame() 170 | .await 171 | .unwrap() 172 | .unwrap() 173 | .into_data() 174 | .unwrap() 175 | .as_ref(), 176 | [2] 177 | ); 178 | assert_eq!( 179 | body.frame() 180 | .await 181 | .unwrap() 182 | .unwrap() 183 | .into_data() 184 | .unwrap() 185 | .as_ref(), 186 | [3] 187 | ); 188 | 189 | assert!(body.frame().await.is_none()); 190 | } 191 | 192 | #[tokio::test] 193 | async fn stream_from_body() { 194 | let chunks: Vec> = vec![ 195 | Ok(Frame::data(Bytes::from(vec![1]))), 196 | Ok(Frame::data(Bytes::from(vec![2]))), 197 | Ok(Frame::data(Bytes::from(vec![3]))), 198 | ]; 199 | let stream = futures_util::stream::iter(chunks); 200 | let body = StreamBody::new(stream); 201 | 202 | let mut stream = BodyStream::new(body); 203 | 204 | assert_eq!( 205 | stream 206 | .next() 207 | .await 208 | .unwrap() 209 | .unwrap() 210 | .into_data() 211 | .unwrap() 212 | .as_ref(), 213 | [1] 214 | ); 215 | assert_eq!( 216 | stream 217 | .next() 218 | .await 219 | .unwrap() 220 | .unwrap() 221 | .into_data() 222 | .unwrap() 223 | .as_ref(), 224 | [2] 225 | ); 226 | assert_eq!( 227 | stream 228 | .next() 229 | .await 230 | .unwrap() 231 | .unwrap() 232 | .into_data() 233 | .unwrap() 234 | .as_ref(), 235 | [3] 236 | ); 237 | 238 | assert!(stream.next().await.is_none()); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /http-body-util/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::io::IoSlice; 3 | 4 | use bytes::{Buf, BufMut, Bytes, BytesMut}; 5 | 6 | #[derive(Debug)] 7 | pub(crate) struct BufList { 8 | bufs: VecDeque, 9 | } 10 | 11 | impl BufList { 12 | #[inline] 13 | pub(crate) fn push(&mut self, buf: T) { 14 | debug_assert!(buf.has_remaining()); 15 | self.bufs.push_back(buf); 16 | } 17 | 18 | #[inline] 19 | pub(crate) fn pop(&mut self) -> Option { 20 | self.bufs.pop_front() 21 | } 22 | } 23 | 24 | impl Buf for BufList { 25 | #[inline] 26 | fn remaining(&self) -> usize { 27 | self.bufs.iter().map(|buf| buf.remaining()).sum() 28 | } 29 | 30 | #[inline] 31 | fn has_remaining(&self) -> bool { 32 | self.bufs.iter().any(|buf| buf.has_remaining()) 33 | } 34 | 35 | #[inline] 36 | fn chunk(&self) -> &[u8] { 37 | self.bufs.front().map(Buf::chunk).unwrap_or_default() 38 | } 39 | 40 | #[inline] 41 | fn advance(&mut self, mut cnt: usize) { 42 | while cnt > 0 { 43 | { 44 | let front = &mut self.bufs[0]; 45 | let rem = front.remaining(); 46 | if rem > cnt { 47 | front.advance(cnt); 48 | return; 49 | } else { 50 | front.advance(rem); 51 | cnt -= rem; 52 | } 53 | } 54 | self.bufs.pop_front(); 55 | } 56 | } 57 | 58 | #[inline] 59 | fn chunks_vectored<'t>(&'t self, dst: &mut [IoSlice<'t>]) -> usize { 60 | if dst.is_empty() { 61 | return 0; 62 | } 63 | let mut vecs = 0; 64 | for buf in &self.bufs { 65 | vecs += buf.chunks_vectored(&mut dst[vecs..]); 66 | if vecs == dst.len() { 67 | break; 68 | } 69 | } 70 | vecs 71 | } 72 | 73 | #[inline] 74 | fn copy_to_bytes(&mut self, len: usize) -> Bytes { 75 | // Our inner buffer may have an optimized version of copy_to_bytes, and if the whole 76 | // request can be fulfilled by the front buffer, we can take advantage. 77 | match self.bufs.front_mut() { 78 | Some(front) if front.remaining() == len => { 79 | let b = front.copy_to_bytes(len); 80 | self.bufs.pop_front(); 81 | b 82 | } 83 | Some(front) if front.remaining() > len => front.copy_to_bytes(len), 84 | _ => { 85 | let rem = self.remaining(); 86 | assert!(len <= rem, "`len` greater than remaining"); 87 | let mut bm = BytesMut::with_capacity(len); 88 | if rem == len { 89 | // .take() costs a lot more, so skip it if we don't need it 90 | bm.put(self); 91 | } else { 92 | bm.put(self.take(len)); 93 | } 94 | bm.freeze() 95 | } 96 | } 97 | } 98 | } 99 | 100 | impl Default for BufList { 101 | fn default() -> Self { 102 | BufList { 103 | bufs: VecDeque::new(), 104 | } 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod tests { 110 | use std::ptr; 111 | 112 | use super::*; 113 | 114 | fn hello_world_buf() -> BufList { 115 | BufList { 116 | bufs: vec![Bytes::from("Hello"), Bytes::from(" "), Bytes::from("World")].into(), 117 | } 118 | } 119 | 120 | #[test] 121 | fn to_bytes_shorter() { 122 | let mut bufs = hello_world_buf(); 123 | let old_ptr = bufs.chunk().as_ptr(); 124 | let start = bufs.copy_to_bytes(4); 125 | assert_eq!(start, "Hell"); 126 | assert!(ptr::eq(old_ptr, start.as_ptr())); 127 | assert_eq!(bufs.chunk(), b"o"); 128 | assert!(ptr::eq(old_ptr.wrapping_add(4), bufs.chunk().as_ptr())); 129 | assert_eq!(bufs.remaining(), 7); 130 | } 131 | 132 | #[test] 133 | fn to_bytes_eq() { 134 | let mut bufs = hello_world_buf(); 135 | let old_ptr = bufs.chunk().as_ptr(); 136 | let start = bufs.copy_to_bytes(5); 137 | assert_eq!(start, "Hello"); 138 | assert!(ptr::eq(old_ptr, start.as_ptr())); 139 | assert_eq!(bufs.chunk(), b" "); 140 | assert_eq!(bufs.remaining(), 6); 141 | } 142 | 143 | #[test] 144 | fn to_bytes_longer() { 145 | let mut bufs = hello_world_buf(); 146 | let start = bufs.copy_to_bytes(7); 147 | assert_eq!(start, "Hello W"); 148 | assert_eq!(bufs.remaining(), 4); 149 | } 150 | 151 | #[test] 152 | fn one_long_buf_to_bytes() { 153 | let mut buf = BufList::default(); 154 | buf.push(b"Hello World" as &[_]); 155 | assert_eq!(buf.copy_to_bytes(5), "Hello"); 156 | assert_eq!(buf.chunk(), b" World"); 157 | } 158 | 159 | #[test] 160 | #[should_panic(expected = "`len` greater than remaining")] 161 | fn buf_to_bytes_too_many() { 162 | hello_world_buf().copy_to_bytes(42); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /http-body/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.0.1 (July 12, 2024) 2 | 3 | - Include LICENSE file. 4 | 5 | # 1.0.0 (November 15, 2023) 6 | 7 | - Update `http` to 1.0. 8 | - Add `Frame::map_data()`. 9 | 10 | # 1.0.0-rc.2 (Dec 28, 2022) 11 | 12 | - Change return type of `Frame::into_data()` and `Frame::into_trailers()` methods to return `Result` instead of `Option`. 13 | 14 | # 1.0.0-rc1 (Oct 25, 2022) 15 | 16 | - Body trait forward-compat redesign (#67). 17 | - `poll_data`/`poll_trailers` removed in favor of `poll_frame`. 18 | - New `Frame` type that represents http frames such as DATA and trailers, as 19 | well as unknown frames for future implementations like h3. 20 | - For more information on this change the proposal can be found 21 | [here](https://github.com/hyperium/hyper/issues/2840). 22 | - Move adatpers and other utilities to `http-body-util`. 23 | 24 | # 0.4.5 (May 20, 2022) 25 | 26 | - Add `String` impl for `Body`. 27 | - Add `Limited` body implementation. 28 | 29 | # 0.4.4 (October 22, 2021) 30 | 31 | - Add `UnsyncBoxBody` and `Body::boxed_unsync`. 32 | 33 | # 0.4.3 (August 8, 2021) 34 | 35 | - Implement `Default` for `BoxBody`. 36 | 37 | # 0.4.2 (May 8, 2021) 38 | 39 | - Correctly override `Body::size_hint` and `Body::is_end_stream` for `Empty`. 40 | - Add `Full` which is a body that consists of a single chunk. 41 | 42 | # 0.4.1 (March 18, 2021) 43 | 44 | - Add combinators to `Body`: 45 | - `map_data`: Change the `Data` chunks produced by the body. 46 | - `map_err`: Change the `Error`s produced by the body. 47 | - `boxed`: Convert the `Body` into a boxed trait object. 48 | - Add `Empty`. 49 | 50 | # 0.4.0 (December 23, 2020) 51 | 52 | - Update `bytes` to v1.0. 53 | 54 | # 0.3.1 (December 13, 2019) 55 | 56 | - Implement `Body` for `http::Request` and `http::Response`. 57 | 58 | # 0.3.0 (December 4, 2019) 59 | 60 | - Rename `next` combinator to `data`. 61 | 62 | # 0.2.0 (December 3, 2019) 63 | 64 | - Update `http` to v0.2. 65 | - Update `bytes` to v0.5. 66 | 67 | # 0.2.0-alpha.3 (October 1, 2019) 68 | 69 | - Fix `Body` to be object-safe. 70 | 71 | # 0.2.0-alpha.2 (October 1, 2019) 72 | 73 | - Add `next` and `trailers` combinator methods. 74 | 75 | # 0.2.0-alpha.1 (August 20, 2019) 76 | 77 | - Update to use `Pin` in `poll_data` and `poll_trailers`. 78 | 79 | # 0.1.0 (May 7, 2019) 80 | 81 | - Initial release 82 | -------------------------------------------------------------------------------- /http-body/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-body" 3 | # When releasing to crates.io: 4 | # - Remove path dependencies 5 | # - Update doc url 6 | # - Cargo.toml 7 | # - README.md 8 | # - Update CHANGELOG.md. 9 | # - Create "http-body-x.y.z" git tag. 10 | version = "1.0.1" 11 | authors = [ 12 | "Carl Lerche ", 13 | "Lucio Franco ", 14 | "Sean McArthur ", 15 | ] 16 | edition = "2018" 17 | readme = "../README.md" 18 | documentation = "https://docs.rs/http-body" 19 | repository = "https://github.com/hyperium/http-body" 20 | license = "MIT" 21 | description = """ 22 | Trait representing an asynchronous, streaming, HTTP request or response body. 23 | """ 24 | keywords = ["http"] 25 | categories = ["web-programming"] 26 | rust-version = "1.61" 27 | 28 | [dependencies] 29 | bytes = "1" 30 | http = "1" 31 | -------------------------------------------------------------------------------- /http-body/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /http-body/src/frame.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderMap; 2 | 3 | /// A frame of any kind related to an HTTP stream (body). 4 | #[derive(Debug)] 5 | pub struct Frame { 6 | kind: Kind, 7 | } 8 | 9 | #[derive(Debug)] 10 | enum Kind { 11 | // The first two variants are "inlined" since they are undoubtedly 12 | // the most common. This saves us from having to allocate a 13 | // boxed trait object for them. 14 | Data(T), 15 | Trailers(HeaderMap), 16 | //Unknown(Box), 17 | } 18 | 19 | impl Frame { 20 | /// Create a DATA frame with the provided `Buf`. 21 | pub fn data(buf: T) -> Self { 22 | Self { 23 | kind: Kind::Data(buf), 24 | } 25 | } 26 | 27 | /// Create a trailers frame. 28 | pub fn trailers(map: HeaderMap) -> Self { 29 | Self { 30 | kind: Kind::Trailers(map), 31 | } 32 | } 33 | 34 | /// Maps this frame's data to a different type. 35 | pub fn map_data(self, f: F) -> Frame 36 | where 37 | F: FnOnce(T) -> D, 38 | { 39 | match self.kind { 40 | Kind::Data(data) => Frame { 41 | kind: Kind::Data(f(data)), 42 | }, 43 | Kind::Trailers(trailers) => Frame { 44 | kind: Kind::Trailers(trailers), 45 | }, 46 | } 47 | } 48 | 49 | /// Returns whether this is a DATA frame. 50 | pub fn is_data(&self) -> bool { 51 | matches!(self.kind, Kind::Data(..)) 52 | } 53 | 54 | /// Consumes self into the buf of the DATA frame. 55 | /// 56 | /// Returns an [`Err`] containing the original [`Frame`] when frame is not a DATA frame. 57 | /// `Frame::is_data` can also be used to determine if the frame is a DATA frame. 58 | pub fn into_data(self) -> Result { 59 | match self.kind { 60 | Kind::Data(data) => Ok(data), 61 | _ => Err(self), 62 | } 63 | } 64 | 65 | /// If this is a DATA frame, returns a reference to it. 66 | /// 67 | /// Returns `None` if not a DATA frame. 68 | pub fn data_ref(&self) -> Option<&T> { 69 | match self.kind { 70 | Kind::Data(ref data) => Some(data), 71 | _ => None, 72 | } 73 | } 74 | 75 | /// If this is a DATA frame, returns a mutable reference to it. 76 | /// 77 | /// Returns `None` if not a DATA frame. 78 | pub fn data_mut(&mut self) -> Option<&mut T> { 79 | match self.kind { 80 | Kind::Data(ref mut data) => Some(data), 81 | _ => None, 82 | } 83 | } 84 | 85 | /// Returns whether this is a trailers frame. 86 | pub fn is_trailers(&self) -> bool { 87 | matches!(self.kind, Kind::Trailers(..)) 88 | } 89 | 90 | /// Consumes self into the buf of the trailers frame. 91 | /// 92 | /// Returns an [`Err`] containing the original [`Frame`] when frame is not a trailers frame. 93 | /// `Frame::is_trailers` can also be used to determine if the frame is a trailers frame. 94 | pub fn into_trailers(self) -> Result { 95 | match self.kind { 96 | Kind::Trailers(trailers) => Ok(trailers), 97 | _ => Err(self), 98 | } 99 | } 100 | 101 | /// If this is a trailers frame, returns a reference to it. 102 | /// 103 | /// Returns `None` if not a trailers frame. 104 | pub fn trailers_ref(&self) -> Option<&HeaderMap> { 105 | match self.kind { 106 | Kind::Trailers(ref trailers) => Some(trailers), 107 | _ => None, 108 | } 109 | } 110 | 111 | /// If this is a trailers frame, returns a mutable reference to it. 112 | /// 113 | /// Returns `None` if not a trailers frame. 114 | pub fn trailers_mut(&mut self) -> Option<&mut HeaderMap> { 115 | match self.kind { 116 | Kind::Trailers(ref mut trailers) => Some(trailers), 117 | _ => None, 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /http-body/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny( 2 | missing_debug_implementations, 3 | missing_docs, 4 | unreachable_pub, 5 | clippy::missing_safety_doc, 6 | clippy::undocumented_unsafe_blocks 7 | )] 8 | #![cfg_attr(test, deny(warnings))] 9 | 10 | //! Asynchronous HTTP request or response body. 11 | //! 12 | //! See [`Body`] for more details. 13 | //! 14 | //! [`Body`]: trait.Body.html 15 | 16 | mod frame; 17 | mod size_hint; 18 | 19 | pub use self::frame::Frame; 20 | pub use self::size_hint::SizeHint; 21 | 22 | use bytes::{Buf, Bytes}; 23 | use std::convert::Infallible; 24 | use std::ops; 25 | use std::pin::Pin; 26 | use std::task::{Context, Poll}; 27 | 28 | /// Trait representing a streaming body of a Request or Response. 29 | /// 30 | /// Individual frames are streamed via the `poll_frame` function, which asynchronously yields 31 | /// instances of [`Frame`]. 32 | /// 33 | /// Frames can contain a data buffer of type `Self::Data`. Frames can also contain an optional 34 | /// set of trailers used to finalize the request/response exchange. This is mostly used when using 35 | /// the HTTP/2.0 protocol. 36 | /// 37 | /// The `size_hint` function provides insight into the total number of bytes that will be streamed. 38 | pub trait Body { 39 | /// Values yielded by the `Body`. 40 | type Data: Buf; 41 | 42 | /// The error type this `Body` might generate. 43 | type Error; 44 | 45 | #[allow(clippy::type_complexity)] 46 | /// Attempt to pull out the next frame of this stream. 47 | /// 48 | /// # Return value 49 | /// 50 | /// This function returns: 51 | /// 52 | /// - [`Poll::Pending`] if the next frame is not ready yet. 53 | /// - [`Poll::Ready(Some(Ok(frame)))`] when the next frame is available. 54 | /// - [`Poll::Ready(Some(Err(error)))`] when an error has been reached. 55 | /// - [`Poll::Ready(None)`] means that all of the frames in this stream have been returned, and 56 | /// that the end of the stream has been reached. 57 | /// 58 | /// If [`Poll::Ready(Some(Err(error)))`] is returned, this body should be discarded. 59 | /// 60 | /// Once the end of the stream is reached, implementations should continue to return 61 | /// [`Poll::Ready(None)`]. 62 | fn poll_frame( 63 | self: Pin<&mut Self>, 64 | cx: &mut Context<'_>, 65 | ) -> Poll, Self::Error>>>; 66 | 67 | /// A hint that may return `true` when the end of stream has been reached. 68 | /// 69 | /// An end of stream means that `poll_frame` will return `None`. 70 | /// 71 | /// A return value of `false` **does not** guarantee that a value will be 72 | /// returned from `poll_frame`. Combinators or other implementations may 73 | /// not be able to know the end of stream has been reached for this hint. 74 | /// 75 | /// Returning `true` allows consumers of this body to optimize, such as not 76 | /// calling `poll_frame` again. 77 | fn is_end_stream(&self) -> bool { 78 | false 79 | } 80 | 81 | /// A hint that returns the bounds on the remaining length of the stream. 82 | /// 83 | /// When the **exact** remaining length of the stream is known, the upper bound will be set and 84 | /// will equal the lower bound. 85 | fn size_hint(&self) -> SizeHint { 86 | SizeHint::default() 87 | } 88 | } 89 | 90 | impl Body for &mut T { 91 | type Data = T::Data; 92 | type Error = T::Error; 93 | 94 | fn poll_frame( 95 | mut self: Pin<&mut Self>, 96 | cx: &mut Context<'_>, 97 | ) -> Poll, Self::Error>>> { 98 | Pin::new(&mut **self).poll_frame(cx) 99 | } 100 | 101 | fn is_end_stream(&self) -> bool { 102 | Pin::new(&**self).is_end_stream() 103 | } 104 | 105 | fn size_hint(&self) -> SizeHint { 106 | Pin::new(&**self).size_hint() 107 | } 108 | } 109 | 110 | impl

Body for Pin

111 | where 112 | P: Unpin + ops::DerefMut, 113 | P::Target: Body, 114 | { 115 | type Data = <

::Target as Body>::Data; 116 | type Error = <

::Target as Body>::Error; 117 | 118 | fn poll_frame( 119 | self: Pin<&mut Self>, 120 | cx: &mut Context<'_>, 121 | ) -> Poll, Self::Error>>> { 122 | Pin::get_mut(self).as_mut().poll_frame(cx) 123 | } 124 | 125 | fn is_end_stream(&self) -> bool { 126 | self.as_ref().is_end_stream() 127 | } 128 | 129 | fn size_hint(&self) -> SizeHint { 130 | self.as_ref().size_hint() 131 | } 132 | } 133 | 134 | impl Body for Box { 135 | type Data = T::Data; 136 | type Error = T::Error; 137 | 138 | fn poll_frame( 139 | mut self: Pin<&mut Self>, 140 | cx: &mut Context<'_>, 141 | ) -> Poll, Self::Error>>> { 142 | Pin::new(&mut **self).poll_frame(cx) 143 | } 144 | 145 | fn is_end_stream(&self) -> bool { 146 | self.as_ref().is_end_stream() 147 | } 148 | 149 | fn size_hint(&self) -> SizeHint { 150 | self.as_ref().size_hint() 151 | } 152 | } 153 | 154 | impl Body for http::Request { 155 | type Data = B::Data; 156 | type Error = B::Error; 157 | 158 | fn poll_frame( 159 | self: Pin<&mut Self>, 160 | cx: &mut Context<'_>, 161 | ) -> Poll, Self::Error>>> { 162 | // SAFETY: 163 | // A pin projection. 164 | unsafe { 165 | self.map_unchecked_mut(http::Request::body_mut) 166 | .poll_frame(cx) 167 | } 168 | } 169 | 170 | fn is_end_stream(&self) -> bool { 171 | self.body().is_end_stream() 172 | } 173 | 174 | fn size_hint(&self) -> SizeHint { 175 | self.body().size_hint() 176 | } 177 | } 178 | 179 | impl Body for http::Response { 180 | type Data = B::Data; 181 | type Error = B::Error; 182 | 183 | fn poll_frame( 184 | self: Pin<&mut Self>, 185 | cx: &mut Context<'_>, 186 | ) -> Poll, Self::Error>>> { 187 | // SAFETY: 188 | // A pin projection. 189 | unsafe { 190 | self.map_unchecked_mut(http::Response::body_mut) 191 | .poll_frame(cx) 192 | } 193 | } 194 | 195 | fn is_end_stream(&self) -> bool { 196 | self.body().is_end_stream() 197 | } 198 | 199 | fn size_hint(&self) -> SizeHint { 200 | self.body().size_hint() 201 | } 202 | } 203 | 204 | impl Body for String { 205 | type Data = Bytes; 206 | type Error = Infallible; 207 | 208 | fn poll_frame( 209 | mut self: Pin<&mut Self>, 210 | _cx: &mut Context<'_>, 211 | ) -> Poll, Self::Error>>> { 212 | if !self.is_empty() { 213 | let s = std::mem::take(&mut *self); 214 | Poll::Ready(Some(Ok(Frame::data(s.into_bytes().into())))) 215 | } else { 216 | Poll::Ready(None) 217 | } 218 | } 219 | 220 | fn is_end_stream(&self) -> bool { 221 | self.is_empty() 222 | } 223 | 224 | fn size_hint(&self) -> SizeHint { 225 | SizeHint::with_exact(self.len() as u64) 226 | } 227 | } 228 | 229 | #[cfg(test)] 230 | fn _assert_bounds() { 231 | fn can_be_trait_object(_: &dyn Body>, Error = std::io::Error>) {} 232 | } 233 | -------------------------------------------------------------------------------- /http-body/src/size_hint.rs: -------------------------------------------------------------------------------- 1 | /// A `Body` size hint 2 | /// 3 | /// The default implementation returns: 4 | /// 5 | /// * 0 for `lower` 6 | /// * `None` for `upper`. 7 | #[derive(Debug, Default, Clone)] 8 | pub struct SizeHint { 9 | lower: u64, 10 | upper: Option, 11 | } 12 | 13 | impl SizeHint { 14 | /// Returns a new `SizeHint` with default values 15 | #[inline] 16 | pub fn new() -> SizeHint { 17 | SizeHint::default() 18 | } 19 | 20 | /// Returns a new `SizeHint` with both upper and lower bounds set to the 21 | /// given value. 22 | #[inline] 23 | pub fn with_exact(value: u64) -> SizeHint { 24 | SizeHint { 25 | lower: value, 26 | upper: Some(value), 27 | } 28 | } 29 | 30 | /// Returns the lower bound of data that the `Body` will yield before 31 | /// completing. 32 | #[inline] 33 | pub fn lower(&self) -> u64 { 34 | self.lower 35 | } 36 | 37 | /// Set the value of the `lower` hint. 38 | /// 39 | /// # Panics 40 | /// 41 | /// The function panics if `value` is greater than `upper`. 42 | #[inline] 43 | pub fn set_lower(&mut self, value: u64) { 44 | assert!(value <= self.upper.unwrap_or(u64::MAX)); 45 | self.lower = value; 46 | } 47 | 48 | /// Returns the upper bound of data the `Body` will yield before 49 | /// completing, or `None` if the value is unknown. 50 | #[inline] 51 | pub fn upper(&self) -> Option { 52 | self.upper 53 | } 54 | 55 | /// Set the value of the `upper` hint value. 56 | /// 57 | /// # Panics 58 | /// 59 | /// This function panics if `value` is less than `lower`. 60 | #[inline] 61 | pub fn set_upper(&mut self, value: u64) { 62 | assert!(value >= self.lower, "`value` is less than than `lower`"); 63 | 64 | self.upper = Some(value); 65 | } 66 | 67 | /// Returns the exact size of data that will be yielded **if** the 68 | /// `lower` and `upper` bounds are equal. 69 | #[inline] 70 | pub fn exact(&self) -> Option { 71 | if Some(self.lower) == self.upper { 72 | self.upper 73 | } else { 74 | None 75 | } 76 | } 77 | 78 | /// Set the value of the `lower` and `upper` bounds to exactly the same. 79 | #[inline] 80 | pub fn set_exact(&mut self, value: u64) { 81 | self.lower = value; 82 | self.upper = Some(value); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /http-body/tests/is_end_stream.rs: -------------------------------------------------------------------------------- 1 | use http_body::{Body, Frame, SizeHint}; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | 5 | struct Mock { 6 | size_hint: SizeHint, 7 | } 8 | 9 | impl Body for Mock { 10 | type Data = ::std::io::Cursor>; 11 | type Error = (); 12 | 13 | fn poll_frame( 14 | self: Pin<&mut Self>, 15 | _cx: &mut Context<'_>, 16 | ) -> Poll, Self::Error>>> { 17 | Poll::Ready(None) 18 | } 19 | 20 | fn size_hint(&self) -> SizeHint { 21 | self.size_hint.clone() 22 | } 23 | } 24 | 25 | #[test] 26 | fn is_end_stream_true() { 27 | let combos = [ 28 | (None, None, false), 29 | (Some(123), None, false), 30 | (Some(0), Some(123), false), 31 | (Some(123), Some(123), false), 32 | (Some(0), Some(0), false), 33 | ]; 34 | 35 | for &(lower, upper, is_end_stream) in &combos { 36 | let mut size_hint = SizeHint::new(); 37 | assert_eq!(0, size_hint.lower()); 38 | assert!(size_hint.upper().is_none()); 39 | 40 | if let Some(lower) = lower { 41 | size_hint.set_lower(lower); 42 | } 43 | 44 | if let Some(upper) = upper { 45 | size_hint.set_upper(upper); 46 | } 47 | 48 | let mut mock = Mock { size_hint }; 49 | 50 | assert_eq!( 51 | is_end_stream, 52 | Pin::new(&mut mock).is_end_stream(), 53 | "size_hint = {:?}", 54 | mock.size_hint.clone() 55 | ); 56 | } 57 | } 58 | 59 | #[test] 60 | fn is_end_stream_default_false() { 61 | let mut mock = Mock { 62 | size_hint: SizeHint::default(), 63 | }; 64 | 65 | assert!( 66 | !Pin::new(&mut mock).is_end_stream(), 67 | "size_hint = {:?}", 68 | mock.size_hint.clone() 69 | ); 70 | } 71 | --------------------------------------------------------------------------------