li_surf/
request.rs

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