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}