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