[go: up one dir, main page]

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}