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}