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    pub(crate) fn new<Sender>(sender: Sender) -> Self
73    where
74        Sender: EffectSender + Send + Sync + 'static,
75    {
76        Self {
77            config: Config::default(),
78            effect_sender: Arc::new(sender),
79            middleware: Arc::new(vec![]),
80        }
81    }
82
83    // This is currently dead code because there's no easy way to configure a client.
84    // TODO: fix that in some future PR
85    #[allow(dead_code)]
86    /// Push middleware onto the middleware stack.
87    ///
88    /// See the [middleware] submodule for more information on middleware.
89    ///
90    /// [middleware]: ../middleware/index.html
91    pub(crate) fn with(mut self, middleware: impl Middleware) -> Self {
92        let m = Arc::get_mut(&mut self.middleware)
93            .expect("Registering middleware is not possible after the Client has been used");
94        m.push(Arc::new(middleware));
95        self
96    }
97
98    /// Send a `Request` using this client.
99    ///
100    /// # Errors
101    /// Errors if there is an error sending the request.
102    ///
103    /// # Panics
104    /// Panics if we can't create an HTTP request.
105    pub async fn send(&self, request: impl Into<Request>) -> Result<ResponseAsync> {
106        let mut request: Request = request.into();
107        let middleware = self.middleware.clone();
108
109        let mw_stack = match request.take_middleware() {
110            Some(req_mw) => {
111                let mut mw = Vec::with_capacity(middleware.len() + req_mw.len());
112                mw.extend(middleware.iter().cloned());
113                mw.extend(req_mw);
114                Arc::new(mw)
115            }
116            None => middleware,
117        };
118
119        let next = Next::new(&mw_stack, &|request, client| {
120            Box::pin(async move {
121                let request = request
122                    .into_protocol_request()
123                    .await
124                    .expect("Failed to create request");
125                match client.effect_sender.send(request).await {
126                    HttpResult::Ok(response) => Ok(response.into()),
127                    HttpResult::Err(e) => Err(e),
128                }
129            })
130        });
131
132        let client = Self {
133            config: self.config.clone(),
134            effect_sender: Arc::clone(&self.effect_sender),
135            // Erase the middleware stack for the Client accessible from within middleware.
136            // This avoids gratuitous circular borrow & logic issues.
137            middleware: Arc::new(vec![]),
138        };
139
140        let response = next.run(request, client).await?;
141        Ok(ResponseAsync::new(response.into()))
142    }
143
144    /// Submit a `Request` and get the response body as bytes.
145    ///
146    /// # Errors
147    /// Errors if there is an error sending the request
148    pub async fn recv_bytes(&self, request: impl Into<Request>) -> Result<Vec<u8>> {
149        let mut response = self.send(request.into()).await?;
150        response.body_bytes().await
151    }
152
153    /// Submit a `Request` and get the response body as a string.
154    ///
155    /// # Errors
156    /// Errors if there is an error sending the request
157    pub async fn recv_string(&self, request: impl Into<Request>) -> Result<String> {
158        let mut response = self.send(request.into()).await?;
159        response.body_string().await
160    }
161
162    /// Submit a `Request` and decode the response body from json into a struct.
163    ///
164    /// # Errors
165    /// Errors if there is an error sending the request
166    pub async fn recv_json<T: serde::de::DeserializeOwned>(
167        &self,
168        request: impl Into<Request>,
169    ) -> Result<T> {
170        let mut response = self.send(request.into()).await?;
171        response.body_json::<T>().await
172    }
173
174    /// Submit a `Request` and decode the response body from form encoding into a struct.
175    ///
176    /// # Errors
177    ///
178    /// Any I/O error encountered while reading the body is immediately returned
179    /// as an `Err`.
180    ///
181    /// If the body cannot be interpreted as valid json for the target type `T`,
182    /// an `Err` is returned.
183    pub async fn recv_form<T: serde::de::DeserializeOwned>(
184        &self,
185        request: impl Into<Request>,
186    ) -> Result<T> {
187        let mut response = self.send(request.into()).await?;
188        response.body_form::<T>().await
189    }
190
191    /// Perform an HTTP `GET` request using the `Client` connection.
192    ///
193    /// # Panics
194    ///
195    /// This will panic if a malformed URL is passed.
196    ///
197    /// # Errors
198    ///
199    /// Returns errors from the middleware, http backend, and network sockets.
200    pub fn get(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
201        RequestBuilder::new_for_middleware(Method::Get, self.url(uri), self.clone())
202    }
203
204    /// Perform an HTTP `HEAD` request using the `Client` connection.
205    ///
206    /// # Panics
207    ///
208    /// This will panic if a malformed URL is passed.
209    ///
210    /// # Errors
211    ///
212    /// Returns errors from the middleware, http backend, and network sockets.
213    pub fn head(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
214        RequestBuilder::new_for_middleware(Method::Head, self.url(uri), self.clone())
215    }
216
217    /// Perform an HTTP `POST` request using the `Client` connection.
218    ///
219    /// # Panics
220    ///
221    /// This will panic if a malformed URL is passed.
222    ///
223    /// # Errors
224    ///
225    /// Returns errors from the middleware, http backend, and network sockets.
226    pub fn post(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
227        RequestBuilder::new_for_middleware(Method::Post, self.url(uri), self.clone())
228    }
229
230    /// Perform an HTTP `PUT` request using the `Client` connection.
231    ///
232    /// # Panics
233    ///
234    /// This will panic if a malformed URL is passed.
235    ///
236    /// # Errors
237    ///
238    /// Returns errors from the middleware, http backend, and network sockets.
239    pub fn put(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
240        RequestBuilder::new_for_middleware(Method::Put, self.url(uri), self.clone())
241    }
242
243    /// Perform an HTTP `DELETE` request using the `Client` connection.
244    ///
245    /// # Panics
246    ///
247    /// This will panic if a malformed URL is passed.
248    ///
249    /// # Errors
250    ///
251    /// Returns errors from the middleware, http backend, and network sockets.
252    pub fn delete(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
253        RequestBuilder::new_for_middleware(Method::Delete, self.url(uri), self.clone())
254    }
255
256    /// Perform an HTTP `CONNECT` request using the `Client` connection.
257    ///
258    /// # Panics
259    ///
260    /// This will panic if a malformed URL is passed.
261    ///
262    /// # Errors
263    ///
264    /// Returns errors from the middleware, http backend, and network sockets.
265    pub fn connect(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
266        RequestBuilder::new_for_middleware(Method::Connect, self.url(uri), self.clone())
267    }
268
269    /// Perform an HTTP `OPTIONS` request using the `Client` connection.
270    ///
271    /// # Panics
272    ///
273    /// This will panic if a malformed URL is passed.
274    ///
275    /// # Errors
276    ///
277    /// Returns errors from the middleware, http backend, and network sockets.
278    pub fn options(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
279        RequestBuilder::new_for_middleware(Method::Options, self.url(uri), self.clone())
280    }
281
282    /// Perform an HTTP `TRACE` request using the `Client` connection.
283    ///
284    /// # Panics
285    ///
286    /// This will panic if a malformed URL is passed.
287    ///
288    /// # Errors
289    ///
290    /// Returns errors from the middleware, http backend, and network sockets.
291    pub fn trace(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
292        RequestBuilder::new_for_middleware(Method::Trace, self.url(uri), self.clone())
293    }
294
295    /// Perform an HTTP `PATCH` request using the `Client` connection.
296    ///
297    /// # Panics
298    ///
299    /// This will panic if a malformed URL is passed.
300    ///
301    /// # Errors
302    ///
303    /// Returns errors from the middleware, http backend, and network sockets.
304    pub fn patch(&self, uri: impl AsRef<str>) -> RequestBuilder<()> {
305        RequestBuilder::new_for_middleware(Method::Patch, self.url(uri), self.clone())
306    }
307
308    /// Perform a HTTP request with the given verb using the `Client` connection.
309    ///
310    /// # Panics
311    ///
312    /// This will panic if a malformed URL is passed.
313    ///
314    /// # Errors
315    ///
316    /// Returns errors from the middleware, http backend, and network sockets.
317    pub fn request(&self, verb: Method, uri: impl AsRef<str>) -> RequestBuilder<()> {
318        RequestBuilder::new_for_middleware(verb, self.url(uri), self.clone())
319    }
320
321    /// Get the current configuration.
322    #[must_use]
323    pub fn config(&self) -> &Config {
324        &self.config
325    }
326
327    // private function to generate a url based on the base_path
328    fn url(&self, uri: impl AsRef<str>) -> Url {
329        match &self.config.base_url {
330            None => uri.as_ref().parse().unwrap(),
331            Some(base) => base.join(uri.as_ref()).unwrap(),
332        }
333    }
334}
335
336#[cfg(test)]
337mod client_tests {
338    use super::Client;
339    use crate::protocol::{HttpRequest, HttpResponse};
340    use crate::testing::FakeShell;
341
342    #[futures_test::test]
343    async fn an_http_get() {
344        let mut shell = FakeShell::default();
345        shell.provide_response(HttpResponse::ok().body("Hello World!").build());
346
347        let client = Client::new(shell.clone());
348
349        let mut response = client.get("https://example.com").await.unwrap();
350        assert_eq!(response.body_string().await.unwrap(), "Hello World!");
351
352        assert_eq!(
353            shell.take_requests_received(),
354            vec![HttpRequest::get("https://example.com/").build()]
355        );
356    }
357}