1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3#[cfg(feature = "compio")]
4use async_fn_stream::try_fn_stream;
5#[cfg(feature = "http3")]
6use bytes::Buf;
7use bytes::Bytes;
8#[cfg(feature = "compio")]
9use compio::{fs::File, io::AsyncReadAt, BufResult};
10#[cfg(all(feature = "http3", feature = "compio"))]
11use compio_quic::RecvStream as CompioRecvStream;
12#[cfg(feature = "http3")]
13use futures::ready;
14#[cfg(feature = "compio")]
15use futures::{stream::BoxStream, StreamExt as _};
16use futures::{FutureExt, Stream};
17#[cfg(feature = "http3")]
18use h3::{
19 client::RequestStream as ClientRequestStream, server::RequestStream as ServerRequestStream,
20};
21#[cfg(all(feature = "http3", feature = "generic"))]
22use h3_quinn::RecvStream as GenericRecvStream;
23use http_body_util::Full;
24#[cfg(feature = "generic")]
25use http_body_util::{combinators::BoxBody, StreamBody};
26use hyper::body::{Body, Frame, Incoming};
27#[cfg(feature = "compio")]
28use send_wrapper::SendWrapper;
29use std::{
30 io::Error,
31 pin::Pin,
32 task::{Context, Poll},
33};
34
35pub use http_body_util::BodyExt;
36
37#[cfg(test)]
38mod tests;
39
40pub enum HttpBody {
42 Standard(Full<Bytes>),
44 Incoming(Incoming),
46 #[cfg(feature = "generic")]
47 GenericStream(BoxBody<Bytes, std::io::Error>),
49 #[cfg(feature = "compio")]
50 CompioStream(BoxStream<'static, Result<Bytes, std::io::Error>>),
52 #[cfg(all(feature = "http3", feature = "generic"))]
54 GenericClient(ClientRequestStream<GenericRecvStream, Bytes>),
55 #[cfg(all(feature = "http3", feature = "generic"))]
57 GenericServer(ServerRequestStream<GenericRecvStream, Bytes>),
58 #[cfg(all(feature = "http3", feature = "compio"))]
60 CompioClient(ClientRequestStream<CompioRecvStream, Bytes>),
61 #[cfg(all(feature = "http3", feature = "compio"))]
63 CompioServer(ServerRequestStream<CompioRecvStream, Bytes>),
64}
65
66impl HttpBody {
67 pub fn from_incoming(incoming: Incoming) -> Self {
76 HttpBody::Incoming(incoming)
77 }
78
79 #[cfg(all(feature = "http3", feature = "generic"))]
88 pub fn from_generic_client(stream: ClientRequestStream<GenericRecvStream, Bytes>) -> Self {
89 HttpBody::GenericClient(stream)
90 }
91
92 #[cfg(all(feature = "http3", feature = "generic"))]
101 pub fn from_generic_server(stream: ServerRequestStream<GenericRecvStream, Bytes>) -> Self {
102 HttpBody::GenericServer(stream)
103 }
104
105 #[cfg(all(feature = "http3", feature = "compio"))]
114 pub fn from_compio_client(stream: ClientRequestStream<CompioRecvStream, Bytes>) -> Self {
115 HttpBody::CompioClient(stream)
116 }
117
118 #[cfg(all(feature = "http3", feature = "compio"))]
127 pub fn from_compio_server(stream: ServerRequestStream<CompioRecvStream, Bytes>) -> Self {
128 HttpBody::CompioServer(stream)
129 }
130
131 pub fn from_text(text: &str) -> Self {
142 Self::from_bytes(text.as_bytes())
143 }
144
145 pub fn from_bytes(bytes: &[u8]) -> Self {
156 let all_bytes = Bytes::copy_from_slice(bytes);
157 HttpBody::Standard(Full::new(all_bytes))
158 }
159
160 #[cfg(feature = "generic")]
161 pub fn from_generic_stream<S>(stream: S) -> Self
173 where
174 S: Stream<Item = Result<Frame<Bytes>, Error>> + Send + Sync + 'static,
175 {
176 let body = StreamBody::new(stream);
177 HttpBody::GenericStream(BodyExt::boxed(body))
178 }
179
180 #[cfg(feature = "compio")]
181 pub fn from_compio_stream<S>(stream: S) -> Self
193 where
194 S: Stream<Item = Result<Bytes, Error>> + Send + 'static,
195 {
196 HttpBody::CompioStream(stream.boxed())
197 }
198
199 pub fn empty() -> Self {
204 Self::from_bytes(&Bytes::new())
205 }
206
207 pub fn try_clone(&self) -> Result<Self, Error> {
212 match self {
213 HttpBody::Standard(content) => Ok(HttpBody::Standard(content.clone())),
214 _ => Err(Error::new(
215 std::io::ErrorKind::Other,
216 "Cannot clone stream body",
217 )),
218 }
219 }
220}
221
222impl Body for HttpBody {
223 type Data = Bytes;
224
225 type Error = std::io::Error;
226
227 fn poll_frame(
228 self: Pin<&mut Self>,
229 cx: &mut Context<'_>,
230 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
231 match self.get_mut() {
232 HttpBody::Standard(full_body) => full_body.frame().poll_unpin(cx).map_err(Error::other),
233
234 HttpBody::Incoming(incoming) => incoming.frame().poll_unpin(cx).map_err(Error::other),
235
236 #[cfg(feature = "generic")]
237 HttpBody::GenericStream(stream) => stream.frame().poll_unpin(cx).map_err(Error::other),
238
239 #[cfg(feature = "compio")]
240 HttpBody::CompioStream(stream) => stream
241 .poll_next_unpin(cx)
242 .map(|b| b.map(|b| b.map(Frame::data))),
243
244 #[cfg(all(feature = "http3", feature = "generic"))]
245 HttpBody::GenericClient(stream) => match ready!(stream.poll_recv_data(cx)) {
246 Ok(frame) => match frame {
247 Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
248 frame.copy_to_bytes(frame.remaining()),
249 )))),
250 None => {
251 cx.waker().wake_by_ref();
252 Poll::Ready(None)
253 }
254 },
255 Err(e) => {
256 println!("Error polling frame: {}", e);
257 Poll::Ready(Some(Err(Error::other(e))))
258 }
259 },
260
261 #[cfg(all(feature = "http3", feature = "generic"))]
262 HttpBody::GenericServer(stream) => match ready!(stream.poll_recv_data(cx)) {
263 Ok(frame) => match frame {
264 Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
265 frame.copy_to_bytes(frame.remaining()),
266 )))),
267 None => {
268 cx.waker().wake_by_ref();
269 Poll::Ready(None)
270 }
271 },
272 Err(e) => Poll::Ready(Some(Err(Error::other(e)))),
273 },
274
275 #[cfg(all(feature = "http3", feature = "compio"))]
276 HttpBody::CompioClient(stream) => match ready!(stream.poll_recv_data(cx)) {
277 Ok(frame) => match frame {
278 Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
279 frame.copy_to_bytes(frame.remaining()),
280 )))),
281 None => {
282 cx.waker().wake_by_ref();
283 Poll::Ready(None)
284 }
285 },
286 Err(e) => {
287 println!("Error polling frame: {}", e);
288 Poll::Ready(Some(Err(Error::other(e))))
289 }
290 },
291
292 #[cfg(all(feature = "http3", feature = "compio"))]
293 HttpBody::CompioServer(stream) => match ready!(stream.poll_recv_data(cx)) {
294 Ok(frame) => match frame {
295 Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
296 frame.copy_to_bytes(frame.remaining()),
297 )))),
298 None => {
299 cx.waker().wake_by_ref();
300 Poll::Ready(None)
301 }
302 },
303 Err(e) => Poll::Ready(Some(Err(Error::other(e)))),
304 },
305 }
306 }
307}
308
309impl Stream for HttpBody {
310 type Item = Result<Frame<Bytes>, Error>;
311
312 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
313 self.poll_frame(cx)
314 }
315}
316
317impl From<&str> for HttpBody {
318 fn from(value: &str) -> Self {
319 HttpBody::from_text(value)
320 }
321}
322
323impl From<String> for HttpBody {
324 fn from(value: String) -> Self {
325 HttpBody::from_text(&value)
326 }
327}
328
329impl From<&[u8]> for HttpBody {
330 fn from(value: &[u8]) -> Self {
331 HttpBody::from_bytes(value)
332 }
333}
334
335impl From<Vec<u8>> for HttpBody {
336 fn from(value: Vec<u8>) -> Self {
337 HttpBody::from_bytes(&value)
338 }
339}
340
341impl From<Bytes> for HttpBody {
342 fn from(value: Bytes) -> Self {
343 HttpBody::from_bytes(&value)
344 }
345}
346
347#[cfg(feature = "compio")]
348impl From<File> for HttpBody {
349 fn from(value: File) -> Self {
350 let stream = from_asyncread(value);
351 HttpBody::from_compio_stream(SendWrapper::new(stream))
352 }
353}
354
355#[cfg(feature = "compio")]
356fn from_asyncread<R>(reader: R) -> impl Stream<Item = Result<Bytes, Error>>
357where
358 R: AsyncReadAt,
359{
360 try_fn_stream(|emitter| async move {
361 let mut pos = 0;
362 loop {
363 let buf = Vec::with_capacity(4096);
364 let BufResult(res, buffer) = reader.read_at(buf, pos).await;
365 let len = res?;
366 if len == 0 {
367 break Ok(());
368 }
369 pos += len as u64;
370 emitter.emit(Bytes::from(buffer)).await
371 }
372 })
373}