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