kael_http_client/
async_body.rs1use std::{
2 io::{Cursor, Read},
3 pin::Pin,
4 task::Poll,
5};
6
7use bytes::Bytes;
8use futures::AsyncRead;
9use http_body::{Body, Frame, SizeHint};
10
11pub struct AsyncBody(Inner);
15
16enum Inner {
17 Empty,
18 Bytes(std::io::Cursor<Bytes>),
19 AsyncReader(Pin<Box<dyn futures::AsyncRead + Send + Sync>>),
20}
21
22impl AsyncBody {
23 pub fn empty() -> Self {
28 Self(Inner::Empty)
29 }
30 pub fn from_reader<R>(read: R) -> Self
32 where
33 R: AsyncRead + Send + Sync + 'static,
34 {
35 Self(Inner::AsyncReader(Box::pin(read)))
36 }
37
38 pub fn from_bytes(bytes: Bytes) -> Self {
40 Self(Inner::Bytes(Cursor::new(bytes)))
41 }
42
43 pub async fn read_to_end_limited(&mut self, max_bytes: usize) -> std::io::Result<Vec<u8>> {
46 use futures::AsyncReadExt as _;
47
48 let mut output = Vec::new();
49 let mut chunk = [0u8; 8192];
50 loop {
51 let read = self.read(&mut chunk).await?;
52 if read == 0 {
53 return Ok(output);
54 }
55 let next_len = output.len().checked_add(read).ok_or_else(|| {
56 std::io::Error::new(std::io::ErrorKind::OutOfMemory, "HTTP body size overflow")
57 })?;
58 if next_len > max_bytes {
59 return Err(std::io::Error::new(
60 std::io::ErrorKind::InvalidData,
61 format!("HTTP body exceeds {max_bytes} byte limit"),
62 ));
63 }
64 output.try_reserve(read).map_err(|error| {
65 std::io::Error::new(
66 std::io::ErrorKind::OutOfMemory,
67 format!("could not reserve HTTP body buffer: {error}"),
68 )
69 })?;
70 output.extend_from_slice(&chunk[..read]);
71 }
72 }
73}
74
75impl Default for AsyncBody {
76 fn default() -> Self {
77 Self(Inner::Empty)
78 }
79}
80
81impl From<()> for AsyncBody {
82 fn from(_: ()) -> Self {
83 Self(Inner::Empty)
84 }
85}
86
87impl From<Bytes> for AsyncBody {
88 fn from(bytes: Bytes) -> Self {
89 Self::from_bytes(bytes)
90 }
91}
92
93impl From<Vec<u8>> for AsyncBody {
94 fn from(body: Vec<u8>) -> Self {
95 Self::from_bytes(body.into())
96 }
97}
98
99impl From<String> for AsyncBody {
100 fn from(body: String) -> Self {
101 Self::from_bytes(body.into())
102 }
103}
104
105impl From<&'static [u8]> for AsyncBody {
106 #[inline]
107 fn from(s: &'static [u8]) -> Self {
108 Self::from_bytes(Bytes::from_static(s))
109 }
110}
111
112impl From<&'static str> for AsyncBody {
113 #[inline]
114 fn from(s: &'static str) -> Self {
115 Self::from_bytes(Bytes::from_static(s.as_bytes()))
116 }
117}
118
119#[cfg(all(feature = "reqwest", not(target_arch = "wasm32")))]
120impl TryFrom<reqwest::Body> for AsyncBody {
121 type Error = anyhow::Error;
122
123 fn try_from(value: reqwest::Body) -> Result<Self, Self::Error> {
124 value
125 .as_bytes()
126 .ok_or_else(|| anyhow::anyhow!("Underlying data is a stream"))
127 .map(|bytes| Self::from_bytes(Bytes::copy_from_slice(bytes)))
128 }
129}
130
131impl<T: Into<Self>> From<Option<T>> for AsyncBody {
132 fn from(body: Option<T>) -> Self {
133 match body {
134 Some(body) => body.into(),
135 None => Self::empty(),
136 }
137 }
138}
139
140impl futures::AsyncRead for AsyncBody {
141 fn poll_read(
142 self: Pin<&mut Self>,
143 cx: &mut std::task::Context<'_>,
144 buf: &mut [u8],
145 ) -> std::task::Poll<std::io::Result<usize>> {
146 let inner = &mut self.get_mut().0;
147 match inner {
148 Inner::Empty => Poll::Ready(Ok(0)),
149 Inner::Bytes(cursor) => Poll::Ready(cursor.read(buf)),
151 Inner::AsyncReader(async_reader) => {
152 AsyncRead::poll_read(async_reader.as_mut(), cx, buf)
153 }
154 }
155 }
156}
157
158impl Body for AsyncBody {
159 type Data = Bytes;
160 type Error = std::io::Error;
161
162 fn poll_frame(
163 mut self: Pin<&mut Self>,
164 cx: &mut std::task::Context<'_>,
165 ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
166 let mut buffer = vec![0; 8192];
167 match AsyncRead::poll_read(self.as_mut(), cx, &mut buffer) {
168 Poll::Ready(Ok(0)) => Poll::Ready(None),
169 Poll::Ready(Ok(n)) => {
170 let data = Bytes::copy_from_slice(&buffer[..n]);
171 Poll::Ready(Some(Ok(Frame::data(data))))
172 }
173 Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))),
174 Poll::Pending => Poll::Pending,
175 }
176 }
177
178 fn is_end_stream(&self) -> bool {
179 match &self.0 {
180 Inner::Empty => true,
181 Inner::Bytes(cursor) => cursor.position() >= cursor.get_ref().len() as u64,
182 Inner::AsyncReader(_) => false,
183 }
184 }
185
186 fn size_hint(&self) -> SizeHint {
187 match &self.0 {
188 Inner::Empty => SizeHint::with_exact(0),
189 Inner::Bytes(cursor) => {
190 let remaining = (cursor.get_ref().len() as u64).saturating_sub(cursor.position());
191 SizeHint::with_exact(remaining)
192 }
193 Inner::AsyncReader(_) => SizeHint::default(),
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use futures::executor::block_on;
201 use http_body::Body as _;
202
203 use super::*;
204
205 #[test]
206 fn bounded_read_and_size_hint_track_remaining_bytes() {
207 let mut body = AsyncBody::from(vec![1, 2, 3]);
208 assert_eq!(body.size_hint().exact(), Some(3));
209 let bytes = block_on(body.read_to_end_limited(3)).unwrap();
210 assert_eq!(bytes, [1, 2, 3]);
211 assert!(body.is_end_stream());
212
213 let mut oversized = AsyncBody::from(vec![0; 4]);
214 assert!(block_on(oversized.read_to_end_limited(3)).is_err());
215 }
216}