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 let url = self.parse_url()?;
119
120 #[cfg(any(feature = "gzip", feature = "brotli"))]
121 self.add_accept_encoding();
122
123 let deadline = match self.timeout.or(self.agent.config.timeout) {
124 None => None,
125 Some(timeout) => {
126 let now = time::Instant::now();
127 match now.checked_add(timeout) {
128 Some(dl) => Some(dl),
129 None => {
130 return Err(Error::new(
131 ErrorKind::Io,
132 Some("Request deadline overflowed".to_string()),
133 ))
134 }
135 }
136 }
137 };
138
139 let request_fn = |req: Request| {
140 let reader = payload.into_read();
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.clone()))
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}
613