ureq/request.rs
1use std::convert::TryFrom;
2use std::fmt;
3use std::marker::PhantomData;
4
5use http::{Extensions, HeaderMap, HeaderName, HeaderValue};
6use http::{Method, Request, Response, Uri, Version};
7
8use crate::body::Body;
9use crate::config::typestate::RequestScope;
10use crate::config::{Config, ConfigBuilder, RequestLevelConfig};
11use crate::http;
12use crate::query::url_enc;
13use crate::query::{parse_query_params, QueryParam};
14use crate::send_body::AsSendBody;
15use crate::util::private::Private;
16use crate::util::HeaderMapExt;
17use crate::util::UriExt;
18use crate::{Agent, Error, SendBody};
19
20/// Transparent wrapper around [`http::request::Builder`].
21///
22/// The purpose is to provide the [`.call()`][RequestBuilder::call] and [`.send()`][RequestBuilder::send]
23/// and additional helpers for query parameters like [`.query()`][RequestBuilder::query] functions to
24/// make an API for sending requests.
25pub struct RequestBuilder<B> {
26 agent: Agent,
27 builder: http::request::Builder,
28 query_extra: Vec<QueryParam<'static>>,
29
30 // This is only used in case http::request::Builder contains an error
31 // (such as URL parsing error), and the user wants a `.config()`.
32 dummy_config: Option<Box<Config>>,
33
34 _ph: PhantomData<B>,
35}
36
37/// Typestate when [`RequestBuilder`] has no send body.
38///
39/// `RequestBuilder<WithoutBody>`
40///
41/// Methods: GET, DELETE, HEAD, OPTIONS, CONNECT, TRACE
42#[derive(Debug)]
43pub struct WithoutBody(());
44impl Private for WithoutBody {}
45
46/// Typestate when [`RequestBuilder`] needs to a send body.
47///
48/// `RequestBuilder<WithBody>`
49///
50/// Methods: POST, PUT, PATCH
51#[derive(Debug)]
52pub struct WithBody(());
53impl Private for WithBody {}
54
55impl<Any> RequestBuilder<Any> {
56 /// Get the HTTP Method for this request.
57 ///
58 /// By default this is `GET`. If builder has error, returns None.
59 ///
60 /// # Examples
61 ///
62 /// ```
63 /// use ureq::http::Method;
64 ///
65 /// let req = ureq::get("http://httpbin.org/get");
66 /// assert_eq!(req.method_ref(),Some(&Method::GET));
67 ///
68 /// let req = ureq::post("http://httpbin.org/post");
69 /// assert_eq!(req.method_ref(),Some(&Method::POST));
70 /// ```
71 pub fn method_ref(&self) -> Option<&Method> {
72 self.builder.method_ref()
73 }
74
75 /// Appends a header to this request builder.
76 ///
77 /// This function will append the provided key/value as a header to the
78 /// set of headers. It does not replace headers.
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// let req = ureq::get("https://httpbin.org/get")
84 /// .header("X-Custom-Foo", "bar");
85 /// ```
86 pub fn header<K, V>(mut self, key: K, value: V) -> Self
87 where
88 HeaderName: TryFrom<K>,
89 <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
90 HeaderValue: TryFrom<V>,
91 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
92 {
93 self.builder = self.builder.header(key, value);
94 self
95 }
96
97 /// Get header on this request builder.
98 ///
99 /// When builder has error returns `None`.
100 ///
101 /// # Example
102 ///
103 /// ```
104 /// let req = ureq::get("http://httpbin.org/get")
105 /// .header("Accept", "text/html")
106 /// .header("X-Custom-Foo", "bar");
107 /// let headers = req.headers_ref().unwrap();
108 /// assert_eq!( headers["Accept"], "text/html" );
109 /// assert_eq!( headers["X-Custom-Foo"], "bar" );
110 /// ```
111 pub fn headers_ref(&self) -> Option<&HeaderMap<HeaderValue>> {
112 self.builder.headers_ref()
113 }
114
115 /// Get headers on this request builder.
116 ///
117 /// When builder has error returns `None`.
118 ///
119 /// # Example
120 ///
121 /// ```
122 /// # use ureq::http::header::HeaderValue;
123 /// let mut req = ureq::get("http://httpbin.org/get");
124 /// {
125 /// let headers = req.headers_mut().unwrap();
126 /// headers.insert("Accept", HeaderValue::from_static("text/html"));
127 /// headers.insert("X-Custom-Foo", HeaderValue::from_static("bar"));
128 /// }
129 /// let headers = req.headers_ref().unwrap();
130 /// assert_eq!( headers["Accept"], "text/html" );
131 /// assert_eq!( headers["X-Custom-Foo"], "bar" );
132 /// ```
133 pub fn headers_mut(&mut self) -> Option<&mut HeaderMap<HeaderValue>> {
134 self.builder.headers_mut()
135 }
136
137 /// Add a query parameter to the URL.
138 ///
139 /// Always appends a new parameter, also when using the name of
140 /// an already existing one.
141 ///
142 /// # Examples
143 ///
144 /// ```
145 /// let req = ureq::get("https://httpbin.org/get")
146 /// .query("my_query", "with_value");
147 /// ```
148 pub fn query<K, V>(mut self, key: K, value: V) -> Self
149 where
150 K: AsRef<str>,
151 V: AsRef<str>,
152 {
153 self.query_extra
154 .push(QueryParam::new_key_value(key.as_ref(), value.as_ref()));
155 self
156 }
157
158 /// Set multi query parameters.
159 ///
160 /// For example, to set `?format=json&dest=/login`
161 ///
162 /// ```
163 /// let query = vec![
164 /// ("format", "json"),
165 /// ("dest", "/login"),
166 /// ];
167 ///
168 /// let response = ureq::get("http://httpbin.org/get")
169 /// .query_pairs(query)
170 /// .call()?;
171 /// # Ok::<_, ureq::Error>(())
172 /// ```
173 pub fn query_pairs<I, K, V>(mut self, iter: I) -> Self
174 where
175 I: IntoIterator<Item = (K, V)>,
176 K: AsRef<str>,
177 V: AsRef<str>,
178 {
179 self.query_extra.extend(
180 iter.into_iter()
181 .map(|(k, v)| QueryParam::new_key_value(k.as_ref(), v.as_ref())),
182 );
183 self
184 }
185
186 /// Overrides the URI for this request.
187 ///
188 /// Typically this is set via `ureq::get(<uri>)` or `Agent::get(<uri>)`. This
189 /// lets us change it.
190 ///
191 /// # Examples
192 ///
193 /// ```
194 /// let req = ureq::get("https://www.google.com/")
195 /// .uri("https://httpbin.org/get");
196 /// ```
197 pub fn uri<T>(mut self, uri: T) -> Self
198 where
199 Uri: TryFrom<T>,
200 <Uri as TryFrom<T>>::Error: Into<http::Error>,
201 {
202 self.builder = self.builder.uri(uri);
203 self
204 }
205
206 /// Get the URI for this request
207 ///
208 /// By default this is `/`.
209 ///
210 /// # Examples
211 ///
212 /// ```
213 /// let req = ureq::get("http://httpbin.org/get");
214 /// assert_eq!(req.uri_ref().unwrap(), "http://httpbin.org/get");
215 /// ```
216 pub fn uri_ref(&self) -> Option<&Uri> {
217 self.builder.uri_ref()
218 }
219
220 /// Set the HTTP version for this request.
221 ///
222 /// By default this is HTTP/1.1.
223 /// ureq only handles HTTP/1.1 and HTTP/1.0.
224 ///
225 /// # Examples
226 ///
227 /// ```
228 /// use ureq::http::Version;
229 ///
230 /// let req = ureq::get("https://www.google.com/")
231 /// .version(Version::HTTP_10);
232 /// ```
233 pub fn version(mut self, version: Version) -> Self {
234 self.builder = self.builder.version(version);
235 self
236 }
237
238 /// Get the HTTP version for this request
239 ///
240 /// By default this is HTTP/1.1.
241 ///
242 /// # Examples
243 ///
244 /// ```
245 /// use ureq::http::Version;
246 ///
247 /// let req = ureq::get("http://httpbin.org/get");
248 /// assert_eq!(req.version_ref().unwrap(), &Version::HTTP_11);
249 /// ```
250 pub fn version_ref(&self) -> Option<&Version> {
251 self.builder.version_ref()
252 }
253
254 /// Override agent level config on the request level.
255 ///
256 /// The agent config is copied and modified on request level.
257 ///
258 /// # Example
259 ///
260 /// ```
261 /// use ureq::Agent;
262 ///
263 /// let agent: Agent = Agent::config_builder()
264 /// .https_only(false)
265 /// .build()
266 /// .into();
267 ///
268 /// let request = agent.get("http://httpbin.org/get")
269 /// .config()
270 /// // override agent default for this request
271 /// .https_only(true)
272 /// .build();
273 ///
274 /// // Make the request
275 /// let result = request.call();
276 ///
277 /// // The https_only was set on request level
278 /// assert!(matches!(result.unwrap_err(), ureq::Error::RequireHttpsOnly(_)));
279 /// # Ok::<_, ureq::Error>(())
280 /// ```
281 pub fn config(self) -> ConfigBuilder<RequestScope<Any>> {
282 ConfigBuilder(RequestScope(self))
283 }
284
285 /// Adds an extension to this builder
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// let req = ureq::get("http://httpbin.org/get")
291 /// .extension("My Extension");
292 ///
293 /// assert_eq!(req.extensions_ref().unwrap().get::<&'static str>(),
294 /// Some(&"My Extension"));
295 /// ```
296 pub fn extension<T>(mut self, extension: T) -> Self
297 where
298 T: Clone + std::any::Any + Send + Sync + 'static,
299 {
300 self.builder = self.builder.extension(extension);
301 self
302 }
303
304 /// Get a reference to the extensions for this request builder.
305 ///
306 /// If the builder has an error, this returns `None`.
307 ///
308 /// # Example
309 ///
310 /// ```
311 /// let req = ureq::get("http://httpbin.org/get")
312 /// .extension("My Extension").extension(5u32);
313 /// let extensions = req.extensions_ref().unwrap();
314 /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension"));
315 /// assert_eq!(extensions.get::<u32>(), Some(&5u32));
316 /// ```
317 pub fn extensions_ref(&self) -> Option<&Extensions> {
318 self.builder.extensions_ref()
319 }
320
321 /// Get a mutable reference to the extensions for this request builder.
322 ///
323 /// If the builder has an error, this returns `None`.
324 ///
325 /// # Example
326 ///
327 /// ```
328 /// let mut req = ureq::get("http://httpbin.org/get");
329 /// let mut extensions = req.extensions_mut().unwrap();
330 /// extensions.insert(5u32);
331 /// assert_eq!(extensions.get::<u32>(), Some(&5u32));
332 /// ```
333 pub fn extensions_mut(&mut self) -> Option<&mut Extensions> {
334 self.builder.extensions_mut()
335 }
336
337 pub(crate) fn request_level_config(&mut self) -> &mut Config {
338 let Some(exts) = self.builder.extensions_mut() else {
339 // This means self.builder has an error such as URL parsing error.
340 // The error will surface on .call() (or .send()) and we fill in
341 // a dummy Config meanwhile.
342 return self
343 .dummy_config
344 .get_or_insert_with(|| Box::new(Config::default()));
345 };
346
347 if exts.get::<RequestLevelConfig>().is_none() {
348 exts.insert(self.agent.new_request_level_config());
349 }
350
351 // Unwrap is OK because of above check
352 let req_level: &mut RequestLevelConfig = exts.get_mut().unwrap();
353
354 &mut req_level.0
355 }
356}
357
358impl RequestBuilder<WithoutBody> {
359 pub(crate) fn new<T>(agent: Agent, method: Method, uri: T) -> Self
360 where
361 Uri: TryFrom<T>,
362 <Uri as TryFrom<T>>::Error: Into<http::Error>,
363 {
364 Self {
365 agent,
366 builder: Request::builder().method(method).uri(uri),
367 query_extra: vec![],
368 dummy_config: None,
369 _ph: PhantomData,
370 }
371 }
372
373 /// Sends the request and blocks the caller until we receive a response.
374 ///
375 /// It sends neither `Content-Length` nor `Transfer-Encoding`.
376 ///
377 /// ```
378 /// let res = ureq::get("http://httpbin.org/get")
379 /// .call()?;
380 /// # Ok::<_, ureq::Error>(())
381 /// ```
382 pub fn call(self) -> Result<Response<Body>, Error> {
383 let request = self.builder.body(())?;
384 do_call(self.agent, request, self.query_extra, SendBody::none())
385 }
386
387 /// Force sending a body.
388 ///
389 /// This is an escape hatch to interact with broken services.
390 ///
391 /// According to the spec, methods such as GET, DELETE and TRACE should
392 /// not have a body. Despite that there are broken API services and
393 /// servers that use it.
394 ///
395 /// Example using DELETE while sending a body.
396 ///
397 /// ```
398 /// let res = ureq::delete("http://httpbin.org/delete")
399 /// // this "unlocks" send() below
400 /// .force_send_body()
401 /// .send("DELETE with body is not correct")?;
402 /// # Ok::<_, ureq::Error>(())
403 /// ```
404 pub fn force_send_body(mut self) -> RequestBuilder<WithBody> {
405 if let Some(exts) = self.extensions_mut() {
406 exts.insert(ForceSendBody);
407 }
408
409 RequestBuilder {
410 agent: self.agent,
411 builder: self.builder,
412 query_extra: self.query_extra,
413 dummy_config: None,
414 _ph: PhantomData,
415 }
416 }
417}
418
419#[derive(Debug, Clone)]
420pub(crate) struct ForceSendBody;
421
422impl RequestBuilder<WithBody> {
423 pub(crate) fn new<T>(agent: Agent, method: Method, uri: T) -> Self
424 where
425 Uri: TryFrom<T>,
426 <Uri as TryFrom<T>>::Error: Into<http::Error>,
427 {
428 Self {
429 agent,
430 builder: Request::builder().method(method).uri(uri),
431 query_extra: vec![],
432 dummy_config: None,
433 _ph: PhantomData,
434 }
435 }
436
437 /// Set the content-type header.
438 ///
439 /// ```
440 /// let res = ureq::post("http://httpbin.org/post")
441 /// .content_type("text/html; charset=utf-8")
442 /// .send("<html><body>åäö</body></html>")?;
443 /// # Ok::<_, ureq::Error>(())
444 /// ```
445 pub fn content_type<V>(mut self, content_type: V) -> Self
446 where
447 HeaderValue: TryFrom<V>,
448 <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
449 {
450 self.builder = self.builder.header("content-type", content_type);
451 self
452 }
453
454 /// Send body data and blocks the caller until we receive response.
455 ///
456 /// ```
457 /// let res = ureq::post("http://httpbin.org/post")
458 /// .send(&[0_u8; 1000])?;
459 /// # Ok::<_, ureq::Error>(())
460 /// ```
461 pub fn send(self, data: impl AsSendBody) -> Result<Response<Body>, Error> {
462 let request = self.builder.body(())?;
463 let mut data_ref = data;
464 do_call(self.agent, request, self.query_extra, data_ref.as_body())
465 }
466
467 /// Send an empty body.
468 ///
469 /// The method is POST, PUT or PATCH, which normally has a body. Using
470 /// this function makes it explicit you want to send an empty body despite
471 /// the method.
472 ///
473 /// This is equivalent to `.send(&[])`.
474 ///
475 /// ```
476 /// let res = ureq::post("http://httpbin.org/post")
477 /// .send_empty()?;
478 /// # Ok::<_, ureq::Error>(())
479 /// ```
480 pub fn send_empty(self) -> Result<Response<Body>, Error> {
481 self.send(&[])
482 }
483
484 /// Send form encoded data.
485 ///
486 /// Constructs a [form submission] with the content-type header
487 /// `application/x-www-form-urlencoded`. Keys and values will be URL encoded.
488 ///
489 /// ```
490 /// let form = [
491 /// ("name", "martin"),
492 /// ("favorite_bird", "blue-footed booby"),
493 /// ];
494 ///
495 /// let response = ureq::post("http://httpbin.org/post")
496 /// .send_form(form)?;
497 /// # Ok::<_, ureq::Error>(())
498 /// ```
499 ///
500 /// [form submission]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#url-encoded_form_submission
501 pub fn send_form<I, K, V>(self, iter: I) -> Result<Response<Body>, Error>
502 where
503 I: IntoIterator<Item = (K, V)>,
504 K: AsRef<str>,
505 V: AsRef<str>,
506 {
507 let iter = iter.into_iter();
508
509 // TODO(martin): can we calculate a size hint for capacity here?
510 let mut body = String::new();
511
512 for (k, v) in iter {
513 if !body.is_empty() {
514 body.push('&');
515 }
516 body.push_str(&url_enc(k.as_ref()));
517 body.push('=');
518 body.push_str(&url_enc(v.as_ref()));
519 }
520
521 let mut request = self.builder.body(())?;
522
523 if !request.headers().has_content_type() {
524 request.headers_mut().append(
525 http::header::CONTENT_TYPE,
526 HeaderValue::from_static("application/x-www-form-urlencoded"),
527 );
528 }
529
530 do_call(self.agent, request, self.query_extra, body.as_body())
531 }
532
533 /// Send body data as JSON.
534 ///
535 /// Requires the **json** feature.
536 ///
537 /// The data typically derives [`Serialize`](serde::Serialize) and is converted
538 /// to a string before sending (does allocate). Will set the content-type header
539 /// `application/json`.
540 ///
541 /// ```
542 /// use serde::Serialize;
543 ///
544 /// #[derive(Serialize)]
545 /// struct MyData {
546 /// thing: String,
547 /// }
548 ///
549 /// let body = MyData {
550 /// thing: "yo".to_string(),
551 /// };
552 ///
553 /// let res = ureq::post("http://httpbin.org/post")
554 /// .send_json(&body)?;
555 /// # Ok::<_, ureq::Error>(())
556 /// ```
557 #[cfg(feature = "json")]
558 pub fn send_json(self, data: impl serde::ser::Serialize) -> Result<Response<Body>, Error> {
559 let mut request = self.builder.body(())?;
560 let body = SendBody::from_json(&data)?;
561
562 if !request.headers().has_content_type() {
563 request.headers_mut().append(
564 http::header::CONTENT_TYPE,
565 HeaderValue::from_static("application/json; charset=utf-8"),
566 );
567 }
568
569 do_call(self.agent, request, self.query_extra, body)
570 }
571}
572
573fn do_call(
574 agent: Agent,
575 mut request: Request<()>,
576 query_extra: Vec<QueryParam<'static>>,
577 body: SendBody,
578) -> Result<Response<Body>, Error> {
579 if !query_extra.is_empty() {
580 request.uri().ensure_valid_url()?;
581 request = amend_request_query(request, query_extra.into_iter());
582 }
583 let response = agent.run_via_middleware(request, body)?;
584 Ok(response)
585}
586
587fn amend_request_query(
588 request: Request<()>,
589 query_extra: impl Iterator<Item = QueryParam<'static>>,
590) -> Request<()> {
591 let (mut parts, body) = request.into_parts();
592 let uri = parts.uri;
593 let mut path = uri.path().to_string();
594 let query_existing = parse_query_params(uri.query().unwrap_or(""));
595
596 let mut do_first = true;
597
598 fn append<'a>(
599 path: &mut String,
600 do_first: &mut bool,
601 iter: impl Iterator<Item = QueryParam<'a>>,
602 ) {
603 for q in iter {
604 if *do_first {
605 *do_first = false;
606 path.push('?');
607 } else {
608 path.push('&');
609 }
610 path.push_str(&q);
611 }
612 }
613
614 append(&mut path, &mut do_first, query_existing);
615 append(&mut path, &mut do_first, query_extra);
616
617 // Unwraps are OK, because we had a correct URI to begin with
618 let rebuild = Uri::builder()
619 .scheme(uri.scheme().unwrap().clone())
620 .authority(uri.authority().unwrap().clone())
621 .path_and_query(path)
622 .build()
623 .unwrap();
624
625 parts.uri = rebuild;
626
627 Request::from_parts(parts, body)
628}
629
630impl fmt::Debug for RequestBuilder<WithoutBody> {
631 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632 f.debug_struct("RequestBuilder<WithoutBody>")
633 // unwraps are OK because we can't be in this state without having method+uri
634 .field("method", &self.builder.method_ref().unwrap())
635 .field("uri", &self.builder.uri_ref().unwrap())
636 .finish()
637 }
638}
639
640impl fmt::Debug for RequestBuilder<WithBody> {
641 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
642 f.debug_struct("RequestBuilder<WithBody>")
643 // unwraps are OK because we can't be in this state without having method+uri
644 .field("method", &self.builder.method_ref().unwrap())
645 .field("uri", &self.builder.uri_ref().unwrap())
646 .finish()
647 }
648}
649
650#[cfg(test)]
651mod test {
652 use std::time::Duration;
653
654 use crate::get;
655 use crate::test::init_test_log;
656
657 use super::*;
658
659 #[test]
660 fn disallow_empty_host() {
661 let err = crate::get("file:///some/path").call().unwrap_err();
662 assert_eq!(err.to_string(), "http: invalid format");
663 assert!(matches!(err, Error::Http(_)));
664 }
665
666 #[test]
667 fn debug_print_without_body() {
668 let call = crate::get("https://foo/bar");
669 assert_eq!(
670 format!("{:?}", call),
671 "RequestBuilder<WithoutBody> { method: GET, uri: https://foo/bar }"
672 );
673 }
674
675 #[test]
676 fn debug_print_with_body() {
677 let call = crate::post("https://foo/bar");
678 assert_eq!(
679 format!("{:?}", call),
680 "RequestBuilder<WithBody> { method: POST, uri: https://foo/bar }"
681 );
682 }
683
684 #[test]
685 fn config_after_broken_url() {
686 init_test_log();
687 get("http://x.y.z/ borked url")
688 .config()
689 .timeout_global(Some(Duration::from_millis(1)))
690 .build();
691 }
692
693 #[test]
694 fn add_params_to_request_without_query() {
695 let request = Request::builder()
696 .uri("https://foo.bar/path")
697 .body(())
698 .unwrap();
699
700 let amended = amend_request_query(
701 request,
702 vec![
703 QueryParam::new_key_value("x", "z"),
704 QueryParam::new_key_value("ab", "cde"),
705 ]
706 .into_iter(),
707 );
708
709 assert_eq!(amended.uri(), "https://foo.bar/path?x=z&ab=cde");
710 }
711
712 #[test]
713 fn add_params_to_request_with_query() {
714 let request = Request::builder()
715 .uri("https://foo.bar/path?x=z")
716 .body(())
717 .unwrap();
718
719 let amended = amend_request_query(
720 request,
721 vec![QueryParam::new_key_value("ab", "cde")].into_iter(),
722 );
723
724 assert_eq!(amended.uri(), "https://foo.bar/path?x=z&ab=cde");
725 }
726
727 #[test]
728 fn add_params_that_need_percent_encoding() {
729 let request = Request::builder()
730 .uri("https://foo.bar/path")
731 .body(())
732 .unwrap();
733
734 let amended = amend_request_query(
735 request,
736 vec![QueryParam::new_key_value("å ", "i åa ä e ö")].into_iter(),
737 );
738
739 assert_eq!(
740 amended.uri(),
741 "https://foo.bar/path?%C3%A5%20=i%20%C3%A5a%20%C3%A4%20e%20%C3%B6"
742 );
743 }
744}