ureq/
request.rs

1use std::io::Read;
2use std::{fmt, time};
3
4use url::{form_urlencoded, ParseError, Url};
5
6use crate::agent::Agent;
7use crate::body::Payload;
8use crate::error::{Error, ErrorKind};
9use crate::header::{self, Header};
10use crate::middleware::MiddlewareNext;
11use crate::unit::{self, Unit};
12use crate::Response;
13
14pub type Result<T> = std::result::Result<T, Error>;
15
16/// Request instances are builders that creates a request.
17///
18/// ```
19/// # fn main() -> Result<(), ureq::Error> {
20/// # ureq::is_test(true);
21/// let response = ureq::get("http://example.com/get")
22///     .query("foo", "bar baz")  // add ?foo=bar+baz
23///     .call()?;                 // run the request
24/// # Ok(())
25/// # }
26/// ```
27#[derive(Clone)]
28#[must_use = "Requests do nothing until consumed by `call()`"]
29pub struct Request {
30    agent: Agent,
31    method: String,
32    url: String,
33    pub(crate) headers: Vec<Header>,
34    timeout: Option<time::Duration>,
35}
36
37impl fmt::Debug for Request {
38    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39        write!(
40            f,
41            "Request({} {}, {:?})",
42            self.method, self.url, self.headers
43        )
44    }
45}
46
47impl Request {
48    pub(crate) fn new(agent: Agent, method: String, url: String) -> Request {
49        Request {
50            agent,
51            method,
52            url,
53            headers: vec![],
54            timeout: None,
55        }
56    }
57
58    #[inline(always)]
59    /// Sets overall timeout for the request, overriding agent's configuration if any.
60    pub fn timeout(mut self, timeout: time::Duration) -> Self {
61        self.timeout = Some(timeout);
62        self
63    }
64
65    /// Sends the request with no body and blocks the caller until done.
66    ///
67    /// Use this with GET, HEAD, OPTIONS or TRACE. It sends neither
68    /// Content-Length nor Transfer-Encoding.
69    ///
70    /// ```
71    /// # fn main() -> Result<(), ureq::Error> {
72    /// # ureq::is_test(true);
73    /// let resp = ureq::get("http://example.com/")
74    ///     .call()?;
75    /// # Ok(())
76    /// # }
77    /// ```
78    pub fn call(self) -> Result<Response> {
79        self.do_call(Payload::Empty)
80    }
81
82    fn parse_url(&self) -> Result<Url> {
83        Ok(self.url.parse().and_then(|url: Url|
84            // No hostname is fine for urls in general, but not for website urls.
85            if url.host_str().is_none() {
86                Err(ParseError::EmptyHost)
87            } else {
88                Ok(url)
89            }
90        )?)
91    }
92
93    /// Add Accept-Encoding header with supported values, unless user has
94    /// already set this header or is requesting a specific byte-range.
95    #[cfg(any(feature = "gzip", feature = "brotli"))]
96    fn add_accept_encoding(&mut self) {
97        let should_add = !self.headers.iter().map(|h| h.name()).any(|name| {
98            name.eq_ignore_ascii_case("accept-encoding") || name.eq_ignore_ascii_case("range")
99        });
100        if should_add {
101            const GZ: bool = cfg!(feature = "gzip");
102            const BR: bool = cfg!(feature = "brotli");
103            const ACCEPT: &str = match (GZ, BR) {
104                (true, true) => "gzip, br",
105                (true, false) => "gzip",
106                (false, true) => "br",
107                (false, false) => "identity", // unreachable due to cfg feature on this fn
108            };
109            self.headers.push(Header::new("accept-encoding", ACCEPT));
110        }
111    }
112
113    #[cfg_attr(not(any(feature = "gzip", feature = "brotli")), allow(unused_mut))]
114    fn do_call(mut self, payload: Payload) -> Result<Response> {
115        for h in &self.headers {
116            h.validate()?;
117        }
118
119        #[cfg(any(feature = "gzip", feature = "brotli"))]
120        self.add_accept_encoding();
121
122        let deadline = match self.timeout.or(self.agent.config.timeout) {
123            None => None,
124            Some(timeout) => {
125                let now = time::Instant::now();
126                match now.checked_add(timeout) {
127                    Some(dl) => Some(dl),
128                    None => {
129                        return Err(Error::new(
130                            ErrorKind::Io,
131                            Some("Request deadline overflowed".to_string()),
132                        ))
133                    }
134                }
135            }
136        };
137
138        let request_fn = |req: Request| {
139            let reader = payload.into_read();
140            let url = req.parse_url()?;
141            let unit = Unit::new(
142                &req.agent,
143                &req.method,
144                &url,
145                req.headers,
146                &reader,
147                deadline,
148            );
149
150            unit::connect(unit, true, reader).map_err(|e| e.url(url))
151        };
152
153        let response = if !self.agent.state.middleware.is_empty() {
154            // Clone agent to get a local copy with same lifetime as Payload
155            let agent = self.agent.clone();
156            let chain = &mut agent.state.middleware.iter().map(|mw| mw.as_ref());
157
158            let request_fn = Box::new(request_fn);
159
160            let next = MiddlewareNext { chain, request_fn };
161
162            // // Run middleware chain
163            next.handle(self)?
164        } else {
165            // Run the request_fn without any further indirection.
166            request_fn(self)?
167        };
168
169        if response.status() >= 400 {
170            Err(Error::Status(response.status(), response))
171        } else {
172            Ok(response)
173        }
174    }
175
176    /// Send data a json value.
177    ///
178    /// The `Content-Length` header is implicitly set to the length of the serialized value.
179    ///
180    /// ```
181    /// # fn main() -> Result<(), ureq::Error> {
182    /// # ureq::is_test(true);
183    /// let resp = ureq::post("http://httpbin.org/post")
184    ///     .send_json(ureq::json!({
185    ///       "name": "martin",
186    ///       "rust": true,
187    ///     }))?;
188    /// # Ok(())
189    /// # }
190    /// ```
191    #[cfg(feature = "json")]
192    pub fn send_json(mut self, data: impl serde::Serialize) -> Result<Response> {
193        if self.header("Content-Type").is_none() {
194            self = self.set("Content-Type", "application/json");
195        }
196
197        let json_bytes = serde_json::to_vec(&data)
198            .expect("Failed to serialize data passed to send_json into JSON");
199
200        self.do_call(Payload::Bytes(&json_bytes))
201    }
202
203    /// Send data as bytes.
204    ///
205    /// The `Content-Length` header is implicitly set to the length of the serialized value.
206    ///
207    /// ```
208    /// # fn main() -> Result<(), ureq::Error> {
209    /// # ureq::is_test(true);
210    /// let resp = ureq::put("http://httpbin.org/put")
211    ///     .send_bytes(&[0; 1000])?;
212    /// # Ok(())
213    /// # }
214    /// ```
215    pub fn send_bytes(self, data: &[u8]) -> Result<Response> {
216        self.do_call(Payload::Bytes(data))
217    }
218
219    /// Send data as a string.
220    ///
221    /// The `Content-Length` header is implicitly set to the length of the serialized value.
222    /// Defaults to `utf-8`
223    ///
224    /// ## Charset support
225    ///
226    /// Requires feature `ureq = { version = "*", features = ["charset"] }`
227    ///
228    /// If a `Content-Type` header is present and it contains a charset specification, we
229    /// attempt to encode the string using that character set. If it fails, we fall back
230    /// on utf-8.
231    ///
232    /// ```
233    /// // this example requires features = ["charset"]
234    ///
235    /// # fn main() -> Result<(), ureq::Error> {
236    /// # ureq::is_test(true);
237    /// let resp = ureq::post("http://httpbin.org/post")
238    ///     .set("Content-Type", "text/plain; charset=iso-8859-1")
239    ///     .send_string("Hällo Wörld!")?;
240    /// # Ok(())
241    /// # }
242    /// ```
243    pub fn send_string(self, data: &str) -> Result<Response> {
244        let charset =
245            crate::response::charset_from_content_type(self.header("content-type")).to_string();
246        self.do_call(Payload::Text(data, charset))
247    }
248
249    /// Send a sequence of (key, value) pairs as form-urlencoded data.
250    ///
251    /// The `Content-Type` header is implicitly set to application/x-www-form-urlencoded.
252    /// The `Content-Length` header is implicitly set to the length of the serialized value.
253    ///
254    /// ```
255    /// # fn main() -> Result<(), ureq::Error> {
256    /// # ureq::is_test(true);
257    /// let resp = ureq::post("http://httpbin.org/post")
258    ///     .send_form(&[
259    ///       ("foo", "bar"),
260    ///       ("foo2", "bar2"),
261    ///     ])?;
262    /// # Ok(())
263    /// # }
264    /// ```
265    pub fn send_form(mut self, data: &[(&str, &str)]) -> Result<Response> {
266        if self.header("Content-Type").is_none() {
267            self = self.set("Content-Type", "application/x-www-form-urlencoded");
268        }
269        let encoded = form_urlencoded::Serializer::new(String::new())
270            .extend_pairs(data)
271            .finish();
272        self.do_call(Payload::Bytes(&encoded.into_bytes()))
273    }
274
275    /// Send data from a reader.
276    ///
277    /// If no Content-Length and Transfer-Encoding header has been set, it uses the [chunked transfer encoding](https://tools.ietf.org/html/rfc7230#section-4.1).
278    ///
279    /// The caller may set the Content-Length header to the expected byte size of the reader if is
280    /// known.
281    ///
282    /// The input from the reader is buffered into chunks of size 16,384, the max size of a TLS fragment.
283    ///
284    /// ```
285    /// use std::io::Cursor;
286    /// # fn main() -> Result<(), ureq::Error> {
287    /// # ureq::is_test(true);
288    /// let read = Cursor::new(vec![0x20; 100]);
289    /// let resp = ureq::post("http://httpbin.org/post")
290    ///     .send(read)?;
291    /// # Ok(())
292    /// # }
293    /// ```
294    pub fn send(self, reader: impl Read) -> Result<Response> {
295        self.do_call(Payload::Reader(Box::new(reader)))
296    }
297
298    /// Set a header field.
299    ///
300    /// ```
301    /// # fn main() -> Result<(), ureq::Error> {
302    /// # ureq::is_test(true);
303    /// let resp = ureq::get("http://httpbin.org/bytes/1000")
304    ///     .set("Accept", "text/plain")
305    ///     .set("Range", "bytes=500-999")
306    ///     .call()?;
307    /// # Ok(())
308    /// # }
309    /// ```
310    pub fn set(mut self, header: &str, value: &str) -> Self {
311        header::add_header(&mut self.headers, Header::new(header, value));
312        self
313    }
314
315    /// Returns the value for a set header.
316    ///
317    /// ```
318    /// let req = ureq::get("/my_page")
319    ///     .set("X-API-Key", "foobar");
320    /// assert_eq!("foobar", req.header("x-api-Key").unwrap());
321    /// ```
322    pub fn header(&self, name: &str) -> Option<&str> {
323        header::get_header(&self.headers, name)
324    }
325
326    /// A list of the set header names in this request. Lowercased to be uniform.
327    ///
328    /// ```
329    /// let req = ureq::get("/my_page")
330    ///     .set("X-API-Key", "foobar")
331    ///     .set("Content-Type", "application/json");
332    /// assert_eq!(req.header_names(), vec!["x-api-key", "content-type"]);
333    /// ```
334    pub fn header_names(&self) -> Vec<String> {
335        self.headers
336            .iter()
337            .map(|h| h.name().to_ascii_lowercase())
338            .collect()
339    }
340
341    /// Tells if the header has been set.
342    ///
343    /// ```
344    /// let req = ureq::get("/my_page")
345    ///     .set("X-API-Key", "foobar");
346    /// assert_eq!(true, req.has("x-api-Key"));
347    /// ```
348    pub fn has(&self, name: &str) -> bool {
349        header::has_header(&self.headers, name)
350    }
351
352    /// All headers corresponding values for the give name, or empty vector.
353    ///
354    /// ```
355    /// let req = ureq::get("/my_page")
356    ///     .set("X-Forwarded-For", "1.2.3.4")
357    ///     .set("X-Forwarded-For", "2.3.4.5");
358    ///
359    /// assert_eq!(req.all("x-forwarded-for"), vec![
360    ///     "1.2.3.4",
361    ///     "2.3.4.5",
362    /// ]);
363    /// ```
364    pub fn all(&self, name: &str) -> Vec<&str> {
365        header::get_all_headers(&self.headers, name)
366    }
367
368    /// Set a query parameter.
369    ///
370    /// For example, to set `?format=json&dest=/login`
371    ///
372    /// ```
373    /// # fn main() -> Result<(), ureq::Error> {
374    /// # ureq::is_test(true);
375    /// let resp = ureq::get("http://httpbin.org/get")
376    ///     .query("format", "json")
377    ///     .query("dest", "/login")
378    ///     .call()?;
379    /// # Ok(())
380    /// # }
381    /// ```
382    pub fn query(mut self, param: &str, value: &str) -> Self {
383        if let Ok(mut url) = self.parse_url() {
384            url.query_pairs_mut().append_pair(param, value);
385
386            // replace url
387            self.url = url.to_string();
388        }
389        self
390    }
391
392    /// Set multi query parameters.
393    ///
394    /// For example, to set `?format=json&dest=/login`
395    ///
396    /// ```
397    /// # fn main() -> Result<(), ureq::Error> {
398    /// # ureq::is_test(true);
399    ///
400    /// let query = vec![
401    ///     ("format", "json"),
402    ///     ("dest", "/login"),
403    /// ];
404    ///
405    /// let resp = ureq::get("http://httpbin.org/get")
406    ///     .query_pairs(query)
407    ///     .call()?;
408    /// # Ok(())
409    /// # }
410    /// ```
411    pub fn query_pairs<'a, P>(mut self, pairs: P) -> Self
412    where
413        P: IntoIterator<Item = (&'a str, &'a str)>,
414    {
415        if let Ok(mut url) = self.parse_url() {
416            {
417                let mut query_pairs = url.query_pairs_mut();
418                for (param, value) in pairs {
419                    query_pairs.append_pair(param, value);
420                }
421            }
422
423            // replace url
424            self.url = url.to_string();
425        }
426        self
427    }
428
429    /// Returns the value of the request method. Something like `GET`, `POST`, `PUT` etc.
430    ///
431    /// ```
432    /// let req = ureq::put("http://httpbin.org/put");
433    ///
434    /// assert_eq!(req.method(), "PUT");
435    /// ```
436    pub fn method(&self) -> &str {
437        &self.method
438    }
439
440    /// Get the url str that will be used for this request.
441    ///
442    /// The url might differ from that originally provided when constructing the
443    /// request if additional query parameters have been added using [`Request::query()`].
444    ///
445    /// In case the original url provided to build the request is not possible to
446    /// parse to a Url, this function returns the original, and it will error once the
447    /// Request object is used.
448    ///
449    /// ```
450    /// # fn main() -> Result<(), ureq::Error> {
451    /// # ureq::is_test(true);
452    /// let req = ureq::get("http://httpbin.org/get")
453    ///     .query("foo", "bar");
454    ///
455    /// assert_eq!(req.url(), "http://httpbin.org/get?foo=bar");
456    /// # Ok(())
457    /// # }
458    /// ```
459    ///
460    /// ```
461    /// # fn main() -> Result<(), ureq::Error> {
462    /// # ureq::is_test(true);
463    /// let req = ureq::get("SO WRONG")
464    ///     .query("foo", "bar"); // does nothing
465    ///
466    /// assert_eq!(req.url(), "SO WRONG");
467    /// # Ok(())
468    /// # }
469    /// ```
470    pub fn url(&self) -> &str {
471        &self.url
472    }
473
474    /// Get the parsed url that will be used for this request. The parsed url
475    /// has functions to inspect the parts of the url further.
476    ///
477    /// The url might differ from that originally provided when constructing the
478    /// request if additional query parameters have been added using [`Request::query()`].
479    ///
480    /// Returns a `Result` since a common use case is to construct
481    /// the [`Request`] using a `&str` in which case the url needs to be parsed
482    /// to inspect the parts. If the Request url is not possible to parse, this
483    /// function produces the same error that would otherwise happen when
484    /// `call` or `send_*` is called.
485    ///
486    /// ```
487    /// # fn main() -> Result<(), ureq::Error> {
488    /// # ureq::is_test(true);
489    /// let req = ureq::get("http://httpbin.org/get")
490    ///     .query("foo", "bar");
491    ///
492    /// assert_eq!(req.request_url()?.host(), "httpbin.org");
493    /// # Ok(())
494    /// # }
495    /// ```
496    pub fn request_url(&self) -> Result<RequestUrl> {
497        Ok(RequestUrl::new(self.parse_url()?))
498    }
499}
500
501/// Parsed result of a request url with handy inspection methods.
502#[derive(Debug, Clone)]
503pub struct RequestUrl {
504    url: Url,
505    query_pairs: Vec<(String, String)>,
506}
507
508impl RequestUrl {
509    fn new(url: Url) -> Self {
510        // This is needed to avoid url::Url Cow<str>. We want ureq API to work with &str.
511        let query_pairs = url
512            .query_pairs()
513            .map(|(k, v)| (k.to_string(), v.to_string()))
514            .collect();
515
516        RequestUrl { url, query_pairs }
517    }
518
519    /// Handle the request url as a standard [`url::Url`].
520    pub fn as_url(&self) -> &Url {
521        &self.url
522    }
523
524    /// Get the scheme of the request url, i.e. "https" or "http".
525    pub fn scheme(&self) -> &str {
526        self.url.scheme()
527    }
528
529    /// Host of the request url.
530    pub fn host(&self) -> &str {
531        // this unwrap() is ok, because RequestUrl is tested for empty host
532        // urls in Request::parse_url().
533        self.url.host_str().unwrap()
534    }
535
536    /// Port of the request url, if available. Ports are only available if they
537    /// are present in the original url. Specifically the scheme default ports,
538    /// 443 for `https` and and 80 for `http` are `None` unless explicitly
539    /// set in the url, i.e. `https://my-host.com:443/some/path`.
540    pub fn port(&self) -> Option<u16> {
541        self.url.port()
542    }
543
544    /// Path of the request url.
545    pub fn path(&self) -> &str {
546        self.url.path()
547    }
548
549    /// Returns all query parameters as a vector of key-value pairs.
550    ///
551    /// ```
552    /// # fn main() -> Result<(), ureq::Error> {
553    /// # ureq::is_test(true);
554    /// let req = ureq::get("http://httpbin.org/get")
555    ///     .query("foo", "42")
556    ///     .query("foo", "43");
557    ///
558    /// assert_eq!(req.request_url()?.query_pairs(), vec![
559    ///     ("foo", "42"),
560    ///     ("foo", "43")
561    /// ]);
562    /// # Ok(())
563    /// # }
564    /// ```
565    pub fn query_pairs(&self) -> Vec<(&str, &str)> {
566        self.query_pairs
567            .iter()
568            .map(|(k, v)| (k.as_str(), v.as_str()))
569            .collect()
570    }
571}
572
573#[cfg(test)]
574mod tests {
575    use super::*;
576
577    #[test]
578    fn request_implements_send_and_sync() {
579        let _request: Box<dyn Send> = Box::new(Request::new(
580            Agent::new(),
581            "GET".to_string(),
582            "https://example.com/".to_string(),
583        ));
584        let _request: Box<dyn Sync> = Box::new(Request::new(
585            Agent::new(),
586            "GET".to_string(),
587            "https://example.com/".to_string(),
588        ));
589    }
590
591    #[test]
592    fn send_byte_slice() {
593        let bytes = vec![1, 2, 3];
594        crate::agent()
595            .post("http://example.com")
596            .send(&bytes[1..2])
597            .ok();
598    }
599
600    #[test]
601    fn disallow_empty_host() {
602        let req = crate::agent().get("file:///some/path");
603
604        // Both request_url and call() must surface the same error.
605        assert_eq!(
606            req.request_url().unwrap_err().kind(),
607            crate::ErrorKind::InvalidUrl
608        );
609
610        assert_eq!(req.call().unwrap_err().kind(), crate::ErrorKind::InvalidUrl);
611    }
612}