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}