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}