1 | use std::convert::TryFrom; |
2 | use std::fmt; |
3 | use std::time::Duration; |
4 | |
5 | use http::{request::Parts, Request as HttpRequest, Version}; |
6 | use serde::Serialize; |
7 | #[cfg (feature = "json" )] |
8 | use serde_json; |
9 | use serde_urlencoded; |
10 | |
11 | use super::body::{self, Body}; |
12 | #[cfg (feature = "multipart" )] |
13 | use super::multipart; |
14 | use super::Client; |
15 | use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}; |
16 | use crate::{async_impl, Method, Url}; |
17 | |
18 | /// A request which can be executed with `Client::execute()`. |
19 | pub struct Request { |
20 | body: Option<Body>, |
21 | inner: async_impl::Request, |
22 | } |
23 | |
24 | /// A builder to construct the properties of a `Request`. |
25 | /// |
26 | /// To construct a `RequestBuilder`, refer to the `Client` documentation. |
27 | #[derive (Debug)] |
28 | #[must_use = "RequestBuilder does nothing until you 'send' it" ] |
29 | pub struct RequestBuilder { |
30 | client: Client, |
31 | request: crate::Result<Request>, |
32 | } |
33 | |
34 | impl Request { |
35 | /// Constructs a new request. |
36 | #[inline ] |
37 | pub fn new(method: Method, url: Url) -> Self { |
38 | Request { |
39 | body: None, |
40 | inner: async_impl::Request::new(method, url), |
41 | } |
42 | } |
43 | |
44 | /// Get the method. |
45 | #[inline ] |
46 | pub fn method(&self) -> &Method { |
47 | self.inner.method() |
48 | } |
49 | |
50 | /// Get a mutable reference to the method. |
51 | #[inline ] |
52 | pub fn method_mut(&mut self) -> &mut Method { |
53 | self.inner.method_mut() |
54 | } |
55 | |
56 | /// Get the url. |
57 | #[inline ] |
58 | pub fn url(&self) -> &Url { |
59 | self.inner.url() |
60 | } |
61 | |
62 | /// Get a mutable reference to the url. |
63 | #[inline ] |
64 | pub fn url_mut(&mut self) -> &mut Url { |
65 | self.inner.url_mut() |
66 | } |
67 | |
68 | /// Get the headers. |
69 | #[inline ] |
70 | pub fn headers(&self) -> &HeaderMap { |
71 | self.inner.headers() |
72 | } |
73 | |
74 | /// Get a mutable reference to the headers. |
75 | #[inline ] |
76 | pub fn headers_mut(&mut self) -> &mut HeaderMap { |
77 | self.inner.headers_mut() |
78 | } |
79 | |
80 | /// Get the http version. |
81 | #[inline ] |
82 | pub fn version(&self) -> Version { |
83 | self.inner.version() |
84 | } |
85 | |
86 | /// Get a mutable reference to the http version. |
87 | #[inline ] |
88 | pub fn version_mut(&mut self) -> &mut Version { |
89 | self.inner.version_mut() |
90 | } |
91 | |
92 | /// Get the body. |
93 | #[inline ] |
94 | pub fn body(&self) -> Option<&Body> { |
95 | self.body.as_ref() |
96 | } |
97 | |
98 | /// Get a mutable reference to the body. |
99 | #[inline ] |
100 | pub fn body_mut(&mut self) -> &mut Option<Body> { |
101 | &mut self.body |
102 | } |
103 | |
104 | /// Get the timeout. |
105 | #[inline ] |
106 | pub fn timeout(&self) -> Option<&Duration> { |
107 | self.inner.timeout() |
108 | } |
109 | |
110 | /// Get a mutable reference to the timeout. |
111 | #[inline ] |
112 | pub fn timeout_mut(&mut self) -> &mut Option<Duration> { |
113 | self.inner.timeout_mut() |
114 | } |
115 | |
116 | /// Attempts to clone the `Request`. |
117 | /// |
118 | /// None is returned if a body is which can not be cloned. This can be because the body is a |
119 | /// stream. |
120 | pub fn try_clone(&self) -> Option<Request> { |
121 | let body = if let Some(ref body) = self.body.as_ref() { |
122 | if let Some(body) = body.try_clone() { |
123 | Some(body) |
124 | } else { |
125 | return None; |
126 | } |
127 | } else { |
128 | None |
129 | }; |
130 | let mut req = Request::new(self.method().clone(), self.url().clone()); |
131 | *req.headers_mut() = self.headers().clone(); |
132 | *req.version_mut() = self.version().clone(); |
133 | req.body = body; |
134 | Some(req) |
135 | } |
136 | |
137 | pub(crate) fn into_async(self) -> (async_impl::Request, Option<body::Sender>) { |
138 | use crate::header::CONTENT_LENGTH; |
139 | |
140 | let mut req_async = self.inner; |
141 | let body = self.body.and_then(|body| { |
142 | let (tx, body, len) = body.into_async(); |
143 | if let Some(len) = len { |
144 | req_async.headers_mut().insert(CONTENT_LENGTH, len.into()); |
145 | } |
146 | *req_async.body_mut() = Some(body); |
147 | tx |
148 | }); |
149 | (req_async, body) |
150 | } |
151 | } |
152 | |
153 | impl RequestBuilder { |
154 | pub(crate) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder { |
155 | let mut builder = RequestBuilder { client, request }; |
156 | |
157 | let auth = builder |
158 | .request |
159 | .as_mut() |
160 | .ok() |
161 | .and_then(|req| async_impl::request::extract_authority(req.url_mut())); |
162 | |
163 | if let Some((username, password)) = auth { |
164 | builder.basic_auth(username, password) |
165 | } else { |
166 | builder |
167 | } |
168 | } |
169 | |
170 | /// Add a `Header` to this Request. |
171 | /// |
172 | /// ```rust |
173 | /// use reqwest::header::USER_AGENT; |
174 | /// |
175 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
176 | /// let client = reqwest::blocking::Client::new(); |
177 | /// let res = client.get("https://www.rust-lang.org" ) |
178 | /// .header(USER_AGENT, "foo" ) |
179 | /// .send()?; |
180 | /// # Ok(()) |
181 | /// # } |
182 | /// ``` |
183 | pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder |
184 | where |
185 | HeaderName: TryFrom<K>, |
186 | HeaderValue: TryFrom<V>, |
187 | <HeaderName as TryFrom<K>>::Error: Into<http::Error>, |
188 | <HeaderValue as TryFrom<V>>::Error: Into<http::Error>, |
189 | { |
190 | self.header_sensitive(key, value, false) |
191 | } |
192 | |
193 | /// Add a `Header` to this Request with ability to define if header_value is sensitive. |
194 | fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder |
195 | where |
196 | HeaderName: TryFrom<K>, |
197 | HeaderValue: TryFrom<V>, |
198 | <HeaderName as TryFrom<K>>::Error: Into<http::Error>, |
199 | <HeaderValue as TryFrom<V>>::Error: Into<http::Error>, |
200 | { |
201 | let mut error = None; |
202 | if let Ok(ref mut req) = self.request { |
203 | match <HeaderName as TryFrom<K>>::try_from(key) { |
204 | Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) { |
205 | Ok(mut value) => { |
206 | value.set_sensitive(sensitive); |
207 | req.headers_mut().append(key, value); |
208 | } |
209 | Err(e) => error = Some(crate::error::builder(e.into())), |
210 | }, |
211 | Err(e) => error = Some(crate::error::builder(e.into())), |
212 | }; |
213 | } |
214 | if let Some(err) = error { |
215 | self.request = Err(err); |
216 | } |
217 | self |
218 | } |
219 | |
220 | /// Add a set of Headers to the existing ones on this Request. |
221 | /// |
222 | /// The headers will be merged in to any already set. |
223 | /// |
224 | /// ```rust |
225 | /// use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT, CONTENT_TYPE}; |
226 | /// # use std::fs; |
227 | /// |
228 | /// fn construct_headers() -> HeaderMap { |
229 | /// let mut headers = HeaderMap::new(); |
230 | /// headers.insert(USER_AGENT, HeaderValue::from_static("reqwest" )); |
231 | /// headers.insert(CONTENT_TYPE, HeaderValue::from_static("image/png" )); |
232 | /// headers |
233 | /// } |
234 | /// |
235 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
236 | /// let file = fs::File::open("much_beauty.png" )?; |
237 | /// let client = reqwest::blocking::Client::new(); |
238 | /// let res = client.post("http://httpbin.org/post" ) |
239 | /// .headers(construct_headers()) |
240 | /// .body(file) |
241 | /// .send()?; |
242 | /// # Ok(()) |
243 | /// # } |
244 | /// ``` |
245 | pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder { |
246 | if let Ok(ref mut req) = self.request { |
247 | crate::util::replace_headers(req.headers_mut(), headers); |
248 | } |
249 | self |
250 | } |
251 | |
252 | /// Enable HTTP basic authentication. |
253 | /// |
254 | /// ```rust |
255 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
256 | /// let client = reqwest::blocking::Client::new(); |
257 | /// let resp = client.delete("http://httpbin.org/delete" ) |
258 | /// .basic_auth("admin" , Some("good password" )) |
259 | /// .send()?; |
260 | /// # Ok(()) |
261 | /// # } |
262 | /// ``` |
263 | pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder |
264 | where |
265 | U: fmt::Display, |
266 | P: fmt::Display, |
267 | { |
268 | let header_value = crate::util::basic_auth(username, password); |
269 | self.header_sensitive(crate::header::AUTHORIZATION, header_value, true) |
270 | } |
271 | |
272 | /// Enable HTTP bearer authentication. |
273 | /// |
274 | /// ```rust |
275 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
276 | /// let client = reqwest::blocking::Client::new(); |
277 | /// let resp = client.delete("http://httpbin.org/delete" ) |
278 | /// .bearer_auth("token" ) |
279 | /// .send()?; |
280 | /// # Ok(()) |
281 | /// # } |
282 | /// ``` |
283 | pub fn bearer_auth<T>(self, token: T) -> RequestBuilder |
284 | where |
285 | T: fmt::Display, |
286 | { |
287 | let header_value = format!("Bearer {}" , token); |
288 | self.header_sensitive(crate::header::AUTHORIZATION, &*header_value, true) |
289 | } |
290 | |
291 | /// Set the request body. |
292 | /// |
293 | /// # Examples |
294 | /// |
295 | /// Using a string: |
296 | /// |
297 | /// ```rust |
298 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
299 | /// let client = reqwest::blocking::Client::new(); |
300 | /// let res = client.post("http://httpbin.org/post" ) |
301 | /// .body("from a &str!" ) |
302 | /// .send()?; |
303 | /// # Ok(()) |
304 | /// # } |
305 | /// ``` |
306 | /// |
307 | /// Using a `File`: |
308 | /// |
309 | /// ```rust |
310 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
311 | /// let file = std::fs::File::open("from_a_file.txt" )?; |
312 | /// let client = reqwest::blocking::Client::new(); |
313 | /// let res = client.post("http://httpbin.org/post" ) |
314 | /// .body(file) |
315 | /// .send()?; |
316 | /// # Ok(()) |
317 | /// # } |
318 | /// ``` |
319 | /// |
320 | /// Using arbitrary bytes: |
321 | /// |
322 | /// ```rust |
323 | /// # use std::fs; |
324 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
325 | /// // from bytes! |
326 | /// let bytes: Vec<u8> = vec![1, 10, 100]; |
327 | /// let client = reqwest::blocking::Client::new(); |
328 | /// let res = client.post("http://httpbin.org/post" ) |
329 | /// .body(bytes) |
330 | /// .send()?; |
331 | /// # Ok(()) |
332 | /// # } |
333 | /// ``` |
334 | pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder { |
335 | if let Ok(ref mut req) = self.request { |
336 | *req.body_mut() = Some(body.into()); |
337 | } |
338 | self |
339 | } |
340 | |
341 | /// Enables a request timeout. |
342 | /// |
343 | /// The timeout is applied from when the request starts connecting until the |
344 | /// response body has finished. It affects only this request and overrides |
345 | /// the timeout configured using `ClientBuilder::timeout()`. |
346 | pub fn timeout(mut self, timeout: Duration) -> RequestBuilder { |
347 | if let Ok(ref mut req) = self.request { |
348 | *req.timeout_mut() = Some(timeout); |
349 | } |
350 | self |
351 | } |
352 | |
353 | /// Modify the query string of the URL. |
354 | /// |
355 | /// Modifies the URL of this request, adding the parameters provided. |
356 | /// This method appends and does not overwrite. This means that it can |
357 | /// be called multiple times and that existing query parameters are not |
358 | /// overwritten if the same key is used. The key will simply show up |
359 | /// twice in the query string. |
360 | /// Calling `.query(&[("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`. |
361 | /// |
362 | /// ```rust |
363 | /// # use reqwest::Error; |
364 | /// # |
365 | /// # fn run() -> Result<(), Error> { |
366 | /// let client = reqwest::blocking::Client::new(); |
367 | /// let res = client.get("http://httpbin.org" ) |
368 | /// .query(&[("lang" , "rust" )]) |
369 | /// .send()?; |
370 | /// # Ok(()) |
371 | /// # } |
372 | /// ``` |
373 | /// |
374 | /// # Note |
375 | /// This method does not support serializing a single key-value |
376 | /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such |
377 | /// as `.query(&[("key", "val")])`. It's also possible to serialize structs |
378 | /// and maps into a key-value pair. |
379 | /// |
380 | /// # Errors |
381 | /// This method will fail if the object you provide cannot be serialized |
382 | /// into a query string. |
383 | pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder { |
384 | let mut error = None; |
385 | if let Ok(ref mut req) = self.request { |
386 | let url = req.url_mut(); |
387 | let mut pairs = url.query_pairs_mut(); |
388 | let serializer = serde_urlencoded::Serializer::new(&mut pairs); |
389 | |
390 | if let Err(err) = query.serialize(serializer) { |
391 | error = Some(crate::error::builder(err)); |
392 | } |
393 | } |
394 | if let Ok(ref mut req) = self.request { |
395 | if let Some("" ) = req.url().query() { |
396 | req.url_mut().set_query(None); |
397 | } |
398 | } |
399 | if let Some(err) = error { |
400 | self.request = Err(err); |
401 | } |
402 | self |
403 | } |
404 | |
405 | /// Set HTTP version |
406 | pub fn version(mut self, version: Version) -> RequestBuilder { |
407 | if let Ok(ref mut req) = self.request { |
408 | *req.version_mut() = version; |
409 | } |
410 | self |
411 | } |
412 | |
413 | /// Send a form body. |
414 | /// |
415 | /// Sets the body to the url encoded serialization of the passed value, |
416 | /// and also sets the `Content-Type: application/x-www-form-urlencoded` |
417 | /// header. |
418 | /// |
419 | /// ```rust |
420 | /// # use reqwest::Error; |
421 | /// # use std::collections::HashMap; |
422 | /// # |
423 | /// # fn run() -> Result<(), Error> { |
424 | /// let mut params = HashMap::new(); |
425 | /// params.insert("lang" , "rust" ); |
426 | /// |
427 | /// let client = reqwest::blocking::Client::new(); |
428 | /// let res = client.post("http://httpbin.org" ) |
429 | /// .form(¶ms) |
430 | /// .send()?; |
431 | /// # Ok(()) |
432 | /// # } |
433 | /// ``` |
434 | /// |
435 | /// # Errors |
436 | /// |
437 | /// This method fails if the passed value cannot be serialized into |
438 | /// url encoded format |
439 | pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder { |
440 | let mut error = None; |
441 | if let Ok(ref mut req) = self.request { |
442 | match serde_urlencoded::to_string(form) { |
443 | Ok(body) => { |
444 | req.headers_mut().insert( |
445 | CONTENT_TYPE, |
446 | HeaderValue::from_static("application/x-www-form-urlencoded" ), |
447 | ); |
448 | *req.body_mut() = Some(body.into()); |
449 | } |
450 | Err(err) => error = Some(crate::error::builder(err)), |
451 | } |
452 | } |
453 | if let Some(err) = error { |
454 | self.request = Err(err); |
455 | } |
456 | self |
457 | } |
458 | |
459 | /// Send a JSON body. |
460 | /// |
461 | /// Sets the body to the JSON serialization of the passed value, and |
462 | /// also sets the `Content-Type: application/json` header. |
463 | /// |
464 | /// # Optional |
465 | /// |
466 | /// This requires the optional `json` feature enabled. |
467 | /// |
468 | /// # Examples |
469 | /// |
470 | /// ```rust |
471 | /// # use reqwest::Error; |
472 | /// # use std::collections::HashMap; |
473 | /// # |
474 | /// # fn run() -> Result<(), Error> { |
475 | /// let mut map = HashMap::new(); |
476 | /// map.insert("lang", "rust"); |
477 | /// |
478 | /// let client = reqwest::blocking::Client::new(); |
479 | /// let res = client.post("http://httpbin.org") |
480 | /// .json(&map) |
481 | /// .send()?; |
482 | /// # Ok(()) |
483 | /// # } |
484 | /// ``` |
485 | /// |
486 | /// # Errors |
487 | /// |
488 | /// Serialization can fail if `T`'s implementation of `Serialize` decides to |
489 | /// fail, or if `T` contains a map with non-string keys. |
490 | #[cfg (feature = "json" )] |
491 | #[cfg_attr (docsrs, doc(cfg(feature = "json" )))] |
492 | pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder { |
493 | let mut error = None; |
494 | if let Ok(ref mut req) = self.request { |
495 | match serde_json::to_vec(json) { |
496 | Ok(body) => { |
497 | if !req.headers().contains_key(CONTENT_TYPE) { |
498 | req.headers_mut() |
499 | .insert(CONTENT_TYPE, HeaderValue::from_static("application/json" )); |
500 | } |
501 | *req.body_mut() = Some(body.into()); |
502 | } |
503 | Err(err) => error = Some(crate::error::builder(err)), |
504 | } |
505 | } |
506 | if let Some(err) = error { |
507 | self.request = Err(err); |
508 | } |
509 | self |
510 | } |
511 | |
512 | /// Sends a multipart/form-data body. |
513 | /// |
514 | /// ``` |
515 | /// # use reqwest::Error; |
516 | /// |
517 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
518 | /// let client = reqwest::blocking::Client::new(); |
519 | /// let form = reqwest::blocking::multipart::Form::new() |
520 | /// .text("key3", "value3") |
521 | /// .file("file", "/path/to/field")?; |
522 | /// |
523 | /// let response = client.post("your url") |
524 | /// .multipart(form) |
525 | /// .send()?; |
526 | /// # Ok(()) |
527 | /// # } |
528 | /// ``` |
529 | /// |
530 | /// See [`multipart`](multipart/) for more examples. |
531 | #[cfg (feature = "multipart" )] |
532 | #[cfg_attr (docsrs, doc(cfg(feature = "multipart" )))] |
533 | pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder { |
534 | let mut builder = self.header( |
535 | CONTENT_TYPE, |
536 | format!("multipart/form-data; boundary= {}" , multipart.boundary()).as_str(), |
537 | ); |
538 | if let Ok(ref mut req) = builder.request { |
539 | *req.body_mut() = Some(match multipart.compute_length() { |
540 | Some(length) => Body::sized(multipart.reader(), length), |
541 | None => Body::new(multipart.reader()), |
542 | }) |
543 | } |
544 | builder |
545 | } |
546 | |
547 | /// Build a `Request`, which can be inspected, modified and executed with |
548 | /// `Client::execute()`. |
549 | pub fn build(self) -> crate::Result<Request> { |
550 | self.request |
551 | } |
552 | |
553 | /// Constructs the Request and sends it the target URL, returning a Response. |
554 | /// |
555 | /// # Errors |
556 | /// |
557 | /// This method fails if there was an error while sending request, |
558 | /// redirect loop was detected or redirect limit was exhausted. |
559 | pub fn send(self) -> crate::Result<super::Response> { |
560 | self.client.execute(self.request?) |
561 | } |
562 | |
563 | /// Attempts to clone the `RequestBuilder`. |
564 | /// |
565 | /// None is returned if a body is which can not be cloned. This can be because the body is a |
566 | /// stream. |
567 | /// |
568 | /// # Examples |
569 | /// |
570 | /// With a static body |
571 | /// |
572 | /// ```rust |
573 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
574 | /// let client = reqwest::blocking::Client::new(); |
575 | /// let builder = client.post("http://httpbin.org/post" ) |
576 | /// .body("from a &str!" ); |
577 | /// let clone = builder.try_clone(); |
578 | /// assert!(clone.is_some()); |
579 | /// # Ok(()) |
580 | /// # } |
581 | /// ``` |
582 | /// |
583 | /// Without a body |
584 | /// |
585 | /// ```rust |
586 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
587 | /// let client = reqwest::blocking::Client::new(); |
588 | /// let builder = client.get("http://httpbin.org/get" ); |
589 | /// let clone = builder.try_clone(); |
590 | /// assert!(clone.is_some()); |
591 | /// # Ok(()) |
592 | /// # } |
593 | /// ``` |
594 | /// |
595 | /// With a non-clonable body |
596 | /// |
597 | /// ```rust |
598 | /// # fn run() -> Result<(), Box<std::error::Error>> { |
599 | /// let client = reqwest::blocking::Client::new(); |
600 | /// let builder = client.get("http://httpbin.org/get" ) |
601 | /// .body(reqwest::blocking::Body::new(std::io::empty())); |
602 | /// let clone = builder.try_clone(); |
603 | /// assert!(clone.is_none()); |
604 | /// # Ok(()) |
605 | /// # } |
606 | /// ``` |
607 | pub fn try_clone(&self) -> Option<RequestBuilder> { |
608 | self.request |
609 | .as_ref() |
610 | .ok() |
611 | .and_then(|req| req.try_clone()) |
612 | .map(|req| RequestBuilder { |
613 | client: self.client.clone(), |
614 | request: Ok(req), |
615 | }) |
616 | } |
617 | } |
618 | |
619 | impl<T> TryFrom<HttpRequest<T>> for Request |
620 | where |
621 | T: Into<Body>, |
622 | { |
623 | type Error = crate::Error; |
624 | |
625 | fn try_from(req: HttpRequest<T>) -> crate::Result<Self> { |
626 | let (parts: Parts, body: T) = req.into_parts(); |
627 | let Parts { |
628 | method: Method, |
629 | uri: Uri, |
630 | headers: HeaderMap, |
631 | .. |
632 | } = parts; |
633 | let url: Url = Url::parse(&uri.to_string()).map_err(op:crate::error::builder)?; |
634 | let mut inner: Request = async_impl::Request::new(method, url); |
635 | crate::util::replace_headers(dst:inner.headers_mut(), src:headers); |
636 | Ok(Request { |
637 | body: Some(body.into()), |
638 | inner, |
639 | }) |
640 | } |
641 | } |
642 | |
643 | impl fmt::Debug for Request { |
644 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
645 | fmt_request_fields(&mut f.debug_struct("Request" ), self).finish() |
646 | } |
647 | } |
648 | |
649 | fn fmt_request_fields<'a, 'b>( |
650 | f: &'a mut fmt::DebugStruct<'a, 'b>, |
651 | req: &Request, |
652 | ) -> &'a mut fmt::DebugStruct<'a, 'b> { |
653 | f.field("method" , req.method()) |
654 | .field("url" , req.url()) |
655 | .field(name:"headers" , value:req.headers()) |
656 | } |
657 | |
658 | #[cfg (test)] |
659 | mod tests { |
660 | use super::super::{body, Client}; |
661 | use super::{HttpRequest, Request, Version}; |
662 | use crate::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, HOST}; |
663 | use crate::Method; |
664 | use serde::Serialize; |
665 | #[cfg (feature = "json" )] |
666 | use serde_json; |
667 | use serde_urlencoded; |
668 | use std::collections::{BTreeMap, HashMap}; |
669 | use std::convert::TryFrom; |
670 | |
671 | #[test ] |
672 | fn basic_get_request() { |
673 | let client = Client::new(); |
674 | let some_url = "https://google.com/" ; |
675 | let r = client.get(some_url).build().unwrap(); |
676 | |
677 | assert_eq!(r.method(), &Method::GET); |
678 | assert_eq!(r.url().as_str(), some_url); |
679 | } |
680 | |
681 | #[test ] |
682 | fn basic_head_request() { |
683 | let client = Client::new(); |
684 | let some_url = "https://google.com/" ; |
685 | let r = client.head(some_url).build().unwrap(); |
686 | |
687 | assert_eq!(r.method(), &Method::HEAD); |
688 | assert_eq!(r.url().as_str(), some_url); |
689 | } |
690 | |
691 | #[test ] |
692 | fn basic_post_request() { |
693 | let client = Client::new(); |
694 | let some_url = "https://google.com/" ; |
695 | let r = client.post(some_url).build().unwrap(); |
696 | |
697 | assert_eq!(r.method(), &Method::POST); |
698 | assert_eq!(r.url().as_str(), some_url); |
699 | } |
700 | |
701 | #[test ] |
702 | fn basic_put_request() { |
703 | let client = Client::new(); |
704 | let some_url = "https://google.com/" ; |
705 | let r = client.put(some_url).build().unwrap(); |
706 | |
707 | assert_eq!(r.method(), &Method::PUT); |
708 | assert_eq!(r.url().as_str(), some_url); |
709 | } |
710 | |
711 | #[test ] |
712 | fn basic_patch_request() { |
713 | let client = Client::new(); |
714 | let some_url = "https://google.com/" ; |
715 | let r = client.patch(some_url).build().unwrap(); |
716 | |
717 | assert_eq!(r.method(), &Method::PATCH); |
718 | assert_eq!(r.url().as_str(), some_url); |
719 | } |
720 | |
721 | #[test ] |
722 | fn basic_delete_request() { |
723 | let client = Client::new(); |
724 | let some_url = "https://google.com/" ; |
725 | let r = client.delete(some_url).build().unwrap(); |
726 | |
727 | assert_eq!(r.method(), &Method::DELETE); |
728 | assert_eq!(r.url().as_str(), some_url); |
729 | } |
730 | |
731 | #[test ] |
732 | fn add_header() { |
733 | let client = Client::new(); |
734 | let some_url = "https://google.com/" ; |
735 | let r = client.post(some_url); |
736 | |
737 | let header = HeaderValue::from_static("google.com" ); |
738 | |
739 | // Add a copy of the header to the request builder |
740 | let r = r.header(HOST, header.clone()).build().unwrap(); |
741 | |
742 | // then check it was actually added |
743 | assert_eq!(r.headers().get(HOST), Some(&header)); |
744 | } |
745 | |
746 | #[test ] |
747 | fn add_headers() { |
748 | let client = Client::new(); |
749 | let some_url = "https://google.com/" ; |
750 | let r = client.post(some_url); |
751 | |
752 | let header = HeaderValue::from_static("google.com" ); |
753 | |
754 | let mut headers = HeaderMap::new(); |
755 | headers.insert(HOST, header); |
756 | |
757 | // Add a copy of the headers to the request builder |
758 | let r = r.headers(headers.clone()).build().unwrap(); |
759 | |
760 | // then make sure they were added correctly |
761 | assert_eq!(r.headers(), &headers); |
762 | } |
763 | |
764 | #[test ] |
765 | fn add_headers_multi() { |
766 | let client = Client::new(); |
767 | let some_url = "https://google.com/" ; |
768 | let r = client.post(some_url); |
769 | |
770 | let header_json = HeaderValue::from_static("application/json" ); |
771 | let header_xml = HeaderValue::from_static("application/xml" ); |
772 | |
773 | let mut headers = HeaderMap::new(); |
774 | headers.append(ACCEPT, header_json); |
775 | headers.append(ACCEPT, header_xml); |
776 | |
777 | // Add a copy of the headers to the request builder |
778 | let r = r.headers(headers.clone()).build().unwrap(); |
779 | |
780 | // then make sure they were added correctly |
781 | assert_eq!(r.headers(), &headers); |
782 | let mut all_values = r.headers().get_all(ACCEPT).iter(); |
783 | assert_eq!(all_values.next().unwrap(), &"application/json" ); |
784 | assert_eq!(all_values.next().unwrap(), &"application/xml" ); |
785 | assert_eq!(all_values.next(), None); |
786 | } |
787 | |
788 | #[test ] |
789 | fn add_body() { |
790 | let client = Client::new(); |
791 | let some_url = "https://google.com/" ; |
792 | let r = client.post(some_url); |
793 | |
794 | let body = "Some interesting content" ; |
795 | |
796 | let mut r = r.body(body).build().unwrap(); |
797 | |
798 | let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap(); |
799 | |
800 | assert_eq!(buf, body); |
801 | } |
802 | |
803 | #[test ] |
804 | fn add_query_append() { |
805 | let client = Client::new(); |
806 | let some_url = "https://google.com/" ; |
807 | let mut r = client.get(some_url); |
808 | |
809 | r = r.query(&[("foo" , "bar" )]); |
810 | r = r.query(&[("qux" , 3)]); |
811 | |
812 | let req = r.build().expect("request is valid" ); |
813 | assert_eq!(req.url().query(), Some("foo=bar&qux=3" )); |
814 | } |
815 | |
816 | #[test ] |
817 | fn add_query_append_same() { |
818 | let client = Client::new(); |
819 | let some_url = "https://google.com/" ; |
820 | let mut r = client.get(some_url); |
821 | |
822 | r = r.query(&[("foo" , "a" ), ("foo" , "b" )]); |
823 | |
824 | let req = r.build().expect("request is valid" ); |
825 | assert_eq!(req.url().query(), Some("foo=a&foo=b" )); |
826 | } |
827 | |
828 | #[test ] |
829 | fn add_query_struct() { |
830 | #[derive (Serialize)] |
831 | struct Params { |
832 | foo: String, |
833 | qux: i32, |
834 | } |
835 | |
836 | let client = Client::new(); |
837 | let some_url = "https://google.com/" ; |
838 | let mut r = client.get(some_url); |
839 | |
840 | let params = Params { |
841 | foo: "bar" .into(), |
842 | qux: 3, |
843 | }; |
844 | |
845 | r = r.query(¶ms); |
846 | |
847 | let req = r.build().expect("request is valid" ); |
848 | assert_eq!(req.url().query(), Some("foo=bar&qux=3" )); |
849 | } |
850 | |
851 | #[test ] |
852 | fn add_query_map() { |
853 | let mut params = BTreeMap::new(); |
854 | params.insert("foo" , "bar" ); |
855 | params.insert("qux" , "three" ); |
856 | |
857 | let client = Client::new(); |
858 | let some_url = "https://google.com/" ; |
859 | let mut r = client.get(some_url); |
860 | |
861 | r = r.query(¶ms); |
862 | |
863 | let req = r.build().expect("request is valid" ); |
864 | assert_eq!(req.url().query(), Some("foo=bar&qux=three" )); |
865 | } |
866 | |
867 | #[test ] |
868 | fn add_form() { |
869 | let client = Client::new(); |
870 | let some_url = "https://google.com/" ; |
871 | let r = client.post(some_url); |
872 | |
873 | let mut form_data = HashMap::new(); |
874 | form_data.insert("foo" , "bar" ); |
875 | |
876 | let mut r = r.form(&form_data).build().unwrap(); |
877 | |
878 | // Make sure the content type was set |
879 | assert_eq!( |
880 | r.headers().get(CONTENT_TYPE).unwrap(), |
881 | &"application/x-www-form-urlencoded" |
882 | ); |
883 | |
884 | let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap(); |
885 | |
886 | let body_should_be = serde_urlencoded::to_string(&form_data).unwrap(); |
887 | assert_eq!(buf, body_should_be); |
888 | } |
889 | |
890 | #[test ] |
891 | #[cfg (feature = "json" )] |
892 | fn add_json() { |
893 | let client = Client::new(); |
894 | let some_url = "https://google.com/" ; |
895 | let r = client.post(some_url); |
896 | |
897 | let mut json_data = HashMap::new(); |
898 | json_data.insert("foo" , "bar" ); |
899 | |
900 | let mut r = r.json(&json_data).build().unwrap(); |
901 | |
902 | // Make sure the content type was set |
903 | assert_eq!(r.headers().get(CONTENT_TYPE).unwrap(), &"application/json" ); |
904 | |
905 | let buf = body::read_to_string(r.body_mut().take().unwrap()).unwrap(); |
906 | |
907 | let body_should_be = serde_json::to_string(&json_data).unwrap(); |
908 | assert_eq!(buf, body_should_be); |
909 | } |
910 | |
911 | #[test ] |
912 | #[cfg (feature = "json" )] |
913 | fn add_json_fail() { |
914 | use serde::ser::Error as _; |
915 | use serde::{Serialize, Serializer}; |
916 | use std::error::Error as _; |
917 | struct MyStruct; |
918 | impl Serialize for MyStruct { |
919 | fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error> |
920 | where |
921 | S: Serializer, |
922 | { |
923 | Err(S::Error::custom("nope" )) |
924 | } |
925 | } |
926 | |
927 | let client = Client::new(); |
928 | let some_url = "https://google.com/" ; |
929 | let r = client.post(some_url); |
930 | let json_data = MyStruct; |
931 | let err = r.json(&json_data).build().unwrap_err(); |
932 | assert!(err.is_builder()); // well, duh ;) |
933 | assert!(err.source().unwrap().is::<serde_json::Error>()); |
934 | } |
935 | |
936 | #[test ] |
937 | fn test_replace_headers() { |
938 | use http::HeaderMap; |
939 | |
940 | let mut headers = HeaderMap::new(); |
941 | headers.insert("foo" , "bar" .parse().unwrap()); |
942 | headers.append("foo" , "baz" .parse().unwrap()); |
943 | |
944 | let client = Client::new(); |
945 | let req = client |
946 | .get("https://hyper.rs" ) |
947 | .header("im-a" , "keeper" ) |
948 | .header("foo" , "pop me" ) |
949 | .headers(headers) |
950 | .build() |
951 | .expect("request build" ); |
952 | |
953 | assert_eq!(req.headers()["im-a" ], "keeper" ); |
954 | |
955 | let foo = req.headers().get_all("foo" ).iter().collect::<Vec<_>>(); |
956 | assert_eq!(foo.len(), 2); |
957 | assert_eq!(foo[0], "bar" ); |
958 | assert_eq!(foo[1], "baz" ); |
959 | } |
960 | |
961 | #[test ] |
962 | fn normalize_empty_query() { |
963 | let client = Client::new(); |
964 | let some_url = "https://google.com/" ; |
965 | let empty_query: &[(&str, &str)] = &[]; |
966 | |
967 | let req = client |
968 | .get(some_url) |
969 | .query(empty_query) |
970 | .build() |
971 | .expect("request build" ); |
972 | |
973 | assert_eq!(req.url().query(), None); |
974 | assert_eq!(req.url().as_str(), "https://google.com/" ); |
975 | } |
976 | |
977 | #[test ] |
978 | fn convert_url_authority_into_basic_auth() { |
979 | let client = Client::new(); |
980 | let some_url = "https://Aladdin:open sesame@localhost/" ; |
981 | |
982 | let req = client.get(some_url).build().expect("request build" ); |
983 | |
984 | assert_eq!(req.url().as_str(), "https://localhost/" ); |
985 | assert_eq!( |
986 | req.headers()["authorization" ], |
987 | "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" |
988 | ); |
989 | } |
990 | |
991 | #[test ] |
992 | fn convert_from_http_request() { |
993 | let http_request = HttpRequest::builder() |
994 | .method("GET" ) |
995 | .uri("http://localhost/" ) |
996 | .header("User-Agent" , "my-awesome-agent/1.0" ) |
997 | .body("test test test" ) |
998 | .unwrap(); |
999 | let req: Request = Request::try_from(http_request).unwrap(); |
1000 | assert_eq!(req.body().is_none(), false); |
1001 | let test_data = b"test test test" ; |
1002 | assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..])); |
1003 | let headers = req.headers(); |
1004 | assert_eq!(headers.get("User-Agent" ).unwrap(), "my-awesome-agent/1.0" ); |
1005 | assert_eq!(req.method(), Method::GET); |
1006 | assert_eq!(req.url().as_str(), "http://localhost/" ); |
1007 | } |
1008 | |
1009 | #[test ] |
1010 | fn set_http_request_version() { |
1011 | let http_request = HttpRequest::builder() |
1012 | .method("GET" ) |
1013 | .uri("http://localhost/" ) |
1014 | .header("User-Agent" , "my-awesome-agent/1.0" ) |
1015 | .version(Version::HTTP_11) |
1016 | .body("test test test" ) |
1017 | .unwrap(); |
1018 | let req: Request = Request::try_from(http_request).unwrap(); |
1019 | assert_eq!(req.body().is_none(), false); |
1020 | let test_data = b"test test test" ; |
1021 | assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..])); |
1022 | let headers = req.headers(); |
1023 | assert_eq!(headers.get("User-Agent" ).unwrap(), "my-awesome-agent/1.0" ); |
1024 | assert_eq!(req.method(), Method::GET); |
1025 | assert_eq!(req.url().as_str(), "http://localhost/" ); |
1026 | assert_eq!(req.version(), Version::HTTP_11); |
1027 | } |
1028 | |
1029 | #[test ] |
1030 | fn test_basic_auth_sensitive_header() { |
1031 | let client = Client::new(); |
1032 | let some_url = "https://localhost/" ; |
1033 | |
1034 | let req = client |
1035 | .get(some_url) |
1036 | .basic_auth("Aladdin" , Some("open sesame" )) |
1037 | .build() |
1038 | .expect("request build" ); |
1039 | |
1040 | assert_eq!(req.url().as_str(), "https://localhost/" ); |
1041 | assert_eq!( |
1042 | req.headers()["authorization" ], |
1043 | "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" |
1044 | ); |
1045 | assert_eq!(req.headers()["authorization" ].is_sensitive(), true); |
1046 | } |
1047 | |
1048 | #[test ] |
1049 | fn test_bearer_auth_sensitive_header() { |
1050 | let client = Client::new(); |
1051 | let some_url = "https://localhost/" ; |
1052 | |
1053 | let req = client |
1054 | .get(some_url) |
1055 | .bearer_auth("Hold my bear" ) |
1056 | .build() |
1057 | .expect("request build" ); |
1058 | |
1059 | assert_eq!(req.url().as_str(), "https://localhost/" ); |
1060 | assert_eq!(req.headers()["authorization" ], "Bearer Hold my bear" ); |
1061 | assert_eq!(req.headers()["authorization" ].is_sensitive(), true); |
1062 | } |
1063 | } |
1064 | |