Skip to main content

crux_http/
request.rs

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