1use std::convert::TryFrom;
2use std::fmt;
3use std::future::Future;
4use std::time::Duration;
5
6use serde::Serialize;
7#[cfg(feature = "json")]
8use serde_json;
9
10use super::body::Body;
11use super::client::{Client, Pending};
12#[cfg(feature = "multipart")]
13use super::multipart;
14use super::response::Response;
15#[cfg(feature = "multipart")]
16use crate::header::CONTENT_LENGTH;
17use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
18use crate::{Method, Url};
19use http::{request::Parts, Request as HttpRequest, Version};
20
21pub struct Request {
23 method: Method,
24 url: Url,
25 headers: HeaderMap,
26 body: Option<Body>,
27 timeout: Option<Duration>,
28 version: Version,
29}
30
31#[must_use = "RequestBuilder does nothing until you 'send' it"]
35pub struct RequestBuilder {
36 client: Client,
37 request: crate::Result<Request>,
38}
39
40impl Request {
41 #[inline]
43 pub fn new(method: Method, url: Url) -> Self {
44 Request {
45 method,
46 url,
47 headers: HeaderMap::new(),
48 body: None,
49 timeout: None,
50 version: Version::default(),
51 }
52 }
53
54 #[inline]
56 pub fn method(&self) -> &Method {
57 &self.method
58 }
59
60 #[inline]
62 pub fn method_mut(&mut self) -> &mut Method {
63 &mut self.method
64 }
65
66 #[inline]
68 pub fn url(&self) -> &Url {
69 &self.url
70 }
71
72 #[inline]
74 pub fn url_mut(&mut self) -> &mut Url {
75 &mut self.url
76 }
77
78 #[inline]
80 pub fn headers(&self) -> &HeaderMap {
81 &self.headers
82 }
83
84 #[inline]
86 pub fn headers_mut(&mut self) -> &mut HeaderMap {
87 &mut self.headers
88 }
89
90 #[inline]
92 pub fn body(&self) -> Option<&Body> {
93 self.body.as_ref()
94 }
95
96 #[inline]
98 pub fn body_mut(&mut self) -> &mut Option<Body> {
99 &mut self.body
100 }
101
102 #[inline]
104 pub fn timeout(&self) -> Option<&Duration> {
105 self.timeout.as_ref()
106 }
107
108 #[inline]
110 pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
111 &mut self.timeout
112 }
113
114 #[inline]
116 pub fn version(&self) -> Version {
117 self.version
118 }
119
120 #[inline]
122 pub fn version_mut(&mut self) -> &mut Version {
123 &mut self.version
124 }
125
126 pub fn try_clone(&self) -> Option<Request> {
130 let body = match self.body.as_ref() {
131 Some(body) => Some(body.try_clone()?),
132 None => None,
133 };
134 let mut req = Request::new(self.method().clone(), self.url().clone());
135 *req.timeout_mut() = self.timeout().copied();
136 *req.headers_mut() = self.headers().clone();
137 *req.version_mut() = self.version();
138 req.body = body;
139 Some(req)
140 }
141
142 pub(super) fn pieces(
143 self,
144 ) -> (
145 Method,
146 Url,
147 HeaderMap,
148 Option<Body>,
149 Option<Duration>,
150 Version,
151 ) {
152 (
153 self.method,
154 self.url,
155 self.headers,
156 self.body,
157 self.timeout,
158 self.version,
159 )
160 }
161}
162
163impl RequestBuilder {
164 pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
165 let mut builder = RequestBuilder { client, request };
166
167 let auth = builder
168 .request
169 .as_mut()
170 .ok()
171 .and_then(|req| extract_authority(&mut req.url));
172
173 if let Some((username, password)) = auth {
174 builder.basic_auth(username, password)
175 } else {
176 builder
177 }
178 }
179
180 pub fn from_parts(client: Client, request: Request) -> RequestBuilder {
182 RequestBuilder {
183 client,
184 request: crate::Result::Ok(request),
185 }
186 }
187
188 pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder
190 where
191 HeaderName: TryFrom<K>,
192 <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
193 HeaderValue: TryFrom<V>,
194 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
195 {
196 self.header_sensitive(key, value, false)
197 }
198
199 fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder
201 where
202 HeaderName: TryFrom<K>,
203 <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
204 HeaderValue: TryFrom<V>,
205 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
206 {
207 let mut error = None;
208 if let Ok(ref mut req) = self.request {
209 match <HeaderName as TryFrom<K>>::try_from(key) {
210 Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
211 Ok(mut value) => {
212 if sensitive {
216 value.set_sensitive(true);
217 }
218 req.headers_mut().append(key, value);
219 }
220 Err(e) => error = Some(crate::error::builder(e.into())),
221 },
222 Err(e) => error = Some(crate::error::builder(e.into())),
223 };
224 }
225 if let Some(err) = error {
226 self.request = Err(err);
227 }
228 self
229 }
230
231 pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder {
235 if let Ok(ref mut req) = self.request {
236 crate::util::replace_headers(req.headers_mut(), headers);
237 }
238 self
239 }
240
241 pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
256 where
257 U: fmt::Display,
258 P: fmt::Display,
259 {
260 let header_value = crate::util::basic_auth(username, password);
261 self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
262 }
263
264 pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
266 where
267 T: fmt::Display,
268 {
269 let header_value = format!("Bearer {token}");
270 self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
271 }
272
273 pub fn res_auth<T>(self, user_id: T, token: T) -> RequestBuilder
275 where
276 T: fmt::Display,
277 {
278 let header_value = format!("res {user_id}:{token}");
279 self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
280 }
281
282 pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
284 if let Ok(ref mut req) = self.request {
285 *req.body_mut() = Some(body.into());
286 }
287 self
288 }
289
290 pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
296 if let Ok(ref mut req) = self.request {
297 *req.timeout_mut() = Some(timeout);
298 }
299 self
300 }
301
302 #[cfg(feature = "multipart")]
322 #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
323 pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
324 let mut builder = self.header(
325 CONTENT_TYPE,
326 format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
327 );
328
329 builder = match multipart.compute_length() {
330 Some(length) => builder.header(CONTENT_LENGTH, length),
331 None => builder,
332 };
333
334 if let Ok(ref mut req) = builder.request {
335 *req.body_mut() = Some(multipart.stream())
336 }
337 builder
338 }
339
340 pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
359 let mut error = None;
360 if let Ok(ref mut req) = self.request {
361 let url = req.url_mut();
362 let mut pairs = url.query_pairs_mut();
363 let serializer = serde_urlencoded::Serializer::new(&mut pairs);
364
365 if let Err(err) = query.serialize(serializer) {
366 error = Some(crate::error::builder(err));
367 }
368 }
369 if let Ok(ref mut req) = self.request {
370 if let Some("") = req.url().query() {
371 req.url_mut().set_query(None);
372 }
373 }
374 if let Some(err) = error {
375 self.request = Err(err);
376 }
377 self
378 }
379
380 pub fn version(mut self, version: Version) -> RequestBuilder {
382 if let Ok(ref mut req) = self.request {
383 req.version = version;
384 }
385 self
386 }
387
388 pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
416 let mut error = None;
417 if let Ok(ref mut req) = self.request {
418 match serde_urlencoded::to_string(form) {
419 Ok(body) => {
420 req.headers_mut().insert(
421 CONTENT_TYPE,
422 HeaderValue::from_static("application/x-www-form-urlencoded"),
423 );
424 *req.body_mut() = Some(body.into());
425 }
426 Err(err) => error = Some(crate::error::builder(err)),
427 }
428 }
429 if let Some(err) = error {
430 self.request = Err(err);
431 }
432 self
433 }
434
435 #[cfg(feature = "json")]
446 #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
447 pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
448 let mut error = None;
449 if let Ok(ref mut req) = self.request {
450 match serde_json::to_vec(json) {
451 Ok(body) => {
452 if !req.headers().contains_key(CONTENT_TYPE) {
453 req.headers_mut()
454 .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
455 }
456 *req.body_mut() = Some(body.into());
457 }
458 Err(err) => error = Some(crate::error::builder(err)),
459 }
460 }
461 if let Some(err) = error {
462 self.request = Err(err);
463 }
464 self
465 }
466
467 pub fn fetch_mode_no_cors(self) -> RequestBuilder {
477 self
478 }
479
480 pub fn build(self) -> crate::Result<Request> {
483 self.request
484 }
485
486 pub fn build_split(self) -> (Client, crate::Result<Request>) {
492 (self.client, self.request)
493 }
494
495 pub fn send(self) -> impl Future<Output = Result<Response, crate::Error>> {
517 match self.request {
518 Ok(req) => self.client.execute_request(req),
519 Err(err) => Pending::new_err(err),
520 }
521 }
522
523 pub fn try_clone(&self) -> Option<RequestBuilder> {
543 self.request
544 .as_ref()
545 .ok()
546 .and_then(|req| req.try_clone())
547 .map(|req| RequestBuilder {
548 client: self.client.clone(),
549 request: Ok(req),
550 })
551 }
552}
553
554impl fmt::Debug for Request {
555 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
556 fmt_request_fields(&mut f.debug_struct("Request"), self).finish()
557 }
558}
559
560impl fmt::Debug for RequestBuilder {
561 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
562 let mut builder = f.debug_struct("RequestBuilder");
563 match self.request {
564 Ok(ref req) => fmt_request_fields(&mut builder, req).finish(),
565 Err(ref err) => builder.field("error", err).finish(),
566 }
567 }
568}
569
570fn fmt_request_fields<'a, 'b>(
571 f: &'a mut fmt::DebugStruct<'a, 'b>,
572 req: &Request,
573) -> &'a mut fmt::DebugStruct<'a, 'b> {
574 f.field("method", &req.method)
575 .field("url", &req.url)
576 .field("headers", &req.headers)
577}
578
579pub(crate) fn extract_authority(url: &mut Url) -> Option<(String, Option<String>)> {
582 use percent_encoding::percent_decode;
583
584 if url.has_authority() {
585 let username: String = percent_decode(url.username().as_bytes())
586 .decode_utf8()
587 .ok()?
588 .into();
589 let password = url.password().and_then(|pass| {
590 percent_decode(pass.as_bytes())
591 .decode_utf8()
592 .ok()
593 .map(String::from)
594 });
595 if !username.is_empty() || password.is_some() {
596 url.set_username("")
597 .expect("has_authority means set_username shouldn't fail");
598 url.set_password(None)
599 .expect("has_authority means set_password shouldn't fail");
600 return Some((username, password));
601 }
602 }
603
604 None
605}
606
607impl<T> TryFrom<HttpRequest<T>> for Request
608where
609 T: Into<Body>,
610{
611 type Error = crate::Error;
612
613 fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
614 let (parts, body) = req.into_parts();
615 let Parts {
616 method,
617 uri,
618 headers,
619 version,
620 ..
621 } = parts;
622 let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
623 Ok(Request {
624 method,
625 url,
626 headers,
627 body: Some(body.into()),
628 timeout: None,
629 version,
630 })
631 }
632}
633
634impl TryFrom<Request> for HttpRequest<Body> {
635 type Error = crate::Error;
636
637 fn try_from(req: Request) -> crate::Result<Self> {
638 let Request {
639 method,
640 url,
641 headers,
642 body,
643 version,
644 ..
645 } = req;
646
647 let mut req = HttpRequest::builder()
648 .version(version)
649 .method(method)
650 .uri(url.as_str())
651 .body(body.unwrap_or_else(Body::empty))
652 .map_err(crate::error::builder)?;
653
654 *req.headers_mut() = headers;
655 Ok(req)
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 #![cfg(not(feature = "rustls-tls-manual-roots-no-provider"))]
662
663 use super::{Client, HttpRequest, Request, RequestBuilder, Version};
664 use crate::Method;
665 use serde::Serialize;
666 use std::collections::BTreeMap;
667 use std::convert::TryFrom;
668
669 #[test]
670 fn add_query_append() {
671 let client = Client::new();
672 let some_url = "https://google.com/";
673 let r = client.get(some_url);
674
675 let r = r.query(&[("foo", "bar")]);
676 let r = r.query(&[("qux", 3)]);
677
678 let req = r.build().expect("request is valid");
679 assert_eq!(req.url().query(), Some("foo=bar&qux=3"));
680 }
681
682 #[test]
683 fn add_query_append_same() {
684 let client = Client::new();
685 let some_url = "https://google.com/";
686 let r = client.get(some_url);
687
688 let r = r.query(&[("foo", "a"), ("foo", "b")]);
689
690 let req = r.build().expect("request is valid");
691 assert_eq!(req.url().query(), Some("foo=a&foo=b"));
692 }
693
694 #[test]
695 fn add_query_struct() {
696 #[derive(Serialize)]
697 struct Params {
698 foo: String,
699 qux: i32,
700 }
701
702 let client = Client::new();
703 let some_url = "https://google.com/";
704 let r = client.get(some_url);
705
706 let params = Params {
707 foo: "bar".into(),
708 qux: 3,
709 };
710
711 let r = r.query(¶ms);
712
713 let req = r.build().expect("request is valid");
714 assert_eq!(req.url().query(), Some("foo=bar&qux=3"));
715 }
716
717 #[test]
718 fn add_query_map() {
719 let mut params = BTreeMap::new();
720 params.insert("foo", "bar");
721 params.insert("qux", "three");
722
723 let client = Client::new();
724 let some_url = "https://google.com/";
725 let r = client.get(some_url);
726
727 let r = r.query(¶ms);
728
729 let req = r.build().expect("request is valid");
730 assert_eq!(req.url().query(), Some("foo=bar&qux=three"));
731 }
732
733 #[test]
734 fn test_replace_headers() {
735 use http::HeaderMap;
736
737 let mut headers = HeaderMap::new();
738 headers.insert("foo", "bar".parse().unwrap());
739 headers.append("foo", "baz".parse().unwrap());
740
741 let client = Client::new();
742 let req = client
743 .get("https://hyper.rs")
744 .header("im-a", "keeper")
745 .header("foo", "pop me")
746 .headers(headers)
747 .build()
748 .expect("request build");
749
750 assert_eq!(req.headers()["im-a"], "keeper");
751
752 let foo = req.headers().get_all("foo").iter().collect::<Vec<_>>();
753 assert_eq!(foo.len(), 2);
754 assert_eq!(foo[0], "bar");
755 assert_eq!(foo[1], "baz");
756 }
757
758 #[test]
759 fn normalize_empty_query() {
760 let client = Client::new();
761 let some_url = "https://google.com/";
762 let empty_query: &[(&str, &str)] = &[];
763
764 let req = client
765 .get(some_url)
766 .query(empty_query)
767 .build()
768 .expect("request build");
769
770 assert_eq!(req.url().query(), None);
771 assert_eq!(req.url().as_str(), "https://google.com/");
772 }
773
774 #[test]
775 fn try_clone_reusable() {
776 let client = Client::new();
777 let builder = client
778 .post("http://httpbin.org/post")
779 .header("foo", "bar")
780 .body("from a &str!");
781 let req = builder
782 .try_clone()
783 .expect("clone successful")
784 .build()
785 .expect("request is valid");
786 assert_eq!(req.url().as_str(), "http://httpbin.org/post");
787 assert_eq!(req.method(), Method::POST);
788 assert_eq!(req.headers()["foo"], "bar");
789 }
790
791 #[test]
792 fn try_clone_no_body() {
793 let client = Client::new();
794 let builder = client.get("http://httpbin.org/get");
795 let req = builder
796 .try_clone()
797 .expect("clone successful")
798 .build()
799 .expect("request is valid");
800 assert_eq!(req.url().as_str(), "http://httpbin.org/get");
801 assert_eq!(req.method(), Method::GET);
802 assert!(req.body().is_none());
803 }
804
805 #[test]
806 #[cfg(feature = "stream")]
807 fn try_clone_stream() {
808 let chunks: Vec<Result<_, ::std::io::Error>> = vec![Ok("hello"), Ok(" "), Ok("world")];
809 let stream = futures_util::stream::iter(chunks);
810 let client = Client::new();
811 let builder = client
812 .get("http://httpbin.org/get")
813 .body(super::Body::wrap_stream(stream));
814 let clone = builder.try_clone();
815 assert!(clone.is_none());
816 }
817
818 #[test]
819 fn convert_url_authority_into_basic_auth() {
820 let client = Client::new();
821 let some_url = "https://Aladdin:open sesame@localhost/";
822
823 let req = client.get(some_url).build().expect("request build");
824
825 assert_eq!(req.url().as_str(), "https://localhost/");
826 assert_eq!(
827 req.headers()["authorization"],
828 "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
829 );
830 }
831
832 #[test]
833 fn test_basic_auth_sensitive_header() {
834 let client = Client::new();
835 let some_url = "https://localhost/";
836
837 let req = client
838 .get(some_url)
839 .basic_auth("Aladdin", Some("open sesame"))
840 .build()
841 .expect("request build");
842
843 assert_eq!(req.url().as_str(), "https://localhost/");
844 assert_eq!(
845 req.headers()["authorization"],
846 "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
847 );
848 assert!(req.headers()["authorization"].is_sensitive());
849 }
850
851 #[test]
852 fn test_bearer_auth_sensitive_header() {
853 let client = Client::new();
854 let some_url = "https://localhost/";
855
856 let req = client
857 .get(some_url)
858 .bearer_auth("Hold my bear")
859 .build()
860 .expect("request build");
861
862 assert_eq!(req.url().as_str(), "https://localhost/");
863 assert_eq!(req.headers()["authorization"], "Bearer Hold my bear");
864 assert!(req.headers()["authorization"].is_sensitive());
865 }
866
867 #[test]
868 fn test_explicit_sensitive_header() {
869 let client = Client::new();
870 let some_url = "https://localhost/";
871
872 let mut header = http::HeaderValue::from_static("in plain sight");
873 header.set_sensitive(true);
874
875 let req = client
876 .get(some_url)
877 .header("hiding", header)
878 .build()
879 .expect("request build");
880
881 assert_eq!(req.url().as_str(), "https://localhost/");
882 assert_eq!(req.headers()["hiding"], "in plain sight");
883 assert!(req.headers()["hiding"].is_sensitive());
884 }
885
886 #[test]
887 fn convert_from_http_request() {
888 let http_request = HttpRequest::builder()
889 .method("GET")
890 .uri("http://localhost/")
891 .header("User-Agent", "my-awesome-agent/1.0")
892 .body("test test test")
893 .unwrap();
894 let req: Request = Request::try_from(http_request).unwrap();
895 assert!(req.body().is_some());
896 let test_data = b"test test test";
897 assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..]));
898 let headers = req.headers();
899 assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
900 assert_eq!(req.method(), Method::GET);
901 assert_eq!(req.url().as_str(), "http://localhost/");
902 }
903
904 #[test]
905 fn set_http_request_version() {
906 let http_request = HttpRequest::builder()
907 .method("GET")
908 .uri("http://localhost/")
909 .header("User-Agent", "my-awesome-agent/1.0")
910 .version(Version::HTTP_11)
911 .body("test test test")
912 .unwrap();
913 let req: Request = Request::try_from(http_request).unwrap();
914 assert!(req.body().is_some());
915 let test_data = b"test test test";
916 assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..]));
917 let headers = req.headers();
918 assert_eq!(headers.get("User-Agent").unwrap(), "my-awesome-agent/1.0");
919 assert_eq!(req.method(), Method::GET);
920 assert_eq!(req.url().as_str(), "http://localhost/");
921 assert_eq!(req.version(), Version::HTTP_11);
922 }
923
924 #[test]
925 fn builder_split_reassemble() {
926 let builder = {
927 let client = Client::new();
928 client.get("http://example.com")
929 };
930 let (client, inner) = builder.build_split();
931 let request = inner.unwrap();
932 let builder = RequestBuilder::from_parts(client, request);
933 builder.build().unwrap();
934 }
935
936 }