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//! 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.
66use base_scheduler::Cancellation;
67use std::future::Future;
68
69mod buffered;
70mod empty;
71mod error;
72mod frame;
73mod head;
74mod writable;
75
76pub use buffered::BufferedBody;
77pub use empty::EmptyBody;
78pub use error::BodyError;
79pub use frame::BodyFrame;
80pub use head::HeadBody;
81pub use writable::{BodyReader, BodyWriter};
82
83/// Protocol-independent body observations and lifecycle.
84///
85/// No observation performs I/O or requires a scheduler. Readiness, exhaustion,
86/// known representation length and rewindability are independent properties.
87pub trait ReadableBody: Send {
88 /// Application data, without imposing completion-I/O buffer requirements.
89 type Data: AsRef<[u8]> + Send;
90 type Error: std::error::Error + Send + Sync + 'static;
91
92 /// Whether another read can proceed without waiting for produced data.
93 ///
94 /// This is a conservative batching hint. Terminal completion/error can also
95 /// be ready. It does not promise that an I/O operation will finish in one poll.
96 fn is_ready(&self) -> bool {
97 false
98 }
99
100 /// Whether no unread payload frames remain. False can mean unknown; an open
101 /// queue with no frames is not exhausted. This does not suppress read errors.
102 fn is_empty(&self) -> bool {
103 false
104 }
105
106 /// Total representation length, independent of the current read position.
107 fn length(&self) -> Option<u64> {
108 None
109 }
110
111 /// Whether this body can emit trailing headers. HTTP/1 needs this before
112 /// reading to choose chunked framing, even when the data length is known.
113 /// Returning false promises that no Headers frame will be produced.
114 fn may_have_trailers(&self) -> bool {
115 true
116 }
117
118 fn is_rewindable(&self) -> bool {
119 false
120 }
121
122 /// Restore the read position when supported, reporting success.
123 fn rewind(&mut self) -> bool {
124 false
125 }
126
127 /// Abandon further application reads, optionally reporting their failure.
128 ///
129 /// This synchronous operation does not guarantee asynchronous resource
130 /// retirement. An I/O-backed implementation must leave cleanup with an
131 /// explicit resource/driver owner. Cancelling or dropping a read does not
132 /// prove that backend access ended, nor that a connection can be reused.
133 fn close(&mut self, error: Option<Self::Error>);
134
135 /// Read one owned frame. Pre-cancellation prevents consumption. The caller
136 /// stops after `frame.is_end()`; subsequent reads return `BodyFrame::End`.
137 /// Headers occur at most once, after all data. Dropping a pending read is
138 /// not transport retirement, and must not lose an unreturned queued frame.
139 fn read(
140 &mut self,
141 cancellation: &Cancellation,
142 ) -> impl Future<Output = Result<BodyFrame<Self::Data>, Self::Error>> + Send;
143}
144impl<R: ReadableBody + ?Sized> ReadableBody for &mut R {
145 type Data = R::Data;
146 type Error = R::Error;
147
148 fn is_ready(&self) -> bool {
149 (**self).is_ready()
150 }
151 fn is_empty(&self) -> bool {
152 (**self).is_empty()
153 }
154 fn length(&self) -> Option<u64> {
155 (**self).length()
156 }
157 fn may_have_trailers(&self) -> bool {
158 (**self).may_have_trailers()
159 }
160 fn is_rewindable(&self) -> bool {
161 (**self).is_rewindable()
162 }
163 fn rewind(&mut self) -> bool {
164 (**self).rewind()
165 }
166 fn close(&mut self, error: Option<Self::Error>) {
167 (**self).close(error)
168 }
169
170 async fn read(
171 &mut self,
172 cancellation: &Cancellation,
173 ) -> Result<BodyFrame<Self::Data>, Self::Error> {
174 (**self).read(cancellation).await
175 }
176}
177
178/// A body producer accepting owned frames with backpressure.
179/// Normal closure preserves accepted frames; failure abandons unfinished output.
180/// Flushing exposes accepted frames to the consumer, without promising peer receipt.
181pub trait WritableBody: Send {
182 type Data: AsRef<[u8]> + Send;
183 type Error: std::error::Error + Send + Sync + 'static;
184
185 /// Accept one whole frame. Headers follow all data; a terminal frame closes
186 /// output atomically with acceptance. Later writes fail. Queue capacity
187 /// becoming available does not mean the transport has released its buffers.
188 fn write_frame(
189 &mut self,
190 frame: BodyFrame<Self::Data>,
191 cancellation: &Cancellation,
192 ) -> impl Future<Output = Result<(), Self::Error>> + Send;
193
194 /// Write a nonfinal data frame. Use `write_frame` to carry fields or end.
195 fn write(
196 &mut self,
197 data: Self::Data,
198 cancellation: &Cancellation,
199 ) -> impl Future<Output = Result<(), Self::Error>> + Send {
200 self.write_frame(BodyFrame::Data { data, end: false }, cancellation)
201 }
202 fn flush(
203 &mut self,
204 cancellation: &Cancellation,
205 ) -> impl Future<Output = Result<(), Self::Error>> + Send;
206 fn close_write(&mut self, error: Option<Self::Error>);
207 fn abort(&mut self);
208}
209
210impl<W: WritableBody + ?Sized> WritableBody for &mut W {
211 type Data = W::Data;
212 type Error = W::Error;
213 async fn write_frame(
214 &mut self,
215 frame: BodyFrame<Self::Data>,
216 cancellation: &Cancellation,
217 ) -> Result<(), Self::Error> {
218 (**self).write_frame(frame, cancellation).await
219 }
220 async fn write(
221 &mut self,
222 data: Self::Data,
223 cancellation: &Cancellation,
224 ) -> Result<(), Self::Error> {
225 (**self).write(data, cancellation).await
226 }
227 async fn flush(&mut self, cancellation: &Cancellation) -> Result<(), Self::Error> {
228 (**self).flush(cancellation).await
229 }
230 fn close_write(&mut self, error: Option<Self::Error>) {
231 (**self).close_write(error)
232 }
233 fn abort(&mut self) {
234 (**self).abort()
235 }
236}