crux_http/request.rs
1use crate::middleware::Middleware;
2use http_types::{
3 headers::{self, HeaderName, HeaderValues, ToHeaderValues},
4 Body, Method, Mime, Url,
5};
6
7use serde::Serialize;
8
9use std::fmt;
10use std::ops::Index;
11use std::sync::Arc;
12
13/// An HTTP request, returns a `Response`.
14#[derive(Clone)]
15pub struct Request {
16 /// Holds the state of the request.
17 req: http_types::Request,
18 /// Holds an optional per-request middleware stack.
19 middleware: Option<Vec<Arc<dyn Middleware>>>,
20}
21
22impl Request {
23 /// Create a new instance.
24 ///
25 /// This method is particularly useful when input URLs might be passed by third parties, and
26 /// you don't want to panic if they're malformed. If URLs are statically encoded, it might be
27 /// easier to use one of the shorthand methods instead.
28 ///
29 /// # Examples
30 ///
31 /// ```
32 /// fn main() -> crux_http::Result<()> {
33 /// use crux_http::http::{Url, Method};
34 ///
35 /// let url = Url::parse("https://httpbin.org/get")?;
36 /// let req = crux_http::Request::new(Method::Get, url);
37 /// # Ok(()) }
38 /// ```
39 #[must_use]
40 pub fn new(method: Method, url: Url) -> Self {
41 let req = http_types::Request::new(method, url);
42 Self {
43 req,
44 middleware: None,
45 }
46 }
47
48 /// Get the URL querystring.
49 ///
50 /// # Examples
51 ///
52 /// ```
53 /// # use serde::{Deserialize, Serialize};
54 /// # enum Event {}
55 /// # struct Capabilities { http: crux_http::Http<Event> }
56 /// # fn update(caps: &Capabilities) -> crux_http::Result<()> {
57 /// #[derive(Serialize, Deserialize)]
58 /// struct Index {
59 /// page: u32
60 /// }
61 ///
62 /// let req = caps.http.get("https://httpbin.org/get?page=2").build();
63 /// let Index { page } = req.query()?;
64 /// assert_eq!(page, 2);
65 /// # Ok(()) }
66 /// ```
67 ///
68 /// # Errors
69 /// Returns an error if the query string could not be deserialized.
70 pub fn query<T: serde::de::DeserializeOwned>(&self) -> crate::Result<T> {
71 Ok(self.req.query()?)
72 }
73
74 /// Set the URL querystring.
75 ///
76 /// # Examples
77 ///
78 /// ```
79 /// # use serde::{Deserialize, Serialize};
80 /// # enum Event {}
81 /// # struct Capabilities { http: crux_http::Http<Event> }
82 /// # fn update(caps: &Capabilities) -> crux_http::Result<()> {
83 /// #[derive(Serialize, Deserialize)]
84 /// struct Index {
85 /// page: u32
86 /// }
87 ///
88 /// let query = Index { page: 2 };
89 /// let mut req = caps.http.get("https://httpbin.org/get").build();
90 /// req.set_query(&query)?;
91 /// assert_eq!(req.url().query(), Some("page=2"));
92 /// assert_eq!(req.url().as_str(), "https://httpbin.org/get?page=2");
93 /// # Ok(()) }
94 /// ```
95 ///
96 /// # Errors
97 /// Returns an error if the query string could not be serialized.
98 pub fn set_query(&mut self, query: &impl Serialize) -> crate::Result<()> {
99 Ok(self.req.set_query(query)?)
100 }
101
102 /// Get an HTTP header.
103 ///
104 /// # Examples
105 ///
106 /// ```
107 /// # enum Event {}
108 /// # struct Capabilities { http: crux_http::Http<Event> }
109 /// # fn update(caps: &Capabilities) -> crux_http::Result<()> {
110 /// let mut req = caps.http.get("https://httpbin.org/get").build();
111 /// req.set_header("X-Requested-With", "surf");
112 /// assert_eq!(req.header("X-Requested-With").unwrap(), "surf");
113 /// # Ok(()) }
114 /// ```
115 pub fn header(&self, key: impl Into<HeaderName>) -> Option<&HeaderValues> {
116 self.req.header(key)
117 }
118
119 /// Get a mutable reference to a header.
120 pub fn header_mut(&mut self, name: impl Into<HeaderName>) -> Option<&mut HeaderValues> {
121 self.req.header_mut(name)
122 }
123
124 /// Set an HTTP header.
125 pub fn insert_header(
126 &mut self,
127 name: impl Into<HeaderName>,
128 values: impl ToHeaderValues,
129 ) -> Option<HeaderValues> {
130 self.req.insert_header(name, values)
131 }
132
133 /// Append a header to the headers.
134 ///
135 /// Unlike `insert` this function will not override the contents of a header, but insert a
136 /// header if there aren't any. Or else append to the existing list of headers.
137 pub fn append_header(&mut self, name: impl Into<HeaderName>, values: impl ToHeaderValues) {
138 self.req.append_header(name, values);
139 }
140
141 /// Remove a header.
142 pub fn remove_header(&mut self, name: impl Into<HeaderName>) -> Option<HeaderValues> {
143 self.req.remove_header(name)
144 }
145
146 /// An iterator visiting all header pairs in arbitrary order.
147 #[must_use]
148 pub fn iter(&self) -> headers::Iter<'_> {
149 self.req.iter()
150 }
151
152 /// An iterator visiting all header pairs in arbitrary order, with mutable references to the
153 /// values.
154 #[must_use]
155 pub fn iter_mut(&mut self) -> headers::IterMut<'_> {
156 self.req.iter_mut()
157 }
158
159 /// An iterator visiting all header names in arbitrary order.
160 #[must_use]
161 pub fn header_names(&self) -> headers::Names<'_> {
162 self.req.header_names()
163 }
164
165 /// An iterator visiting all header values in arbitrary order.
166 #[must_use]
167 pub fn header_values(&self) -> headers::Values<'_> {
168 self.req.header_values()
169 }
170
171 /// Set an HTTP header.
172 ///
173 /// # Examples
174 ///
175 /// ```
176 /// # enum Event {}
177 /// # struct Capabilities { http: crux_http::Http<Event> }
178 /// # fn update(caps: &Capabilities) -> crux_http::Result<()> {
179 /// let mut req = caps.http.get("https://httpbin.org/get").build();
180 /// req.set_header("X-Requested-With", "surf");
181 /// assert_eq!(req.header("X-Requested-With").unwrap(), "surf");
182 /// # Ok(()) }
183 /// ```
184 pub fn set_header(&mut self, key: impl Into<HeaderName>, value: impl ToHeaderValues) {
185 self.insert_header(key, value);
186 }
187
188 /// Get a request extension value.
189 #[must_use]
190 pub fn ext<T: Send + Sync + 'static>(&self) -> Option<&T> {
191 self.req.ext().get()
192 }
193
194 /// Set a request extension value.
195 pub fn set_ext<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
196 self.req.ext_mut().insert(val)
197 }
198
199 /// Get the request HTTP method.
200 ///
201 /// # Examples
202 ///
203 /// ```
204 /// # enum Event {}
205 /// # struct Capabilities { http: crux_http::Http<Event> }
206 /// # fn update(caps: &Capabilities) -> crux_http::Result<()> {
207 /// let req = caps.http.get("https://httpbin.org/get").build();
208 /// assert_eq!(req.method(), crux_http::http::Method::Get);
209 /// # Ok(()) }
210 /// ```
211 #[must_use]
212 pub fn method(&self) -> Method {
213 self.req.method()
214 }
215
216 /// Get the request url.
217 ///
218 /// # Examples
219 ///
220 /// ```
221 /// # enum Event {}
222 /// # struct Capabilities { http: crux_http::Http<Event> }
223 /// # fn update(caps: &Capabilities) -> crux_http::Result<()> {
224 /// use crux_http::http::Url;
225 /// let req = caps.http.get("https://httpbin.org/get").build();
226 /// assert_eq!(req.url(), &Url::parse("https://httpbin.org/get")?);
227 /// # Ok(()) }
228 /// ```
229 #[must_use]
230 pub fn url(&self) -> &Url {
231 self.req.url()
232 }
233
234 /// Get the request content type as a `Mime`.
235 ///
236 /// Gets the `Content-Type` header and parses it to a `Mime` type.
237 ///
238 /// [Read more on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
239 ///
240 /// # Panics
241 ///
242 /// This method will panic if an invalid MIME type was set as a header. Use the [`set_header`]
243 /// method to bypass any checks.
244 ///
245 /// [`set_header`]: #method.set_header
246 #[must_use]
247 pub fn content_type(&self) -> Option<Mime> {
248 self.req.content_type()
249 }
250
251 /// Set the request content type from a `Mime`.
252 ///
253 /// [Read more on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
254 pub fn set_content_type(&mut self, mime: Mime) {
255 self.req.set_content_type(mime);
256 }
257
258 /// Get the length of the body stream, if it has been set.
259 ///
260 /// This value is set when passing a fixed-size object into as the body.
261 /// E.g. a string, or a buffer. Consumers of this API should check this
262 /// value to decide whether to use `Chunked` encoding, or set the
263 /// response length.
264 #[allow(clippy::len_without_is_empty)]
265 #[must_use]
266 pub fn len(&self) -> Option<usize> {
267 self.req.len()
268 }
269
270 /// Returns `true` if the set length of the body stream is zero, `false`
271 /// otherwise.
272 #[must_use]
273 pub fn is_empty(&self) -> Option<bool> {
274 self.req.is_empty()
275 }
276
277 /// Pass an `AsyncRead` stream as the request body.
278 ///
279 /// # Mime
280 ///
281 /// The encoding is set to `application/octet-stream`.
282 pub fn set_body(&mut self, body: impl Into<Body>) {
283 self.req.set_body(body);
284 }
285
286 /// Take the request body as a `Body`.
287 ///
288 /// This method can be called after the body has already been taken or read,
289 /// but will return an empty `Body`.
290 ///
291 /// This is useful for consuming the body via an `AsyncReader` or `AsyncBufReader`.
292 pub fn take_body(&mut self) -> Body {
293 self.req.take_body()
294 }
295
296 /// Pass JSON as the request body.
297 ///
298 /// # Mime
299 ///
300 /// The `content-type` is set to `application/json`.
301 ///
302 /// # Errors
303 ///
304 /// This method will return an error if the provided data could not be serialized to JSON.
305 pub fn body_json(&mut self, json: &impl Serialize) -> crate::Result<()> {
306 self.set_body(Body::from_json(json)?);
307 Ok(())
308 }
309
310 /// Pass a string as the request body.
311 ///
312 /// # Mime
313 ///
314 /// The `content-type` is set to `text/plain; charset=utf-8`.
315 pub fn body_string(&mut self, string: String) {
316 self.set_body(Body::from_string(string));
317 }
318
319 /// Pass bytes as the request body.
320 ///
321 /// # Mime
322 ///
323 /// The `content-type` is set to `application/octet-stream`.
324 pub fn body_bytes(&mut self, bytes: impl AsRef<[u8]>) {
325 self.set_body(Body::from(bytes.as_ref()));
326 }
327
328 /// Pass a form as the request body.
329 ///
330 /// # Mime
331 ///
332 /// The `content-type` is set to `application/x-www-form-urlencoded`.
333 ///
334 /// # Errors
335 ///
336 /// An error will be returned if the encoding failed.
337 pub fn body_form(&mut self, form: &impl Serialize) -> crate::Result<()> {
338 self.set_body(Body::from_form(form)?);
339 Ok(())
340 }
341
342 /// Push middleware onto a per-request middleware stack.
343 ///
344 /// **Important**: Setting per-request middleware incurs extra allocations.
345 /// Creating a `Client` with middleware is recommended.
346 ///
347 /// Client middleware is run before per-request middleware.
348 ///
349 /// See the [middleware] submodule for more information on middleware.
350 ///
351 /// [middleware]: ../middleware/index.html
352 ///
353 /// # Examples
354 ///
355 /// ```
356 /// # enum Event {}
357 /// # struct Capabilities { http: crux_http::Http<Event> }
358 /// # fn update(caps: &Capabilities) -> crux_http::Result<()> {
359 /// let mut req = caps.http.get("https://httpbin.org/get").build();
360 /// req.middleware(crux_http::middleware::Redirect::default());
361 /// # Ok(()) }
362 /// ```
363 #[allow(clippy::missing_panics_doc)]
364 pub fn middleware(&mut self, middleware: impl Middleware) {
365 if self.middleware.is_none() {
366 self.middleware = Some(vec![]);
367 }
368
369 self.middleware.as_mut().unwrap().push(Arc::new(middleware));
370 }
371
372 pub(crate) fn take_middleware(&mut self) -> Option<Vec<Arc<dyn Middleware>>> {
373 self.middleware.take()
374 }
375}
376
377impl AsRef<http_types::Headers> for Request {
378 fn as_ref(&self) -> &http_types::Headers {
379 self.req.as_ref()
380 }
381}
382
383impl AsMut<http_types::Headers> for Request {
384 fn as_mut(&mut self) -> &mut http_types::Headers {
385 self.req.as_mut()
386 }
387}
388
389impl AsRef<http_types::Request> for Request {
390 fn as_ref(&self) -> &http_types::Request {
391 &self.req
392 }
393}
394
395impl AsMut<http_types::Request> for Request {
396 fn as_mut(&mut self) -> &mut http_types::Request {
397 &mut self.req
398 }
399}
400
401impl From<http_types::Request> for Request {
402 /// Converts an `http_types::Request` to a `crux_http::Request`.
403 fn from(req: http_types::Request) -> Self {
404 Self {
405 req,
406 middleware: None,
407 }
408 }
409}
410
411#[cfg(feature = "http-compat")]
412impl<B: Into<Body>> TryFrom<http::Request<B>> for Request {
413 type Error = anyhow::Error;
414
415 fn try_from(req: http::Request<B>) -> Result<Self, Self::Error> {
416 use std::str::FromStr;
417 let mut o = Request::new(
418 Method::from_str(req.method().as_str()).map_err(|e| anyhow::anyhow!(e))?,
419 req.uri().to_string().parse()?,
420 );
421
422 for (k, v) in req.headers() {
423 o.append_header(k.as_str(), v.to_str()?);
424 }
425
426 o.set_body(req.into_body());
427 Ok(o)
428 }
429}
430
431#[allow(clippy::from_over_into)]
432impl Into<http_types::Request> for Request {
433 /// Converts a `crux_http::Request` to an `http_types::Request`.
434 fn into(self) -> http_types::Request {
435 self.req
436 }
437}
438
439impl fmt::Debug for Request {
440 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441 fmt::Debug::fmt(&self.req, f)
442 }
443}
444
445impl IntoIterator for Request {
446 type Item = (HeaderName, HeaderValues);
447 type IntoIter = headers::IntoIter;
448
449 /// Returns an iterator of references over the remaining items.
450 #[inline]
451 fn into_iter(self) -> Self::IntoIter {
452 self.req.into_iter()
453 }
454}
455
456impl<'a> IntoIterator for &'a Request {
457 type Item = (&'a HeaderName, &'a HeaderValues);
458 type IntoIter = headers::Iter<'a>;
459
460 #[inline]
461 fn into_iter(self) -> Self::IntoIter {
462 self.req.iter()
463 }
464}
465
466impl<'a> IntoIterator for &'a mut Request {
467 type Item = (&'a HeaderName, &'a mut HeaderValues);
468 type IntoIter = headers::IterMut<'a>;
469
470 #[inline]
471 fn into_iter(self) -> Self::IntoIter {
472 self.req.iter_mut()
473 }
474}
475
476impl Index<HeaderName> for Request {
477 type Output = HeaderValues;
478
479 /// Returns a reference to the value corresponding to the supplied name.
480 ///
481 /// # Panics
482 ///
483 /// Panics if the name is not present in `Request`.
484 #[inline]
485 fn index(&self, name: HeaderName) -> &HeaderValues {
486 &self.req[name]
487 }
488}
489
490impl Index<&str> for Request {
491 type Output = HeaderValues;
492
493 /// Returns a reference to the value corresponding to the supplied name.
494 ///
495 /// # Panics
496 ///
497 /// Panics if the name is not present in `Request`.
498 #[inline]
499 fn index(&self, name: &str) -> &HeaderValues {
500 &self.req[name]
501 }
502}