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}