Skip to main content

crux_http/
client.rs

1use std::fmt;
2use std::sync::Arc;
3
4use crate::middleware::{Middleware, Next};
5use crate::protocol::{EffectSender, HttpResult, ProtocolRequestBuilder};
6use crate::{Config, Request, RequestBuilder, ResponseAsync, Result};
7use http_types::{Method, Url};
8
9/// An HTTP client, capable of sending `Request`s
10///
11/// Users should only interact with this type from middlewares - normal crux code should
12/// make use of the `Http` capability type instead.
13///
14/// # Examples
15///
16/// ```no_run
17/// use futures_util::future::BoxFuture;
18/// use crux_http::middleware::{Next, Middleware};
19/// use crux_http::{client::Client, Request, RequestBuilder, ResponseAsync, Result};
20/// use std::time;
21/// use std::sync::Arc;
22///
23/// // Fetches an authorization token prior to making a request
24/// fn fetch_auth<'a>(mut req: Request, client: Client, next: Next<'a>) -> BoxFuture<'a, Result<ResponseAsync>> {
25///     Box::pin(async move {
26///         let auth_token = client.get("https://httpbin.org/get")
27///             .await?
28///             .body_string()
29///             .await?;
30///         req.append_header("Authorization", format!("Bearer {auth_token}"));
31///         next.run(req, client).await
32///     })
33/// }
34/// ```
35pub struct Client {
36    config: Config,
37    effect_sender: Arc<dyn EffectSender + Send + Sync>,
38    /// Holds the middleware stack.
39    ///
40    /// Note(Fishrock123): We do actually want this structure.
41    /// The outer Arc allows us to clone in `.send()` without cloning the array.
42    /// The Vec allows us to add middleware at runtime.
43    /// The inner Arc-s allow us to implement Clone without sharing the vector with the parent.
44    /// We don't use a Mutex around the Vec here because adding a middleware during execution should be an error.
45    #[allow(clippy::rc_buffer)]
46    middleware: Arc<Vec<Arc<dyn Middleware>>>,
47}
48
49impl Clone for Client {
50    /// Clones the Client.
51    ///
52    /// This copies the middleware stack from the original, but shares
53    /// the `HttpClient` and http client config of the original.
54    /// Note that individual middleware in the middleware stack are
55    /// still shared by reference.
56    fn clone(&self) -> Self {
57        Self {
58            config: self.config.clone(),
59            effect_sender: Arc::clone(&self.effect_sender),
60            middleware: Arc::new(self.middleware.iter().cloned().collect()),
61        }
62    }
63}
64
65impl fmt::Debug for Client {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(f, "Client {{}}")
68    }
69}
70
71impl Client {
72    #[cfg(test)]
73    pub(crate) fn new<Sender>(sender: Sender) -> Self
74    where
75        Sender: EffectSender + Send + Sync + 'static,
76    {
77        Self {
78            config: Config::default(),
79            effect_sender: Arc::new(sender),
80            middleware: Arc::new(vec![]),
81        }
82    }
83
84    // This is currently dead code because there's no easy way to configure a client.
85    // TODO: fix that in some future PR
86    #[allow(dead_code)]
87    /// Push middleware onto the middleware stack.
88    ///
89    /// See the [middleware] submodule for more information on middleware.
90    ///
91    /// [middleware]: ../middleware/index.html
92    pub(crate) fn with(mut self, middleware: impl Middleware) -> Self {
93        let m = Arc::get_mut(&mut self.middleware)
94            .expect("Registering middleware is not possible after the Client has been used");
95        m.push(Arc::new(middleware));
96        self
97    }
98
99    /// Send a `Request` using this client.
100    ///
101    /// # Errors
102    /// Errors if there is an error sending the request.
103    ///
104    /// # Panics
105    /// Panics if we can't create an HTTP request.
106    pub async fn send(&self, request: impl Into<Request>) -> Result<ResponseAsync> {
107        let mut request: Request = request.into();
108        let middleware = self.middleware.clone();
109
110        let mw_stack = match request.take_middleware() {
111            Some(req_mw) => {
112                let mut mw = Vec::with_capacity(middleware.len() + req_mw.len());
113                mw.extend(middleware.iter().cloned());
114                mw.extend(req_mw);
115                Arc::new(mw)
116            }
117            None => middleware,
118        };
119
120        let next = Next::new(&mw_stack, &|request, client| {
121            Box::pin(async move {
122                let request = request
123                    .into_protocol_request()
124                    .await
125                    .expect("Failed to create request");
126                match client.effect_sender.send(request).await {
127                    HttpResult::Ok(response) => Ok(response.into()),
128                    HttpResult::Err(e) => Err(e),
129                }
130            })
131        });
132
133        let client = Self {
134            config: self.config.clone(),
135            effect_sender: Arc::clone(&self.effect_sender),
136            // Erase the middleware stack for the Client accessible from within middleware.
137            // This avoids gratuitous circular borrow & logic issues.
138            middleware: Arc::new(vec![]),
139        };
140
141        let response = next.run(request, client).await?;
142        Ok(ResponseAsync::new(response.into()))
143    }
144
145    /// Submit a `Request` and get the response body as bytes.
146    ///
147    /// # Errors
148    /// Errors if there is an error sending the request
149    pub async fn recv_bytes(&self, request: impl Into<Request>) -> Result<Vec<u8>> {
150        let mut response = self.send(request.into()).await?;
151        response.body_bytes().await
152    }
153
154    /// Submit a `Request` and get the response body as a string.
155    ///
156    /// # Errors
157    /// Errors if there is an error sending the request
158    pub async fn recv_string(&self, request: impl Into<Request>) -> Result<String> {
159        let mut response = self.send(request.into()).await?;
160        response.body_string().await
161    }
162
163    /// Submit a `Request` and decode the response body from json into a struct.
164    ///
165    /// # Errors
166    /// Errors if there is an error sending the request
167    pub async fn recv_json<T: serde::de::DeserializeOwned>(
168        &self,
169        request: impl Into<Request>,
170    ) -> Result<T> {
171        let mut response = self.send(request.into()).await?;
172        response.body_json::<T>().await
173    }
174
175    /// Submit a `Request` and decode the response body from form encoding into a struct.
176    ///
177    /// # Errors
178    ///
179    /// Any I/O error encountered while reading the body is immediately returned
180    /// as an `Err`.
181    ///
182    /// If the body cannot be interpreted as valid json for the target type `T`,
183    /// an `Err` is returned.
184    pub async fn recv_form<T: serde::de::DeserializeOwned>(
185        &self,
186        request: impl Into<Request>,
187    ) -> Result<T> {
188        let mut response = self.send(request.into()).await?;
189        response.body_form::<T>().await
190    }
191
192    /// Perform an HTTP `GET` request using the `Client` connection.
193    ///
194    /// # Panics
195    ///
196    /// This will panic if a malformed URL is passed.
197    ///
198    /// # Errors
199    ///
200    /// Returns errors from the middleware, http backend, and network sockets.
201    pub fn get(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
202        RequestBuilder::new_for_middleware(Method::Get, self.url(uri), self.clone())
203    }
204
205    /// Perform an HTTP `HEAD` request using the `Client` connection.
206    ///
207    /// # Panics
208    ///
209    /// This will panic if a malformed URL is passed.
210    ///
211    /// # Errors
212    ///
213    /// Returns errors from the middleware, http backend, and network sockets.
214    pub fn head(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
215        RequestBuilder::new_for_middleware(Method::Head, self.url(uri), self.clone())
216    }
217
218    /// Perform an HTTP `POST` request using the `Client` connection.
219    ///
220    /// # Panics
221    ///
222    /// This will panic if a malformed URL is passed.
223    ///
224    /// # Errors
225    ///
226    /// Returns errors from the middleware, http backend, and network sockets.
227    pub fn post(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
228        RequestBuilder::new_for_middleware(Method::Post, self.url(uri), self.clone())
229    }
230
231    /// Perform an HTTP `PUT` request using the `Client` connection.
232    ///
233    /// # Panics
234    ///
235    /// This will panic if a malformed URL is passed.
236    ///
237    /// # Errors
238    ///
239    /// Returns errors from the middleware, http backend, and network sockets.
240    pub fn put(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
241        RequestBuilder::new_for_middleware(Method::Put, self.url(uri), self.clone())
242    }
243
244    /// Perform an HTTP `DELETE` request using the `Client` connection.
245    ///
246    /// # Panics
247    ///
248    /// This will panic if a malformed URL is passed.
249    ///
250    /// # Errors
251    ///
252    /// Returns errors from the middleware, http backend, and network sockets.
253    pub fn delete(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
254        RequestBuilder::new_for_middleware(Method::Delete, self.url(uri), self.clone())
255    }
256
257    /// Perform an HTTP `CONNECT` request using the `Client` connection.
258    ///
259    /// # Panics
260    ///
261    /// This will panic if a malformed URL is passed.
262    ///
263    /// # Errors
264    ///
265    /// Returns errors from the middleware, http backend, and network sockets.
266    pub fn connect(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
267        RequestBuilder::new_for_middleware(Method::Connect, self.url(uri), self.clone())
268    }
269
270    /// Perform an HTTP `OPTIONS` request using the `Client` connection.
271    ///
272    /// # Panics
273    ///
274    /// This will panic if a malformed URL is passed.
275    ///
276    /// # Errors
277    ///
278    /// Returns errors from the middleware, http backend, and network sockets.
279    pub fn options(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
280        RequestBuilder::new_for_middleware(Method::Options, self.url(uri), self.clone())
281    }
282
283    /// Perform an HTTP `TRACE` request using the `Client` connection.
284    ///
285    /// # Panics
286    ///
287    /// This will panic if a malformed URL is passed.
288    ///
289    /// # Errors
290    ///
291    /// Returns errors from the middleware, http backend, and network sockets.
292    pub fn trace(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
293        RequestBuilder::new_for_middleware(Method::Trace, self.url(uri), self.clone())
294    }
295
296    /// Perform an HTTP `PATCH` request using the `Client` connection.
297    ///
298    /// # Panics
299    ///
300    /// This will panic if a malformed URL is passed.
301    ///
302    /// # Errors
303    ///
304    /// Returns errors from the middleware, http backend, and network sockets.
305    pub fn patch(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
306        RequestBuilder::new_for_middleware(Method::Patch, self.url(uri), self.clone())
307    }
308
309    /// Perform a HTTP request with the given verb using the `Client` connection.
310    ///
311    /// # Panics
312    ///
313    /// This will panic if a malformed URL is passed.
314    ///
315    /// # Errors
316    ///
317    /// Returns errors from the middleware, http backend, and network sockets.
318    pub fn request(&self, verb: Method, uri: impl AsRef<str>) -> RequestBuilder<()> {
319        RequestBuilder::new_for_middleware(verb, self.url(uri), self.clone())
320    }
321
322    /// Get the current configuration.
323    #[must_use]
324    pub fn config(&self) -> &Config {
325        &self.config
326    }
327
328    // private function to generate a url based on the base_path
329    fn url(&self, uri: impl AsRef<str>) -> Url {
330        match &self.config.base_url {
331            None => uri.as_ref().parse().unwrap(),
332            Some(base) => base.join(uri.as_ref()).unwrap(),
333        }
334    }
335}
336
337#[cfg(test)]
338mod client_tests {
339    use super::Client;
340    use crate::protocol::{HttpRequest, HttpResponse};
341    use crate::testing::FakeShell;
342
343    #[futures_test::test]
344    async fn an_http_get() {
345        let mut shell = FakeShell::default();
346        shell.provide_response(HttpResponse::ok().body("Hello World!").build());
347
348        let client = Client::new(shell.clone());
349
350        let mut response = client.get("https://example.com").await.unwrap();
351        assert_eq!(response.body_string().await.unwrap(), "Hello World!");
352
353        assert_eq!(
354            shell.take_requests_received(),
355            vec![HttpRequest::get("https://example.com/").build()]
356        );
357    }
358}