libdd_common/
http_common.rs1use core::convert::Infallible;
5use core::fmt;
6use core::time::Duration;
7
8use thiserror::Error;
9
10#[derive(Debug, Clone, Copy)]
13pub enum ErrorKind {
14 Parse,
15 Closed,
16 Canceled,
17 Incomplete,
18 WriteAborted,
19 ParseStatus,
20 Timeout,
21 Other,
22}
23
24#[derive(Debug, Error)]
25pub struct ClientError {
26 source: anyhow::Error,
27 kind: ErrorKind,
28}
29
30impl core::fmt::Display for ClientError {
31 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32 self.source.fmt(f)
33 }
34}
35
36impl ClientError {
37 pub fn kind(&self) -> ErrorKind {
38 self.kind
39 }
40}
41
42#[derive(Debug)]
43pub enum Error {
44 Client(ClientError),
45 Other(anyhow::Error),
46 Infallible(Infallible),
47}
48
49impl fmt::Display for Error {
50 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51 match self {
52 Error::Client(e) => write!(f, "client error: {e}"),
53 Error::Infallible(e) => match *e {},
54 Error::Other(e) => write!(f, "other error: {e}"),
55 }
56 }
57}
58
59impl From<std::io::Error> for Error {
60 fn from(value: std::io::Error) -> Self {
61 Self::Other(value.into())
62 }
63}
64
65impl From<http::Error> for Error {
66 fn from(value: http::Error) -> Self {
67 Self::Other(value.into())
68 }
69}
70
71impl core::error::Error for Error {}
72
73#[cfg(not(target_arch = "wasm32"))]
76mod native {
77 use super::*;
78 use core::error::Error as _;
79 use core::task::Poll;
80
81 use crate::connector::Connector;
82 use http_body_util::BodyExt;
83 use hyper::body::Incoming;
84 use hyper_util::rt::TokioTimer;
85 use pin_project::pin_project;
86
87 impl From<hyper::Error> for ClientError {
88 fn from(source: hyper::Error) -> Self {
89 use ErrorKind::*;
90 let kind = if source.is_canceled() {
91 Canceled
92 } else if source.is_parse() {
93 Parse
94 } else if source.is_parse_status() {
95 ParseStatus
96 } else if source.is_incomplete_message() {
97 Incomplete
98 } else if source.is_body_write_aborted() {
99 WriteAborted
100 } else if source.is_timeout() {
101 Timeout
102 } else if source.is_closed() {
103 Closed
104 } else {
105 Other
106 };
107 Self {
108 kind,
109 source: source.into(),
110 }
111 }
112 }
113
114 pub type HttpResponse = http::Response<Body>;
115 pub type HttpRequest = http::Request<Body>;
116 pub type HttpRequestError = hyper_util::client::legacy::Error;
117
118 pub type ResponseFuture = hyper_util::client::legacy::ResponseFuture;
119
120 pub(crate) const PERIODIC_POOL_IDLE_TIMEOUT: Duration = Duration::from_secs(5);
127 pub(crate) const POOL_MAX_IDLE: usize = 20;
130
131 pub fn new_client_periodic() -> GenericHttpClient<Connector> {
139 hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::default())
140 .pool_timer(TokioTimer::new())
141 .pool_idle_timeout(PERIODIC_POOL_IDLE_TIMEOUT)
142 .pool_max_idle_per_host(POOL_MAX_IDLE)
143 .build(Connector::default())
144 }
145
146 pub fn new_default_client() -> GenericHttpClient<Connector> {
150 hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::default())
151 .pool_max_idle_per_host(POOL_MAX_IDLE)
152 .build(Connector::default())
153 }
154
155 pub fn into_response(response: hyper::Response<Incoming>) -> HttpResponse {
156 response.map(Body::Incoming)
157 }
158
159 impl From<HttpRequestError> for ClientError {
160 fn from(err: HttpRequestError) -> Self {
161 let kind = if let Some(source) = err.source().and_then(|s| s.downcast_ref::<Error>()) {
162 match source {
163 Error::Client(client_error) => client_error.kind,
164 Error::Other(_) => ErrorKind::Other,
165 Error::Infallible(infallible) => match *infallible {},
166 }
167 } else if err.is_connect() {
168 ErrorKind::Closed
169 } else {
170 ErrorKind::Other
171 };
172 Self {
173 source: err.into(),
174 kind,
175 }
176 }
177 }
178
179 pub async fn collect_response_bytes(response: HttpResponse) -> Result<bytes::Bytes, Error> {
180 Ok(response.into_body().collect().await?.to_bytes())
181 }
182
183 pub fn mock_response(
184 builder: http::response::Builder,
185 body: hyper::body::Bytes,
186 ) -> anyhow::Result<HttpResponse> {
187 Ok(builder.body(Body::from_bytes(body))?)
188 }
189
190 pub fn empty_response(builder: http::response::Builder) -> Result<HttpResponse, Error> {
191 Ok(builder.body(Body::empty())?)
192 }
193
194 #[pin_project(project=BodyProj)]
195 #[derive(Debug)]
196 pub enum Body {
197 Single(#[pin] http_body_util::Full<hyper::body::Bytes>),
198 Empty(#[pin] http_body_util::Empty<hyper::body::Bytes>),
199 Boxed(#[pin] http_body_util::combinators::BoxBody<hyper::body::Bytes, anyhow::Error>),
200 Channel(#[pin] tokio::sync::mpsc::Receiver<hyper::body::Bytes>),
201 Incoming(#[pin] hyper::body::Incoming),
202 }
203
204 pub struct Sender {
205 tx: tokio::sync::mpsc::Sender<hyper::body::Bytes>,
206 }
207
208 impl Sender {
209 pub async fn send_data(&self, data: hyper::body::Bytes) -> anyhow::Result<()> {
210 self.tx.send(data).await?;
211 Ok(())
212 }
213 }
214
215 impl Body {
216 pub fn empty() -> Self {
217 Body::Empty(http_body_util::Empty::new())
218 }
219
220 pub fn from_bytes(bytes: hyper::body::Bytes) -> Self {
221 Body::Single(http_body_util::Full::new(bytes))
222 }
223
224 pub fn boxed<
225 E: core::error::Error + Sync + Send + 'static,
226 T: hyper::body::Body<Data = hyper::body::Bytes, Error = E> + Sync + Send + 'static,
227 >(
228 body: T,
229 ) -> Self {
230 Body::Boxed(body.map_err(anyhow::Error::from).boxed())
231 }
232
233 pub fn channel() -> (Sender, Self) {
234 let (tx, rx) = tokio::sync::mpsc::channel(1);
235 (Sender { tx }, Body::Channel(rx))
236 }
237
238 pub fn incoming(incoming: Incoming) -> Self {
239 Body::Incoming(incoming)
240 }
241 }
242
243 impl Default for Body {
244 fn default() -> Self {
245 Body::empty()
246 }
247 }
248
249 impl From<&'static str> for Body {
250 fn from(s: &'static str) -> Self {
251 Body::from_bytes(hyper::body::Bytes::from_static(s.as_bytes()))
252 }
253 }
254
255 impl From<Vec<u8>> for Body {
256 fn from(s: Vec<u8>) -> Self {
257 Body::from_bytes(hyper::body::Bytes::from(s))
258 }
259 }
260
261 impl From<String> for Body {
262 fn from(s: String) -> Self {
263 Body::from_bytes(hyper::body::Bytes::from(s))
264 }
265 }
266
267 impl hyper::body::Body for Body {
268 type Data = hyper::body::Bytes;
269
270 type Error = Error;
271
272 fn poll_frame(
273 self: core::pin::Pin<&mut Self>,
274 cx: &mut core::task::Context<'_>,
275 ) -> core::task::Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
276 match self.project() {
277 BodyProj::Single(pin) => pin.poll_frame(cx).map_err(Error::Infallible),
278 BodyProj::Empty(pin) => pin.poll_frame(cx).map_err(Error::Infallible),
279 BodyProj::Boxed(pin) => pin.poll_frame(cx).map_err(Error::Other),
280 BodyProj::Channel(pin) => {
281 let data = match pin.get_mut().poll_recv(cx) {
282 Poll::Ready(Some(data)) => data,
283 Poll::Ready(None) => return Poll::Ready(None),
284 Poll::Pending => return Poll::Pending,
285 };
286 Poll::Ready(Some(Ok(hyper::body::Frame::data(data))))
287 }
288 BodyProj::Incoming(pin) => pin
289 .poll_frame(cx)
290 .map_err(|e| Error::Client(ClientError::from(e))),
291 }
292 }
293
294 fn is_end_stream(&self) -> bool {
295 match self {
296 Body::Single(body) => body.is_end_stream(),
297 Body::Empty(body) => body.is_end_stream(),
298 Body::Boxed(body) => body.is_end_stream(),
299 Body::Channel(body) => body.is_closed() && body.is_empty(),
300 Body::Incoming(body) => body.is_end_stream(),
301 }
302 }
303
304 fn size_hint(&self) -> http_body::SizeHint {
305 match self {
306 Body::Single(body) => body.size_hint(),
307 Body::Empty(body) => body.size_hint(),
308 Body::Boxed(body) => body.size_hint(),
309 Body::Channel(_) => http_body::SizeHint::default(),
310 Body::Incoming(body) => body.size_hint(),
311 }
312 }
313 }
314
315 pub type GenericHttpClient<C> = hyper_util::client::legacy::Client<C, Body>;
316
317 pub fn client_builder() -> hyper_util::client::legacy::Builder {
318 hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::default())
319 }
320}
321
322#[cfg(not(target_arch = "wasm32"))]
323pub use native::*;