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}