Url access functions for Request (simpler)
This commit is contained in:
@@ -301,7 +301,7 @@ pub use crate::agent::AgentBuilder;
|
|||||||
pub use crate::error::{Error, ErrorKind, OrAnyStatus, Transport};
|
pub use crate::error::{Error, ErrorKind, OrAnyStatus, Transport};
|
||||||
pub use crate::header::Header;
|
pub use crate::header::Header;
|
||||||
pub use crate::proxy::Proxy;
|
pub use crate::proxy::Proxy;
|
||||||
pub use crate::request::Request;
|
pub use crate::request::{Request, RequestUrl};
|
||||||
pub use crate::resolve::Resolver;
|
pub use crate::resolve::Resolver;
|
||||||
pub use crate::response::Response;
|
pub use crate::response::Response;
|
||||||
|
|
||||||
|
|||||||
200
src/request.rs
200
src/request.rs
@@ -1,7 +1,7 @@
|
|||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::{fmt, time};
|
use std::{fmt, time};
|
||||||
|
|
||||||
use url::{form_urlencoded, Url};
|
use url::{form_urlencoded, ParseError, Url};
|
||||||
|
|
||||||
use crate::body::Payload;
|
use crate::body::Payload;
|
||||||
use crate::header::{self, Header};
|
use crate::header::{self, Header};
|
||||||
@@ -14,31 +14,6 @@ use super::SerdeValue;
|
|||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ParsedUrl(std::result::Result<Url, url::ParseError>);
|
|
||||||
|
|
||||||
impl fmt::Display for ParsedUrl {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
if let Ok(url) = &self.0 {
|
|
||||||
write!(f, "{}", url.as_str())
|
|
||||||
} else {
|
|
||||||
write!(f, "{:?}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for ParsedUrl {
|
|
||||||
fn from(s: String) -> Self {
|
|
||||||
ParsedUrl(s.parse())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Url> for ParsedUrl {
|
|
||||||
fn from(url: Url) -> Self {
|
|
||||||
ParsedUrl(Ok(url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Request instances are builders that creates a request.
|
/// Request instances are builders that creates a request.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@@ -54,7 +29,7 @@ impl From<Url> for ParsedUrl {
|
|||||||
pub struct Request {
|
pub struct Request {
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
method: String,
|
method: String,
|
||||||
parsed_url: ParsedUrl,
|
url: String,
|
||||||
error_on_non_2xx: bool,
|
error_on_non_2xx: bool,
|
||||||
headers: Vec<Header>,
|
headers: Vec<Header>,
|
||||||
timeout: Option<time::Duration>,
|
timeout: Option<time::Duration>,
|
||||||
@@ -65,25 +40,25 @@ impl fmt::Debug for Request {
|
|||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"Request({} {}, {:?})",
|
"Request({} {}, {:?})",
|
||||||
self.method, self.parsed_url, self.headers
|
self.method, self.url, self.headers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Request {
|
impl Request {
|
||||||
pub(crate) fn new(agent: Agent, method: String, url: String) -> Request {
|
pub(crate) fn new(agent: Agent, method: String, url: String) -> Request {
|
||||||
Self::_new(agent, method, url.into())
|
Self::_new(agent, method, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn with_url(agent: Agent, method: String, url: Url) -> Request {
|
pub(crate) fn with_url(agent: Agent, method: String, url: Url) -> Request {
|
||||||
Self::_new(agent, method, url.into())
|
Self::_new(agent, method, url.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _new(agent: Agent, method: String, parsed_url: ParsedUrl) -> Request {
|
fn _new(agent: Agent, method: String, url: String) -> Request {
|
||||||
Request {
|
Request {
|
||||||
agent,
|
agent,
|
||||||
method,
|
method,
|
||||||
parsed_url,
|
url,
|
||||||
headers: vec![],
|
headers: vec![],
|
||||||
error_on_non_2xx: true,
|
error_on_non_2xx: true,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
@@ -114,11 +89,22 @@ impl Request {
|
|||||||
self.do_call(Payload::Empty)
|
self.do_call(Payload::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_url(&self) -> Result<Url> {
|
||||||
|
Ok(self.url.parse().and_then(|url: Url|
|
||||||
|
// No hostname is fine for urls in general, but not for website urls.
|
||||||
|
if url.host_str().is_none() {
|
||||||
|
Err(ParseError::EmptyHost)
|
||||||
|
} else {
|
||||||
|
Ok(url)
|
||||||
|
}
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
fn do_call(self, payload: Payload) -> Result<Response> {
|
fn do_call(self, payload: Payload) -> Result<Response> {
|
||||||
for h in &self.headers {
|
for h in &self.headers {
|
||||||
h.validate()?;
|
h.validate()?;
|
||||||
}
|
}
|
||||||
let url = self.parsed_url.0?;
|
let url = self.parse_url()?;
|
||||||
|
|
||||||
let deadline = match self.timeout.or(self.agent.config.timeout) {
|
let deadline = match self.timeout.or(self.agent.config.timeout) {
|
||||||
None => None,
|
None => None,
|
||||||
@@ -351,8 +337,11 @@ impl Request {
|
|||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn query(mut self, param: &str, value: &str) -> Self {
|
pub fn query(mut self, param: &str, value: &str) -> Self {
|
||||||
if let Ok(url) = &mut self.parsed_url.0 {
|
if let Ok(mut url) = self.parse_url() {
|
||||||
url.query_pairs_mut().append_pair(param, value);
|
url.query_pairs_mut().append_pair(param, value);
|
||||||
|
|
||||||
|
// replace url
|
||||||
|
self.url = url.to_string();
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -368,6 +357,115 @@ impl Request {
|
|||||||
&self.method
|
&self.method
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the url str that will be used for this request.
|
||||||
|
///
|
||||||
|
/// The url might differ from that originally provided when constructing the
|
||||||
|
/// request if additional query parameters have been added using [`Request::query()`].
|
||||||
|
///
|
||||||
|
/// In case the original url provided to build the request is not possible to
|
||||||
|
/// parse to a Url, this function returns the original, and it will error once the
|
||||||
|
/// Request object is used.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), ureq::Error> {
|
||||||
|
/// # ureq::is_test(true);
|
||||||
|
/// let req = ureq::get("http://httpbin.org/get")
|
||||||
|
/// .query("foo", "bar");
|
||||||
|
///
|
||||||
|
/// assert_eq!(req.url(), "http://httpbin.org/get?foo=bar");
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), ureq::Error> {
|
||||||
|
/// # ureq::is_test(true);
|
||||||
|
/// let req = ureq::get("SO WRONG")
|
||||||
|
/// .query("foo", "bar"); // does nothing
|
||||||
|
///
|
||||||
|
/// assert_eq!(req.url(), "SO WRONG");
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn url(&self) -> &str {
|
||||||
|
&self.url
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the parsed url that will be used for this request. The parsed url
|
||||||
|
/// has functions to inspect the parts of the url further.
|
||||||
|
///
|
||||||
|
/// The url might differ from that originally provided when constructing the
|
||||||
|
/// request if additional query parameters have been added using [`Request::query()`].
|
||||||
|
///
|
||||||
|
/// Returns a `Result` since a common use case is to construct
|
||||||
|
/// the [`Request`] using a `&str` in which case the url needs to be parsed
|
||||||
|
/// to inspect the parts. If the Request url is not possible to parse, this
|
||||||
|
/// function produces the same error that would otherwise happen when
|
||||||
|
/// `call` or `send_*` is called.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() -> Result<(), ureq::Error> {
|
||||||
|
/// # ureq::is_test(true);
|
||||||
|
/// let req = ureq::get("http://httpbin.org/get")
|
||||||
|
/// .query("foo", "bar");
|
||||||
|
///
|
||||||
|
/// assert_eq!(req.request_url().unwrap().host(), "httpbin.org");
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn request_url(&self) -> Result<RequestUrl> {
|
||||||
|
Ok(RequestUrl::new(self.parse_url()?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parsed result of a request url with handy inspection methods.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RequestUrl {
|
||||||
|
url: Url,
|
||||||
|
query_pairs: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestUrl {
|
||||||
|
fn new(url: Url) -> Self {
|
||||||
|
// This is needed to avoid url::Url Cow<str>. We want ureq API to work with &str.
|
||||||
|
let query_pairs = url
|
||||||
|
.query_pairs()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
RequestUrl { url, query_pairs }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle the request url as a standard [`url::Url`].
|
||||||
|
pub fn as_url(&self) -> &Url {
|
||||||
|
&self.url
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the scheme of the request url, i.e. "https" or "http".
|
||||||
|
pub fn scheme(&self) -> &str {
|
||||||
|
self.url.scheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Host of the request url.
|
||||||
|
pub fn host(&self) -> &str {
|
||||||
|
// this unwrap() is ok, because RequestUrl is tested for empty host
|
||||||
|
// urls in Request::parse_url().
|
||||||
|
self.url.host_str().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Port of the request url, if available. Ports are only available if they
|
||||||
|
/// are present in the original url. Specifically the scheme default ports,
|
||||||
|
/// 443 for `https` and and 80 for `http` are `None` unless explicitly
|
||||||
|
/// set in the url, i.e. `https://my-host.com:443/some/path`.
|
||||||
|
pub fn port(&self) -> Option<u16> {
|
||||||
|
self.url.port()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Path of the request url.
|
||||||
|
pub fn path(&self) -> &str {
|
||||||
|
self.url.path()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns all query parameters as a vector of key-value pairs.
|
/// Returns all query parameters as a vector of key-value pairs.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@@ -377,23 +475,18 @@ impl Request {
|
|||||||
/// .query("foo", "42")
|
/// .query("foo", "42")
|
||||||
/// .query("foo", "43");
|
/// .query("foo", "43");
|
||||||
///
|
///
|
||||||
/// assert_eq!(req.query_params(), vec![
|
/// assert_eq!(req.request_url().unwrap().query_pairs(), vec![
|
||||||
/// ("foo".to_string(), "42".to_string()),
|
/// ("foo", "42"),
|
||||||
/// ("foo".to_string(), "43".to_string())
|
/// ("foo", "43")
|
||||||
/// ]);
|
/// ]);
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn query_params(&self) -> Vec<(String, String)> {
|
pub fn query_pairs(&self) -> Vec<(&str, &str)> {
|
||||||
let mut ret = vec![];
|
self.query_pairs
|
||||||
|
.iter()
|
||||||
if let Ok(url) = &self.parsed_url.0 {
|
.map(|(k, v)| (k.as_str(), v.as_str()))
|
||||||
for (k, v) in url.query_pairs() {
|
.collect()
|
||||||
ret.push((k.into(), v.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,4 +516,17 @@ mod tests {
|
|||||||
.send(&bytes[1..2])
|
.send(&bytes[1..2])
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn disallow_empty_host() {
|
||||||
|
let req = crate::agent().get("file:///some/path");
|
||||||
|
|
||||||
|
// Both request_url and call() must surface the same error.
|
||||||
|
assert_eq!(
|
||||||
|
req.request_url().unwrap_err().kind(),
|
||||||
|
crate::ErrorKind::InvalidUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(req.call().unwrap_err().kind(), crate::ErrorKind::InvalidUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,7 +229,8 @@ fn connect_inner(
|
|||||||
let host = unit
|
let host = unit
|
||||||
.url
|
.url
|
||||||
.host_str()
|
.host_str()
|
||||||
.ok_or_else(|| ErrorKind::InvalidUrl.msg("no host in URL"))?;
|
// This unwrap is ok because Request::parse_url() ensure there is always a host present.
|
||||||
|
.unwrap();
|
||||||
let url = &unit.url;
|
let url = &unit.url;
|
||||||
let method = &unit.method;
|
let method = &unit.method;
|
||||||
// open socket
|
// open socket
|
||||||
|
|||||||
Reference in New Issue
Block a user