use std::fmt; use std::io::Read; use url::{form_urlencoded, Url}; use crate::body::Payload; use crate::error::ErrorKind; use crate::header::{self, Header}; use crate::unit::{self, Unit}; use crate::Response; use crate::{agent::Agent, error::Error}; #[cfg(feature = "json")] use super::SerdeValue; pub type Result = std::result::Result; #[derive(Debug, Clone)] enum Urlish { Url(Url), Str(String), } /// Request instances are builders that creates a request. /// /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let response = ureq::get("http://example.com/form") /// .query("foo", "bar baz") // add ?foo=bar+baz /// .call()?; // run the request /// # Ok(()) /// # } /// ``` #[derive(Clone)] pub struct Request { agent: Agent, method: String, url: Urlish, error_on_non_2xx: bool, headers: Vec
, query_params: Vec<(String, String)>, } impl fmt::Display for Urlish { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Urlish::Url(u) => write!(f, "{}", u), Urlish::Str(s) => write!(f, "{}", s), } } } impl fmt::Debug for Request { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "Request({} {} {:?}, {:?})", self.method, self.url, self.query_params, self.headers ) } } impl Request { pub(crate) fn new(agent: Agent, method: String, url: String) -> Request { Request { agent, method, url: Urlish::Str(url), headers: vec![], error_on_non_2xx: true, query_params: vec![], } } pub(crate) fn with_url(agent: Agent, method: String, url: Url) -> Request { Request { agent, method, url: Urlish::Url(url), headers: vec![], error_on_non_2xx: true, query_params: vec![], } } /// Sends the request with no body and blocks the caller until done. /// /// Use this with GET, HEAD, or TRACE. It sends neither Content-Length /// nor Transfer-Encoding. /// /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let resp = ureq::get("http://example.com/") /// .call()?; /// # Ok(()) /// # } /// ``` pub fn call(self) -> Result { self.do_call(Payload::Empty) } fn do_call(&self, payload: Payload) -> Result { for h in &self.headers { h.validate()?; } let mut url: Url = match self.url.clone() { Urlish::Url(u) => u, Urlish::Str(s) => s.parse().map_err(|e: url::ParseError| { ErrorKind::BadUrl .msg(&format!("failed to parse URL '{}'", self.url)) .src(e) })?, }; for (name, value) in self.query_params.clone() { url.query_pairs_mut().append_pair(&name, &value); } let reader = payload.into_read(); let unit = Unit::new(&self.agent, &self.method, &url, &self.headers, &reader); let response = unit::connect(unit, true, 0, reader, false).map_err(|e| e.url(url))?; if response.error() && self.error_on_non_2xx { Err(ErrorKind::HTTP.new().response(response)) } else { Ok(response) } } /// Send data a json value. /// /// Requires feature `ureq = { version = "*", features = ["json"] }` /// /// The `Content-Length` header is implicitly set to the length of the serialized value. /// /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let resp = ureq::post("http://httpbin.org/post") /// .send_json(ureq::json!({ /// "name": "martin", /// "rust": true, /// }))?; /// # Ok(()) /// # } /// ``` #[cfg(feature = "json")] pub fn send_json(mut self, data: SerdeValue) -> Result { if self.header("Content-Type").is_none() { self = self.set("Content-Type", "application/json"); } self.do_call(Payload::JSON(data)) } /// Send data as bytes. /// /// The `Content-Length` header is implicitly set to the length of the serialized value. /// /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let resp = ureq::put("http://httpbin.org/put") /// .send_bytes(&[0; 1000])?; /// # Ok(()) /// # } /// ``` pub fn send_bytes(self, data: &[u8]) -> Result { self.do_call(Payload::Bytes(data)) } /// Send data as a string. /// /// The `Content-Length` header is implicitly set to the length of the serialized value. /// Defaults to `utf-8` /// /// ## Charset support /// /// Requires feature `ureq = { version = "*", features = ["charset"] }` /// /// If a `Content-Type` header is present and it contains a charset specification, we /// attempt to encode the string using that character set. If it fails, we fall back /// on utf-8. /// /// ``` /// // this example requires features = ["charset"] /// /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let resp = ureq::post("http://httpbin.org/post") /// .set("Content-Type", "text/plain; charset=iso-8859-1") /// .send_string("Hällo Wörld!")?; /// # Ok(()) /// # } /// ``` pub fn send_string(self, data: &str) -> Result { let charset = crate::response::charset_from_content_type(self.header("content-type")).to_string(); self.do_call(Payload::Text(data, charset)) } /// Send a sequence of (key, value) pairs as form-urlencoded data. /// /// The `Content-Type` header is implicitly set to application/x-www-form-urlencoded. /// The `Content-Length` header is implicitly set to the length of the serialized value. /// /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let resp = ureq::post("http://httpbin.org/post") /// .send_form(&[ /// ("foo", "bar"), /// ("foo2", "bar2"), /// ])?; /// # Ok(()) /// # } /// ``` pub fn send_form(mut self, data: &[(&str, &str)]) -> Result { if self.header("Content-Type").is_none() { self = self.set("Content-Type", "application/x-www-form-urlencoded"); } let encoded = form_urlencoded::Serializer::new(String::new()) .extend_pairs(data) .finish(); self.do_call(Payload::Bytes(&encoded.into_bytes())) } /// Send data from a reader. /// /// 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). /// /// The caller may set the Content-Length header to the expected byte size of the reader if is /// known. /// /// The input from the reader is buffered into chunks of size 16,384, the max size of a TLS fragment. /// /// ``` /// use std::io::Cursor; /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let read = Cursor::new(vec![0x20; 100]); /// let resp = ureq::post("http://httpbin.org/post") /// .send(read)?; /// # Ok(()) /// # } /// ``` pub fn send(self, reader: impl Read) -> Result { self.do_call(Payload::Reader(Box::new(reader))) } /// Set a header field. /// /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let resp = ureq::get("http://httpbin.org/bytes/1000") /// .set("Accept", "text/plain") /// .set("Range", "bytes=500-999") /// .call()?; /// # Ok(()) /// # } /// ``` pub fn set(mut self, header: &str, value: &str) -> Self { header::add_header(&mut self.headers, Header::new(header, value)); self } /// Returns the value for a set header. /// /// ``` /// let req = ureq::get("/my_page") /// .set("X-API-Key", "foobar"); /// assert_eq!("foobar", req.header("x-api-Key").unwrap()); /// ``` pub fn header(&self, name: &str) -> Option<&str> { header::get_header(&self.headers, name) } /// A list of the set header names in this request. Lowercased to be uniform. /// /// ``` /// let req = ureq::get("/my_page") /// .set("X-API-Key", "foobar") /// .set("Content-Type", "application/json"); /// assert_eq!(req.header_names(), vec!["x-api-key", "content-type"]); /// ``` pub fn header_names(&self) -> Vec { self.headers .iter() .map(|h| h.name().to_ascii_lowercase()) .collect() } /// Tells if the header has been set. /// /// ``` /// let req = ureq::get("/my_page") /// .set("X-API-Key", "foobar"); /// assert_eq!(true, req.has("x-api-Key")); /// ``` pub fn has(&self, name: &str) -> bool { header::has_header(&self.headers, name) } /// All headers corresponding values for the give name, or empty vector. /// /// ``` /// let req = ureq::get("/my_page") /// .set("X-Forwarded-For", "1.2.3.4") /// .set("X-Forwarded-For", "2.3.4.5"); /// /// assert_eq!(req.all("x-forwarded-for"), vec![ /// "1.2.3.4", /// "2.3.4.5", /// ]); /// ``` pub fn all(&self, name: &str) -> Vec<&str> { header::get_all_headers(&self.headers, name) } /// Set a query parameter. /// /// For example, to set `?format=json&dest=/login` /// /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let resp = ureq::get("http://httpbin.org/response-headers") /// .query("format", "json") /// .query("dest", "/login") /// .call()?; /// # Ok(()) /// # } /// ``` pub fn query(mut self, param: &str, value: &str) -> Self { self.query_params .push((param.to_string(), value.to_string())); self } /// By default, if a response's status is anything but a 2xx or 3xx, /// call()/send() and related methods will return an Error. If you want /// to handle such responses as non-errors, set this to `false`. /// /// Example: /// ``` /// # fn main() -> Result<(), ureq::Error> { /// # ureq::is_test(true); /// let response = ureq::get("http://httpbin.org/status/500") /// .error_on_non_2xx(false) /// .call()?; /// assert_eq!(response.status(), 500); /// # Ok(()) /// # } /// ``` pub fn error_on_non_2xx(mut self, value: bool) -> Self { self.error_on_non_2xx = value; self } } #[test] fn request_implements_send_and_sync() { let _request: Box = Box::new(Request::new( Agent::new(), "GET".to_string(), "https://example.com/".to_string(), )); let _request: Box = Box::new(Request::new( Agent::new(), "GET".to_string(), "https://example.com/".to_string(), )); } #[test] fn send_byte_slice() { let bytes = vec![1, 2, 3]; crate::agent() .post("http://example.com") .send(&bytes[1..2]) .ok(); }