Skip to main content

ts_http_util/
client.rs

1use std::sync::Arc;
2
3use http::{HeaderName, HeaderValue, Request, Response};
4use hyper::body::{Body, Incoming};
5
6use crate::Error;
7
8/// An HTTP client that can asynchronously send requests and receive responses.
9///
10/// This trait is HTTP version agnostic; it can be implemented for any version of HTTP.
11/// Version-specific features, such as connecting to a server or the HTTP/1.1 protocol upgrade
12/// mechanism, must be implemented individually for concrete implementations in addition to the
13/// `send` method.
14pub trait Client<B>
15where
16    B: Body + Send + 'static,
17    <B as Body>::Data: Send,
18    B::Error: Send + Sync + 'static,
19{
20    /// Sends the given HTTP [`Request`] to the connected server and returns the [`Response`].
21    ///
22    /// Note that the [`Response`] body of [`Incoming`] means the body must be collected separately
23    /// from the [`Response`] status and headers; this allows the status/headers to be checked
24    /// before the full body has arrived.
25    fn send(
26        &self,
27        req: Request<B>,
28    ) -> impl Future<Output = Result<Response<Incoming>, Error>> + Send;
29}
30
31/// Extension trait adding specific HTTP method functions (GET, POST, etc.) on top of the base
32/// [`Client`] trait.
33pub trait ClientExt<B>: Client<B>
34where
35    B: Body + Send + 'static,
36    <B as Body>::Data: Send,
37    B::Error: Send + Sync + 'static,
38{
39    /// Sends an HTTP GET request to the connected server and returns the [`Response`].
40    ///
41    /// By definition, HTTP GET requests do not contain a body. Note that the [`Response`] body of
42    /// [`Incoming`] means the body must be collected separately from the [`Response`] status and
43    /// headers; this allows the status/headers to be checked before the full body has arrived.
44    fn get(
45        &self,
46        url: &url::Url,
47        headers: impl IntoIterator<Item = (HeaderName, HeaderValue)>,
48    ) -> impl Future<Output = Result<Response<Incoming>, Error>>
49    where
50        B: Default,
51    {
52        let mut req = Request::get(url.as_str());
53
54        if let Some(hdrs) = req.headers_mut() {
55            hdrs.extend(crate::host_header(url));
56            hdrs.extend(headers);
57        }
58
59        async move {
60            let req = req.body(Default::default()).map_err(|e| {
61                tracing::error!(error = %e, "constructing request");
62                Error::InvalidParam
63            })?;
64
65            self.send(req).await
66        }
67    }
68
69    /// Sends an HTTP POST request to the connected server and returns the [`Response`].
70    ///
71    /// Note that the [`Response`] body of [`Incoming`] means the body must be collected separately
72    /// from the [`Response`] status and headers; this allows the status/headers to be checked
73    /// before the full body has arrived.
74    fn post(
75        &self,
76        url: &url::Url,
77        headers: impl IntoIterator<Item = (HeaderName, HeaderValue)>,
78        body: B,
79    ) -> impl Future<Output = Result<Response<Incoming>, Error>> {
80        let mut req = Request::post(url.as_str());
81
82        if let Some(hdrs) = req.headers_mut() {
83            hdrs.extend(crate::host_header(url));
84            hdrs.extend(headers);
85        }
86
87        async move {
88            let req = req.body(body).map_err(|e| {
89                tracing::error!(error = %e, "constructing request");
90                Error::InvalidParam
91            })?;
92
93            self.send(req).await
94        }
95    }
96}
97
98impl<T, B> ClientExt<B> for T
99where
100    T: Client<B>,
101    B: Body + Send + 'static,
102    <B as Body>::Data: Send,
103    B::Error: Send + Sync + 'static,
104{
105}
106
107impl<T, B> Client<B> for Arc<T>
108where
109    T: Client<B>,
110    B: Body + Send + 'static,
111    <B as Body>::Data: Send,
112    B::Error: Send + Sync + 'static,
113{
114    fn send(
115        &self,
116        req: Request<B>,
117    ) -> impl Future<Output = Result<Response<Incoming>, Error>> + Send {
118        self.as_ref().send(req)
119    }
120}
121
122impl<T, B> Client<B> for &T
123where
124    T: Client<B>,
125    B: Body + Send + 'static,
126    <B as Body>::Data: Send,
127    B::Error: Send + Sync + 'static,
128{
129    fn send(
130        &self,
131        req: Request<B>,
132    ) -> impl Future<Output = Result<Response<Incoming>, Error>> + Send {
133        (**self).send(req)
134    }
135}
136
137impl<T, B> Client<B> for &mut T
138where
139    T: Client<B>,
140    B: Body + Send + 'static,
141    <B as Body>::Data: Send,
142    B::Error: Send + Sync + 'static,
143{
144    fn send(
145        &self,
146        req: Request<B>,
147    ) -> impl Future<Output = Result<Response<Incoming>, Error>> + Send {
148        (**self).send(req)
149    }
150}