1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3#[cfg(any(feature = "compio-h3", feature = "generic-h3"))]
4use bytes::Buf as _;
5use bytes::Bytes;
6#[cfg(any(feature = "compio-h3", feature = "generic-h3"))]
7use futures::ready;
8#[cfg(feature = "compio")]
9use futures::StreamExt as _;
10use futures::{FutureExt, Stream};
11use http_body_util::Full;
12use hyper::body::{Body, Frame, Incoming};
13use std::{
14 io::Error,
15 pin::Pin,
16 task::{Context, Poll},
17};
18
19pub use http_body_util::BodyExt;
20
21#[cfg(test)]
22mod tests;
23
24pub enum HttpBody {
26 Standard(Full<Bytes>),
28 Incoming(Incoming),
30 #[cfg(feature = "generic")]
31 GenericStream(http_body_util::combinators::BoxBody<Bytes, std::io::Error>),
33 #[cfg(feature = "compio")]
34 CompioStream(futures::stream::BoxStream<'static, Result<Bytes, std::io::Error>>),
36 #[cfg(feature = "generic-h3")]
38 GenericClient(h3::client::RequestStream<h3_quinn::RecvStream, Bytes>),
39 #[cfg(feature = "generic-h3")]
41 GenericServer(h3::server::RequestStream<h3_quinn::RecvStream, Bytes>),
42 #[cfg(feature = "compio-h3")]
44 CompioClient(compio_quic::h3::client::RequestStream<compio_quic::RecvStream, Bytes>),
45 #[cfg(feature = "compio-h3")]
47 CompioServer(compio_quic::h3::server::RequestStream<compio_quic::RecvStream, Bytes>),
48}
49
50impl HttpBody {
51 pub fn from_incoming(incoming: Incoming) -> Self {
60 HttpBody::Incoming(incoming)
61 }
62
63 #[cfg(feature = "generic-h3")]
72 pub fn from_generic_client(
73 stream: h3::client::RequestStream<h3_quinn::RecvStream, Bytes>,
74 ) -> Self {
75 HttpBody::GenericClient(stream)
76 }
77
78 #[cfg(feature = "generic-h3")]
87 pub fn from_generic_server(
88 stream: h3::server::RequestStream<h3_quinn::RecvStream, Bytes>,
89 ) -> Self {
90 HttpBody::GenericServer(stream)
91 }
92
93 #[cfg(feature = "compio-h3")]
102 pub fn from_compio_client(
103 stream: compio_quic::h3::client::RequestStream<compio_quic::RecvStream, Bytes>,
104 ) -> Self {
105 HttpBody::CompioClient(stream)
106 }
107
108 #[cfg(feature = "compio-h3")]
117 pub fn from_compio_server(
118 stream: compio_quic::h3::server::RequestStream<compio_quic::RecvStream, Bytes>,
119 ) -> Self {
120 HttpBody::CompioServer(stream)
121 }
122
123 pub fn from_text(text: &str) -> Self {
134 Self::from_bytes(text.as_bytes())
135 }
136
137 pub fn from_bytes(bytes: &[u8]) -> Self {
148 let all_bytes = Bytes::copy_from_slice(bytes);
149 HttpBody::Standard(Full::new(all_bytes))
150 }
151
152 #[cfg(feature = "generic")]
153 pub fn from_generic_stream<S>(stream: S) -> Self
165 where
166 S: Stream<Item = Result<Frame<Bytes>, Error>> + Send + Sync + 'static,
167 {
168 let body = http_body_util::StreamBody::new(stream);
169 HttpBody::GenericStream(body.boxed())
170 }
171
172 #[cfg(feature = "compio")]
173 pub fn from_compio_stream<S>(stream: S) -> Self
185 where
186 S: Stream<Item = Result<Bytes, Error>> + Send + 'static,
187 {
188 HttpBody::CompioStream(stream.boxed())
189 }
190
191 pub fn empty() -> Self {
196 Self::from_bytes(&Bytes::new())
197 }
198
199 pub fn try_clone(&self) -> Result<Self, Error> {
204 match self {
205 HttpBody::Standard(content) => Ok(HttpBody::Standard(content.clone())),
206 _ => Err(Error::new(
207 std::io::ErrorKind::Other,
208 "Cannot clone stream body",
209 )),
210 }
211 }
212}
213
214impl Body for HttpBody {
215 type Data = Bytes;
216
217 type Error = std::io::Error;
218
219 fn poll_frame(
220 self: Pin<&mut Self>,
221 cx: &mut Context<'_>,
222 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
223 match self.get_mut() {
224 HttpBody::Standard(full_body) => full_body.frame().poll_unpin(cx).map_err(Error::other),
225
226 HttpBody::Incoming(incoming) => incoming.frame().poll_unpin(cx).map_err(Error::other),
227
228 #[cfg(feature = "generic")]
229 HttpBody::GenericStream(stream) => stream.frame().poll_unpin(cx).map_err(Error::other),
230
231 #[cfg(feature = "compio")]
232 HttpBody::CompioStream(stream) => stream
233 .poll_next_unpin(cx)
234 .map(|b| b.map(|b| b.map(Frame::data))),
235
236 #[cfg(feature = "generic-h3")]
237 HttpBody::GenericClient(stream) => match ready!(stream.poll_recv_data(cx)) {
238 Ok(frame) => match frame {
239 Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
240 frame.copy_to_bytes(frame.remaining()),
241 )))),
242 None => {
243 cx.waker().wake_by_ref();
244 Poll::Ready(None)
245 }
246 },
247 Err(e) => Poll::Ready(Some(Err(Error::other(e)))),
248 },
249
250 #[cfg(feature = "generic-h3")]
251 HttpBody::GenericServer(stream) => match ready!(stream.poll_recv_data(cx)) {
252 Ok(frame) => match frame {
253 Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
254 frame.copy_to_bytes(frame.remaining()),
255 )))),
256 None => {
257 cx.waker().wake_by_ref();
258 Poll::Ready(None)
259 }
260 },
261 Err(e) => Poll::Ready(Some(Err(Error::other(e)))),
262 },
263
264 #[cfg(feature = "compio-h3")]
265 HttpBody::CompioClient(stream) => match ready!(stream.poll_recv_data(cx)) {
266 Ok(frame) => match frame {
267 Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
268 frame.copy_to_bytes(frame.remaining()),
269 )))),
270 None => {
271 cx.waker().wake_by_ref();
272 Poll::Ready(None)
273 }
274 },
275 Err(e) => Poll::Ready(Some(Err(Error::other(e)))),
276 },
277
278 #[cfg(feature = "compio-h3")]
279 HttpBody::CompioServer(stream) => match ready!(stream.poll_recv_data(cx)) {
280 Ok(frame) => match frame {
281 Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
282 frame.copy_to_bytes(frame.remaining()),
283 )))),
284 None => {
285 cx.waker().wake_by_ref();
286 Poll::Ready(None)
287 }
288 },
289 Err(e) => Poll::Ready(Some(Err(Error::other(e)))),
290 },
291 }
292 }
293}
294
295impl Stream for HttpBody {
296 type Item = Result<Frame<Bytes>, Error>;
297
298 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
299 self.poll_frame(cx)
300 }
301}
302
303impl From<&str> for HttpBody {
304 fn from(value: &str) -> Self {
305 HttpBody::from_text(value)
306 }
307}
308
309impl From<String> for HttpBody {
310 fn from(value: String) -> Self {
311 HttpBody::from_text(&value)
312 }
313}
314
315impl From<&[u8]> for HttpBody {
316 fn from(value: &[u8]) -> Self {
317 HttpBody::from_bytes(value)
318 }
319}
320
321impl From<Vec<u8>> for HttpBody {
322 fn from(value: Vec<u8>) -> Self {
323 HttpBody::from_bytes(&value)
324 }
325}
326
327impl From<Bytes> for HttpBody {
328 fn from(value: Bytes) -> Self {
329 HttpBody::from_bytes(&value)
330 }
331}
332
333#[cfg(feature = "compio")]
334impl From<compio::fs::File> for HttpBody {
335 fn from(value: compio::fs::File) -> Self {
336 let stream = from_asyncread(value);
337 HttpBody::from_compio_stream(send_wrapper::SendWrapper::new(stream))
338 }
339}
340
341#[cfg(feature = "compio")]
342fn from_asyncread<R>(reader: R) -> impl Stream<Item = Result<Bytes, Error>>
343where
344 R: compio::io::AsyncReadAt,
345{
346 async_fn_stream::try_fn_stream(|emitter| async move {
347 let mut pos = 0;
348 loop {
349 let buf = Vec::with_capacity(4096);
350 let compio::BufResult(res, buffer) = reader.read_at(buf, pos).await;
351 let len = res?;
352 if len == 0 {
353 break Ok(());
354 }
355 pos += len as u64;
356 emitter.emit(Bytes::from(buffer)).await
357 }
358 })
359}