Skip to main content

hyper_body_utils/
lib.rs

1#[cfg(feature = "http3")]
2use bytes::Buf;
3#[cfg(feature = "http3")]
4use futures::ready;
5
6use bytes::Bytes;
7use futures::{stream, FutureExt, Stream, TryStreamExt};
8use std::pin::Pin;
9use std::task::{Context, Poll};
10
11#[cfg(feature = "http3")]
12use h3::client::RequestStream as ClientRequestStream;
13#[cfg(feature = "http3")]
14use h3::server::RequestStream as ServerRequestStream;
15#[cfg(feature = "http3")]
16use h3_quinn::RecvStream;
17
18pub use http_body_util::BodyExt;
19
20use http_body_util::StreamBody;
21use hyper::body::{Body, Frame, Incoming};
22
23#[cfg(any(feature = "tokio-rt", feature = "smol-rt"))]
24use http_body_util::combinators::BoxBody;
25
26#[cfg(feature = "smol-rt")]
27use smol::fs::File;
28#[cfg(feature = "smol-rt")]
29use smol::io::AsyncReadExt;
30
31#[cfg(feature = "tokio-rt")]
32use tokio::fs::File;
33
34#[cfg(feature = "compio-rt")]
35use compio_fs::File;
36#[cfg(feature = "compio-rt")]
37use compio_io::AsyncReadExt;
38#[cfg(feature = "compio-rt")]
39use send_wrapper::SendWrapper;
40
41mod tests;
42
43pub enum HttpBody {
44    Incoming(Incoming),
45    #[cfg(any(feature = "tokio-rt", feature = "smol-rt"))]
46    Stream(BoxBody<Bytes, std::io::Error>),
47    #[cfg(feature = "compio-rt")]
48    Stream(Pin<Box<dyn Stream<Item = Result<Frame<Bytes>, std::io::Error>> + Send>>),
49    #[cfg(feature = "http3")]
50    QuicClientIncoming(ClientRequestStream<RecvStream, Bytes>),
51    #[cfg(feature = "http3")]
52    QuicServerIncoming(ServerRequestStream<RecvStream, Bytes>),
53}
54
55impl HttpBody {
56    pub fn from_incoming(incoming: Incoming) -> Self {
57        HttpBody::Incoming(incoming)
58    }
59
60    #[cfg(feature = "http3")]
61    pub fn from_quic_client(stream: ClientRequestStream<RecvStream, Bytes>) -> Self {
62        HttpBody::QuicClientIncoming(stream)
63    }
64
65    #[cfg(feature = "http3")]
66    pub fn from_quic_server(stream: ServerRequestStream<RecvStream, Bytes>) -> Self {
67        HttpBody::QuicServerIncoming(stream)
68    }
69
70    pub fn from_text(text: &str) -> Self {
71        Self::from_bytes(text.as_bytes())
72    }
73
74    pub fn from_file(file: File) -> Self {
75        #[cfg(feature = "tokio-rt")]
76        {
77            let content = tokio_util::io::ReaderStream::new(file).map_ok(Frame::data);
78            let body = StreamBody::new(content);
79            HttpBody::Stream(BodyExt::boxed(body))
80        }
81
82        #[cfg(feature = "smol-rt")]
83        {
84            let content = file
85                .bytes()
86                .map_ok(|data| Frame::data(Bytes::copy_from_slice(&[data])));
87            let body = StreamBody::new(content);
88            HttpBody::Stream(BodyExt::boxed(body))
89        }
90
91        #[cfg(feature = "compio-rt")]
92        {
93            let content = std::io::Cursor::new(file).read_only().bytes();
94            let body = StreamBody::new(content.map_ok(Frame::data));
95            HttpBody::Stream(Box::pin(SendWrapper::new(body)))
96        }
97    }
98
99    pub fn from_bytes(bytes: &[u8]) -> Self {
100        #[cfg(feature = "tokio-rt")]
101        {
102            let all_bytes = Bytes::copy_from_slice(bytes);
103            let content = stream::iter(vec![Ok(all_bytes)]).map_ok(Frame::data);
104            let body = StreamBody::new(content);
105            HttpBody::Stream(BodyExt::boxed(body))
106        }
107
108        #[cfg(feature = "smol-rt")]
109        {
110            let all_bytes = Bytes::copy_from_slice(bytes);
111            let content = stream::iter(vec![Ok(all_bytes)]).map_ok(Frame::data);
112            let body = StreamBody::new(content);
113            HttpBody::Stream(BodyExt::boxed(body))
114        }
115
116        #[cfg(feature = "compio-rt")]
117        {
118            let all_bytes = Bytes::copy_from_slice(bytes);
119            let content = stream::iter(vec![Ok(all_bytes)]).map_ok(Frame::data);
120            let body = StreamBody::new(content);
121            HttpBody::Stream(Box::pin(SendWrapper::new(body)))
122        }
123    }
124}
125
126impl Body for HttpBody {
127    type Data = Bytes;
128
129    type Error = std::io::Error;
130
131    fn poll_frame(
132        self: Pin<&mut Self>,
133        cx: &mut Context<'_>,
134    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
135        match self.get_mut() {
136            HttpBody::Incoming(incoming) => incoming
137                .frame()
138                .poll_unpin(cx)
139                .map_err(std::io::Error::other),
140
141            #[cfg(any(feature = "tokio-rt", feature = "smol-rt"))]
142            HttpBody::Stream(stream) => {
143                stream.frame().poll_unpin(cx).map_err(std::io::Error::other)
144            }
145
146            #[cfg(feature = "compio-rt")]
147            HttpBody::Stream(stream) => {
148                stream::StreamExt::poll_next_unpin(stream, cx).map_err(std::io::Error::other)
149            }
150
151            #[cfg(feature = "http3")]
152            HttpBody::QuicClientIncoming(stream) => match ready!(stream.poll_recv_data(cx)) {
153                Ok(frame) => match frame {
154                    Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
155                        frame.copy_to_bytes(frame.remaining()),
156                    )))),
157                    None => {
158                        cx.waker().wake_by_ref();
159                        Poll::Ready(None)
160                    }
161                },
162                Err(e) => {
163                    println!("Error polling frame: {}", e);
164                    Poll::Ready(Some(Err(std::io::Error::other(e))))
165                }
166            },
167            #[cfg(feature = "http3")]
168            HttpBody::QuicServerIncoming(stream) => match ready!(stream.poll_recv_data(cx)) {
169                Ok(frame) => match frame {
170                    Some(mut frame) => Poll::Ready(Some(Ok(Frame::data(
171                        frame.copy_to_bytes(frame.remaining()),
172                    )))),
173                    None => {
174                        cx.waker().wake_by_ref();
175                        Poll::Ready(None)
176                    }
177                },
178                Err(e) => Poll::Ready(Some(Err(std::io::Error::other(e)))),
179            },
180        }
181    }
182}
183
184impl Stream for HttpBody {
185    type Item = Result<Frame<Bytes>, std::io::Error>;
186
187    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
188        self.poll_frame(cx)
189    }
190}
191
192/*
193pub struct FileStream {
194    file: OwnedMutexGuard<File>,
195}
196
197impl FileStream {
198    pub fn new(file: OwnedMutexGuard<File>) -> Self {
199        FileStream { file }
200    }
201}
202
203impl Stream for FileStream {
204    type Item = Result<Frame<Bytes>, std::io::Error>;
205
206    #[hotpath::measure]
207    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
208        let mut me = self.as_mut();
209        let mut buf = BytesMut::with_capacity(70);
210        let mut read_buf = Box::pin(me.file.read_buf(&mut buf));
211        match read_buf.poll_unpin(cx) {
212            Poll::Ready(value) => match value {
213                Ok(size) => {
214                    if size == 0 {
215                        Poll::Ready(None)
216                    } else {
217                        Poll::Ready(Some(Ok(Frame::data(buf.into()))))
218                    }
219                }
220                Err(e) => Poll::Ready(Some(Err(e))),
221            },
222            Poll::Pending => Poll::Pending,
223        }
224    }
225}
226*/