API changes for 2.0
* Remove Request::build * All mutations on Request follow builder pattern The previous `build()` on request was necessary because mutating functions did not follow a proper builder pattern (taking `&mut self` instead of `mut self`). With a proper builder pattern, the need for `.build()` goes away. * All Request body and call methods consume self Anything which "executes" the request will now consume the `Request` to produce a `Result<Response>`. * Move all config from request to agent builder Timeouts, redirect config, proxy settings and TLS config are now on `AgentBuilder`. * Rename max_pool_connections -> max_idle_connections * Rename max_pool_connections_per_host -> max_idle_connections_per_host Consistent internal and external naming. * Introduce new AgentConfig for static config created by builder. `Agent` can be seen as having two parts. Static config and a mutable shared state between all states. The static config goes into `AgentConfig` and the mutable shared state into `AgentState`. * Replace all use of `Default` for `new`. Deriving or implementing `Default` makes for a secondary instantiation API. It is useful in some cases, but gets very confusing when there is both `new` _and_ a `Default`. It's especially devious for derived values where a reasonable default is not `0`, `false` or `None`. * Remove feature native_tls, we want only native rustls. This feature made for very clunky handling throughout the code. From a security point of view, it's better to stick with one single TLS API. Rustls recently got an official audit (very positive). https://github.com/ctz/rustls/tree/master/audit Rustls deliberately omits support for older, insecure TLS such as TLS 1.1 or RC4. This might be a problem for a user of ureq, but on balance not considered important enough to keep native_tls. * Remove auth and support for basic auth. The API just wasn't enough. A future reintroduction should at least also provide a `Bearer` mechanism and possibly more. * Rename jar -> cookie_store * Rename jar -> cookie_tin Just make some field names sync up with the type. * Drop "cookies" as default feature The need for handling cookies is probably rare, let's not enable it by default. * Change all feature checks for "cookie" to "cookies" The outward facing feature is "cookies" and I think it's better form that the code uses the official feature name instead of the optional library "cookies". * Keep `set` on Agent level as well as AgentBuilder. The idea is that an auth exchange might result in a header that need to be set _after_ the agent has been built.
This commit is contained in:
316
src/request.rs
316
src/request.rs
@@ -1,13 +1,10 @@
|
||||
use std::fmt;
|
||||
use std::io::Read;
|
||||
#[cfg(any(feature = "native-tls", feature = "tls"))]
|
||||
use std::sync::Arc;
|
||||
use std::time;
|
||||
|
||||
use qstring::QString;
|
||||
use url::{form_urlencoded, Url};
|
||||
|
||||
use crate::agent::{self, Agent};
|
||||
use crate::agent::Agent;
|
||||
use crate::body::BodySize;
|
||||
use crate::body::{Payload, SizedReader};
|
||||
use crate::error::Error;
|
||||
@@ -30,28 +27,14 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
/// .query("foo", "bar baz") // add ?foo=bar%20baz
|
||||
/// .call(); // run the request
|
||||
/// ```
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct Request {
|
||||
pub(crate) agent: Agent,
|
||||
|
||||
// via agent
|
||||
pub(crate) method: String,
|
||||
url: String,
|
||||
|
||||
// from request itself
|
||||
return_error_for_status: bool,
|
||||
pub(crate) headers: Vec<Header>,
|
||||
pub(crate) query: QString,
|
||||
pub(crate) timeout_connect: Option<time::Duration>,
|
||||
pub(crate) timeout_read: Option<time::Duration>,
|
||||
pub(crate) timeout_write: Option<time::Duration>,
|
||||
pub(crate) timeout: Option<time::Duration>,
|
||||
pub(crate) redirects: u32,
|
||||
pub(crate) proxy: Option<Proxy>,
|
||||
#[cfg(feature = "tls")]
|
||||
pub(crate) tls_config: Option<TLSClientConfig>,
|
||||
#[cfg(all(feature = "native-tls", not(feature = "tls")))]
|
||||
pub(crate) tls_connector: Option<TLSConnector>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Request {
|
||||
@@ -72,43 +55,32 @@ impl fmt::Debug for 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 {
|
||||
let headers = agent.headers.clone();
|
||||
Request {
|
||||
agent: agent.clone(),
|
||||
agent,
|
||||
method,
|
||||
url,
|
||||
headers: agent.headers.clone(),
|
||||
redirects: 5,
|
||||
headers,
|
||||
return_error_for_status: true,
|
||||
..Default::default()
|
||||
query: QString::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// "Builds" this request which is effectively the same as cloning.
|
||||
/// This is needed when we use a chain of request builders, but
|
||||
/// don't want to send the request at the end of the chain.
|
||||
///
|
||||
/// ```
|
||||
/// let r = ureq::get("/my_page")
|
||||
/// .set("X-Foo-Bar", "Baz")
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn build(&self) -> Request {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
/// Executes the request and blocks the caller until done.
|
||||
///
|
||||
/// Use `.timeout_connect()` and `.timeout_read()` to avoid blocking forever.
|
||||
///
|
||||
/// ```
|
||||
/// let r = ureq::get("/my_page")
|
||||
/// let r = ureq::builder()
|
||||
/// .timeout_connect(std::time::Duration::from_secs(10)) // max 10 seconds
|
||||
/// .build()
|
||||
/// .get("/my_page")
|
||||
/// .call();
|
||||
///
|
||||
/// println!("{:?}", r);
|
||||
/// ```
|
||||
pub fn call(&mut self) -> Result<Response> {
|
||||
pub fn call(self) -> Result<Response> {
|
||||
self.do_call(Payload::Empty)
|
||||
}
|
||||
|
||||
@@ -146,11 +118,12 @@ impl Request {
|
||||
/// }
|
||||
/// ```
|
||||
#[cfg(feature = "json")]
|
||||
pub fn send_json(&mut self, data: SerdeValue) -> Result<Response> {
|
||||
if self.header("Content-Type").is_none() {
|
||||
self.set("Content-Type", "application/json");
|
||||
pub fn send_json(self, data: SerdeValue) -> Result<Response> {
|
||||
let mut this = self;
|
||||
if this.header("Content-Type").is_none() {
|
||||
this = this.set("Content-Type", "application/json");
|
||||
}
|
||||
self.do_call(Payload::JSON(data))
|
||||
this.do_call(Payload::JSON(data))
|
||||
}
|
||||
|
||||
/// Send data as bytes.
|
||||
@@ -163,7 +136,7 @@ impl Request {
|
||||
/// .send_bytes(body);
|
||||
/// println!("{:?}", r);
|
||||
/// ```
|
||||
pub fn send_bytes(&mut self, data: &[u8]) -> Result<Response> {
|
||||
pub fn send_bytes(self, data: &[u8]) -> Result<Response> {
|
||||
self.do_call(Payload::Bytes(data.to_owned()))
|
||||
}
|
||||
|
||||
@@ -188,7 +161,7 @@ impl Request {
|
||||
/// .send_string("Hällo Wörld!");
|
||||
/// println!("{:?}", r);
|
||||
/// ```
|
||||
pub fn send_string(&mut self, data: &str) -> Result<Response> {
|
||||
pub fn send_string(self, data: &str) -> Result<Response> {
|
||||
let text = data.into();
|
||||
let charset =
|
||||
crate::response::charset_from_content_type(self.header("content-type")).to_string();
|
||||
@@ -210,14 +183,15 @@ impl Request {
|
||||
/// println!("{:?}", r);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn send_form(&mut self, data: &[(&str, &str)]) -> Result<Response> {
|
||||
if self.header("Content-Type").is_none() {
|
||||
self.set("Content-Type", "application/x-www-form-urlencoded");
|
||||
pub fn send_form(self, data: &[(&str, &str)]) -> Result<Response> {
|
||||
let mut this = self;
|
||||
if this.header("Content-Type").is_none() {
|
||||
this = this.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()))
|
||||
this.do_call(Payload::Bytes(encoded.into_bytes()))
|
||||
}
|
||||
|
||||
/// Send data from a reader.
|
||||
@@ -238,7 +212,7 @@ impl Request {
|
||||
/// .set("Content-Type", "text/plain")
|
||||
/// .send(read);
|
||||
/// ```
|
||||
pub fn send(&mut self, reader: impl Read + 'static) -> Result<Response> {
|
||||
pub fn send(self, reader: impl Read + 'static) -> Result<Response> {
|
||||
self.do_call(Payload::Reader(Box::new(reader)))
|
||||
}
|
||||
|
||||
@@ -256,7 +230,7 @@ impl Request {
|
||||
/// println!("Oh no error!");
|
||||
/// }
|
||||
/// ```
|
||||
pub fn set(&mut self, header: &str, value: &str) -> &mut Request {
|
||||
pub fn set(mut self, header: &str, value: &str) -> Self {
|
||||
header::add_header(&mut self.headers, Header::new(header, value));
|
||||
self
|
||||
}
|
||||
@@ -265,8 +239,7 @@ impl Request {
|
||||
///
|
||||
/// ```
|
||||
/// let req = ureq::get("/my_page")
|
||||
/// .set("X-API-Key", "foobar")
|
||||
/// .build();
|
||||
/// .set("X-API-Key", "foobar");
|
||||
/// assert_eq!("foobar", req.header("x-api-Key").unwrap());
|
||||
/// ```
|
||||
pub fn header(&self, name: &str) -> Option<&str> {
|
||||
@@ -278,8 +251,7 @@ impl Request {
|
||||
/// ```
|
||||
/// let req = ureq::get("/my_page")
|
||||
/// .set("X-API-Key", "foobar")
|
||||
/// .set("Content-Type", "application/json")
|
||||
/// .build();
|
||||
/// .set("Content-Type", "application/json");
|
||||
/// assert_eq!(req.header_names(), vec!["x-api-key", "content-type"]);
|
||||
/// ```
|
||||
pub fn header_names(&self) -> Vec<String> {
|
||||
@@ -293,8 +265,7 @@ impl Request {
|
||||
///
|
||||
/// ```
|
||||
/// let req = ureq::get("/my_page")
|
||||
/// .set("X-API-Key", "foobar")
|
||||
/// .build();
|
||||
/// .set("X-API-Key", "foobar");
|
||||
/// assert_eq!(true, req.has("x-api-Key"));
|
||||
/// ```
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
@@ -306,8 +277,8 @@ impl Request {
|
||||
/// ```
|
||||
/// let req = ureq::get("/my_page")
|
||||
/// .set("X-Forwarded-For", "1.2.3.4")
|
||||
/// .set("X-Forwarded-For", "2.3.4.5")
|
||||
/// .build();
|
||||
/// .set("X-Forwarded-For", "2.3.4.5");
|
||||
///
|
||||
/// assert_eq!(req.all("x-forwarded-for"), vec.
|
||||
///
|
||||
/// See [`ClientConfig`](https://docs.rs/rustls/latest/rustls/struct.ClientConfig.html).
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// let tls_config = std::sync::Arc::new(rustls::ClientConfig::new());
|
||||
/// let req = ureq::post("https://cool.server")
|
||||
/// .set_tls_config(tls_config.clone());
|
||||
/// ```
|
||||
#[cfg(feature = "tls")]
|
||||
pub fn set_tls_config(&mut self, tls_config: Arc<rustls::ClientConfig>) -> &mut Request {
|
||||
self.tls_config = Some(TLSClientConfig(tls_config));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the TLS connector that will be used for the connection.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// let tls_connector = std::sync::Arc::new(native_tls::TlsConnector::new().unwrap());
|
||||
/// let req = ureq::post("https://cool.server")
|
||||
/// .set_tls_connector(tls_connector.clone());
|
||||
/// ```
|
||||
#[cfg(all(feature = "native-tls", not(feature = "tls")))]
|
||||
pub fn set_tls_connector(
|
||||
&mut self,
|
||||
tls_connector: Arc<native_tls::TlsConnector>,
|
||||
) -> &mut Request {
|
||||
self.tls_connector = Some(TLSConnector(tls_connector));
|
||||
self
|
||||
}
|
||||
|
||||
// Returns true if this request, with the provided body, is retryable.
|
||||
pub(crate) fn is_retryable(&self, body: &SizedReader) -> bool {
|
||||
// Per https://tools.ietf.org/html/rfc7231#section-8.1.3
|
||||
@@ -660,32 +454,10 @@ impl Request {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TLSClientConfig(pub(crate) Arc<rustls::ClientConfig>);
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
impl fmt::Debug for TLSClientConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TLSClientConfig").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "native-tls", not(feature = "tls")))]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TLSConnector(pub(crate) Arc<native_tls::TlsConnector>);
|
||||
|
||||
#[cfg(all(feature = "native-tls", not(feature = "tls")))]
|
||||
impl fmt::Debug for TLSConnector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("TLSConnector").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_hostname() {
|
||||
let req = Request::new(
|
||||
&Agent::default(),
|
||||
Agent::new(),
|
||||
"GET".to_string(),
|
||||
"unix:/run/foo.socket".to_string(),
|
||||
);
|
||||
@@ -695,12 +467,12 @@ fn no_hostname() {
|
||||
#[test]
|
||||
fn request_implements_send_and_sync() {
|
||||
let _request: Box<dyn Send> = Box::new(Request::new(
|
||||
&Agent::default(),
|
||||
Agent::new(),
|
||||
"GET".to_string(),
|
||||
"https://example.com/".to_string(),
|
||||
));
|
||||
let _request: Box<dyn Sync> = Box::new(Request::new(
|
||||
&Agent::default(),
|
||||
Agent::new(),
|
||||
"GET".to_string(),
|
||||
"https://example.com/".to_string(),
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user