Skip to main content

base_http/body/
mod.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! Native HTTP bodies, following `Protocol::HTTP::Body`.
5//!
6//! [`ReadableBody`] combines synchronous observations and abandonment with asynchronous frame reads using explicit cancellation. I/O-backed implementations retain explicitly constructed scheduler-bound capabilities; asynchronous retirement remains with the resource or driver owner, and [`ReadableBody::close`] signals abandonment without proving retirement. Memory bodies need no scheduler. Readiness, exhaustion, total representation length and replayability have separate meanings.
7//!
8//! Body length describes the whole representation. It does not decrease as chunks are consumed, and a [`HeadBody`] body may report a nonzero length while emitting no data:
9//!
10//! ```rust
11//! use base_http::body::{BufferedBody, HeadBody, ReadableBody};
12//!
13//! let mut body = BufferedBody::from(b"hello".to_vec());
14//! assert!(body.is_ready());
15//! assert_eq!(body.length(), Some(5));
16//! assert_eq!(body.read_chunk(), Some(b"hello".to_vec()));
17//! assert_eq!(body.length(), Some(5));
18//! assert!(body.rewind());
19//!
20//! let head = HeadBody::for_body(&mut body);
21//! assert!(head.is_empty());
22//! assert_eq!(head.length(), Some(5));
23//! ```
24//!
25//! Reads move owned [`BodyFrame`] events: `Data { data, end }`, `Headers { fields, end }` and `End`; [`BodyFrame::is_end`] handles both payload-associated and separate completion. Initial fields stay in the message metadata; a body `Headers` frame contains trailers, and no data or further headers may follow it. An empty data payload is ordinary data unless `end` is true. After completion, reads return `End`. `Request::read` and `Response::read` return these frames without mutating the initial fields; forward a frame with [`WritableBody::write_frame`] or collect its fields with `HttpHeaders::append_trailers`. [`BodyFrame::map_data`] changes the data owner while preserving fields and completion, so forwarding does not require a common allocation, cloning, boxing or copying payload bytes.
26//!
27//! [`BodyReader`] and [`BodyWriter`] are bounded consumer/producer halves implementing [`ReadableBody`] and [`WritableBody`]. [`BodyReader::bounded`] applies backpressure under both a byte and a frame limit. Normal write closure drains accepted frames and marks the last queued payload as final; failure discards queued data and preserves the cause.
28//!
29//! ```rust
30//! use base_http::body::{BodyFrame, BodyReader, BodyError, ReadableBody};
31//! use base_scheduler::Cancellation;
32//! use futures_lite::future::{block_on, zip};
33//!
34//! let (mut reader, mut writer) = BodyReader::bounded(1024, 16);
35//! let producer = async {
36//!     writer.write_frame(
37//!         BodyFrame::Data { data: b"hello".to_vec(), end: true },
38//!         &Cancellation::never(),
39//!     ).await?;
40//!     Ok::<_, BodyError>(())
41//! };
42//! let consumer = async {
43//!     let mut bytes = Vec::new();
44//!     loop {
45//!         let frame = reader.read(&Cancellation::never()).await?;
46//!         let end = frame.is_end();
47//!         match frame {
48//!             BodyFrame::Data { data, .. } => bytes.extend(data),
49//!             BodyFrame::Headers { fields, .. } => {
50//!                 // Forward fields, inspect them, or explicitly collect them.
51//!                 assert!(fields.is_empty());
52//!             }
53//!             BodyFrame::End => {}
54//!         }
55//!         if end { break; }
56//!     }
57//!     Ok::<_, BodyError>(bytes)
58//! };
59//! let (sent, received) = block_on(zip(producer, consumer));
60//! sent?;
61//! assert_eq!(received?, b"hello");
62//! # Ok::<(), BodyError>(())
63//! ```
64//!
65//! # Polling reads alongside other I/O
66//!
67//! A streaming consumer, such as a server-sent events reader, often waits for the next frame and for another source, such as a command channel or a timer, at the same time. The [`ReadableBody::read`] contract requires that dropping a pending read loses no frame, so create a new read for each wait and drop it when the other source wins. The next read continues at the same position. I/O-backed bodies meet this when their underlying reader is cancel-safe, as scheduler sockets and TLS streams are.
68//!
69//! The read future is not `Unpin`, and macros such as `futures_util::select!` require every arm to be `FusedFuture + Unpin`. Pin each read with [`std::pin::pin!`] and fuse it with `FutureExt::fuse`, and do the same for other futures which are not already fused, such as a channel's `recv()`; no custom wrapper type or allocation is needed. Combinators such as `futures_lite::future::or`, whose futures produce one output type, need neither. When the other source only needs to stop reading, a [`Cancellation`] passed to `read` is simpler still.
70//!
71//! ```rust
72//! use base_http::body::{BodyError, BodyFrame, BodyReader, ReadableBody};
73//! use base_scheduler::Cancellation;
74//! use futures_lite::future::block_on;
75//! use futures_util::{FutureExt, StreamExt, select_biased};
76//! use std::pin::pin;
77//!
78//! enum Command {
79//!     Stop,
80//! }
81//!
82//! let (mut body, mut writer) = BodyReader::bounded(1024, 16);
83//! let (sender, mut commands) = futures_channel::mpsc::unbounded();
84//! let never = Cancellation::never();
85//! block_on(async {
86//!     writer.write_frame(BodyFrame::Data { data: b"event".to_vec(), end: false }, &never).await?;
87//!     let mut received = Vec::new();
88//!     loop {
89//!         // Each iteration races a new read, which is dropped while still waiting when the command wins.
90//!         let mut read = pin!(body.read(&never).fuse());
91//!         select_biased! {
92//!             frame = read => {
93//!                 let frame = frame?;
94//!                 // Check completion before moving the frame's contents.
95//!                 let end = frame.is_end();
96//!                 if let BodyFrame::Data { data, .. } = frame {
97//!                     received.extend(data);
98//!                 }
99//!                 if end {
100//!                     break;
101//!                 }
102//!                 sender.unbounded_send(Command::Stop).unwrap();
103//!             }
104//!             command = commands.next() => match command {
105//!                 Some(Command::Stop) | None => break,
106//!             },
107//!         }
108//!     }
109//!     assert_eq!(received, b"event");
110//!
111//!     // The dropped read consumed nothing, so the body is still read in order.
112//!     writer.write_frame(BodyFrame::Data { data: b"more".to_vec(), end: true }, &never).await?;
113//!     let BodyFrame::Data { data, end } = body.read(&never).await? else {
114//!         unreachable!();
115//!     };
116//!     assert_eq!((data.as_slice(), end), (&b"more"[..], true));
117//!     Ok::<_, BodyError>(())
118//! })?;
119//! # Ok::<(), BodyError>(())
120//! ```
121//!
122//! There is one pull-body abstraction. A protocol driver reads bodies directly in its existing future, so bodies do not spawn tasks. Custom bodies can read incoming input while producing outgoing chunks. Imperative producers can use the bounded pair with explicitly supervised concurrent work.
123use base_scheduler::Cancellation;
124use std::future::Future;
125
126mod buffered;
127mod empty;
128mod error;
129mod frame;
130mod head;
131mod writable;
132
133pub use buffered::BufferedBody;
134pub use empty::EmptyBody;
135pub use error::BodyError;
136pub use frame::BodyFrame;
137pub use head::HeadBody;
138pub use writable::{BodyReader, BodyWriter};
139
140/// Protocol-independent body observations and lifecycle.
141///
142/// No observation performs I/O or requires a scheduler. Readiness, exhaustion,
143/// known representation length and rewindability are independent properties.
144pub trait ReadableBody: Send {
145    /// Application data, without imposing completion-I/O buffer requirements.
146    type Data: AsRef<[u8]> + Send;
147    type Error: std::error::Error + Send + Sync + 'static;
148
149    /// Whether another read can proceed without waiting for produced data.
150    ///
151    /// This is a conservative batching hint. Terminal completion/error can also
152    /// be ready. It does not promise that an I/O operation will finish in one poll.
153    fn is_ready(&self) -> bool {
154        false
155    }
156
157    /// Whether no unread payload frames remain. False can mean unknown; an open
158    /// queue with no frames is not exhausted. This does not suppress read errors.
159    fn is_empty(&self) -> bool {
160        false
161    }
162
163    /// Total representation length, independent of the current read position.
164    fn length(&self) -> Option<u64> {
165        None
166    }
167
168    /// Whether this body can emit trailing headers. An HTTP/1.1 response needs
169    /// this before reading to choose chunked framing, even when the data length
170    /// is known. An HTTP/1 request of known length uses `Content-Length` and
171    /// drops any trailers whatever this reports. Returning false promises that
172    /// no Headers frame will be produced.
173    fn may_have_trailers(&self) -> bool {
174        true
175    }
176
177    fn is_rewindable(&self) -> bool {
178        false
179    }
180
181    /// Restore the read position when supported, reporting success.
182    fn rewind(&mut self) -> bool {
183        false
184    }
185
186    /// Abandon further application reads, optionally reporting their failure.
187    ///
188    /// This synchronous operation does not guarantee asynchronous resource
189    /// retirement. An I/O-backed implementation must leave cleanup with an
190    /// explicit resource/driver owner. Cancelling or dropping a read does not
191    /// prove that backend access ended, nor that a connection can be reused.
192    fn close(&mut self, error: Option<Self::Error>);
193
194    /// Read one owned frame. Pre-cancellation prevents consumption. The caller
195    /// stops after `frame.is_end()`; subsequent reads return `BodyFrame::End`.
196    /// Headers occur at most once, after all data. Dropping a pending read is
197    /// not transport retirement, and must not lose an unreturned queued frame.
198    ///
199    /// Callers can therefore race a read against other futures; see [polling reads alongside other I/O](self#polling-reads-alongside-other-io).
200    fn read(
201        &mut self,
202        cancellation: &Cancellation,
203    ) -> impl Future<Output = Result<BodyFrame<Self::Data>, Self::Error>> + Send;
204}
205impl<R: ReadableBody + ?Sized> ReadableBody for &mut R {
206    type Data = R::Data;
207    type Error = R::Error;
208
209    fn is_ready(&self) -> bool {
210        (**self).is_ready()
211    }
212    fn is_empty(&self) -> bool {
213        (**self).is_empty()
214    }
215    fn length(&self) -> Option<u64> {
216        (**self).length()
217    }
218    fn may_have_trailers(&self) -> bool {
219        (**self).may_have_trailers()
220    }
221    fn is_rewindable(&self) -> bool {
222        (**self).is_rewindable()
223    }
224    fn rewind(&mut self) -> bool {
225        (**self).rewind()
226    }
227    fn close(&mut self, error: Option<Self::Error>) {
228        (**self).close(error)
229    }
230
231    async fn read(
232        &mut self,
233        cancellation: &Cancellation,
234    ) -> Result<BodyFrame<Self::Data>, Self::Error> {
235        (**self).read(cancellation).await
236    }
237}
238
239/// A body producer accepting owned frames with backpressure.
240/// Normal closure preserves accepted frames; failure abandons unfinished output.
241/// Flushing exposes accepted frames to the consumer, without promising peer receipt.
242pub trait WritableBody: Send {
243    type Data: AsRef<[u8]> + Send;
244    type Error: std::error::Error + Send + Sync + 'static;
245
246    /// Accept one whole frame. Headers follow all data; a terminal frame closes
247    /// output atomically with acceptance. Later writes fail. Queue capacity
248    /// becoming available does not mean the transport has released its buffers.
249    fn write_frame(
250        &mut self,
251        frame: BodyFrame<Self::Data>,
252        cancellation: &Cancellation,
253    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
254
255    /// Write a nonfinal data frame. Use `write_frame` to carry fields or end.
256    fn write(
257        &mut self,
258        data: Self::Data,
259        cancellation: &Cancellation,
260    ) -> impl Future<Output = Result<(), Self::Error>> + Send {
261        self.write_frame(BodyFrame::Data { data, end: false }, cancellation)
262    }
263    fn flush(
264        &mut self,
265        cancellation: &Cancellation,
266    ) -> impl Future<Output = Result<(), Self::Error>> + Send;
267    fn close_write(&mut self, error: Option<Self::Error>);
268    fn abort(&mut self);
269}
270
271impl<W: WritableBody + ?Sized> WritableBody for &mut W {
272    type Data = W::Data;
273    type Error = W::Error;
274    async fn write_frame(
275        &mut self,
276        frame: BodyFrame<Self::Data>,
277        cancellation: &Cancellation,
278    ) -> Result<(), Self::Error> {
279        (**self).write_frame(frame, cancellation).await
280    }
281    async fn write(
282        &mut self,
283        data: Self::Data,
284        cancellation: &Cancellation,
285    ) -> Result<(), Self::Error> {
286        (**self).write(data, cancellation).await
287    }
288    async fn flush(&mut self, cancellation: &Cancellation) -> Result<(), Self::Error> {
289        (**self).flush(cancellation).await
290    }
291    fn close_write(&mut self, error: Option<Self::Error>) {
292        (**self).close_write(error)
293    }
294    fn abort(&mut self) {
295        (**self).abort()
296    }
297}