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}