use std::fmt;
use std::ops::Deref;
use std::sync::Arc;
use std::time::Duration;
use url::Url;
use crate::middleware::Middleware;
use crate::pool::ConnectionPool;
use crate::proxy::Proxy;
use crate::request::Request;
use crate::resolve::{ArcResolver, StdResolver};
use crate::stream::TlsConnector;
#[cfg(feature = "cookies")]
use {
crate::cookies::{CookieStoreGuard, CookieTin},
cookie_store::CookieStore,
};
/// Strategy for keeping `authorization` headers during redirects.
///
/// `Never` is the default strategy and never preserves `authorization` header in redirects.
/// `SameHost` send the authorization header in redirects only if the host of the redirect is
/// the same of the previous request, and both use the same scheme (or switch to a more secure one, i.e
/// we can redirect from `http` to `https`, but not the reverse).
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RedirectAuthHeaders {
/// Never preserve the `authorization` header on redirect. This is the default.
Never,
/// Preserve the `authorization` header when the redirect is to the same host. Both hosts must use
/// the same scheme (or switch to a more secure one, i.e we can redirect from `http` to `https`,
/// but not the reverse).
SameHost,
}
/// Accumulates options towards building an [Agent].
pub struct AgentBuilder {
config: AgentConfig,
max_idle_connections: usize,
max_idle_connections_per_host: usize,
/// Cookies saved between requests.
/// Invariant: All cookies must have a nonempty domain and path.
#[cfg(feature = "cookies")]
cookie_store: Option<CookieStore>,
resolver: ArcResolver,
middleware: Vec<Box<dyn Middleware>>,
}
#[derive(Clone)]
pub(crate) struct TlsConfig(Arc<dyn TlsConnector>);
impl fmt::Debug for TlsConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TlsConfig").finish()
}
}
impl Deref for TlsConfig {
type Target = Arc<dyn TlsConnector>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// Config as built by AgentBuilder and then static for the lifetime of the Agent.
#[derive(Clone, Debug)]
pub(crate) struct AgentConfig {
pub proxy: Option<Proxy>,
pub timeout_connect: Option<Duration>,
pub timeout_read: Option<Duration>,
pub timeout_write: Option<Duration>,
pub timeout: Option<Duration>,
pub https_only: bool,
pub no_delay: bool,
pub redirects: u32,
pub redirect_auth_headers: RedirectAuthHeaders,
pub user_agent: String,
pub tls_config: TlsConfig,
}
/// Agents keep state between requests.
///
/// By default, no state, such as cookies, is kept between requests.
/// But by creating an agent as entry point for the request, we
/// can keep a state.
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let mut agent = ureq::agent();
///
/// agent
/// .post("http://example.com/login")
/// .call()?;
///
/// let secret = agent
/// .get("http://example.com/my-protected-page")
/// .call()?
/// .into_string()?;
///
/// println!("Secret is: {}", secret);
/// # Ok(())
/// # }
/// ```
///
/// Agent uses an inner Arc, so cloning an Agent results in an instance
/// that shares the same underlying connection pool and other state.
#[derive(Debug, Clone)]
pub struct Agent {
pub(crate) config: Arc<AgentConfig>,
/// Reused agent state for repeated requests from this agent.
pub(crate) state: Arc<AgentState>,
}
/// Container of the state
///
/// *Internal API*.
pub(crate) struct AgentState {
/// Reused connections between requests.
pub(crate) pool: ConnectionPool,
/// Cookies saved between requests.
/// Invariant: All cookies must have a nonempty domain and path.
#[cfg(feature = "cookies")]
pub(crate) cookie_tin: CookieTin,
pub(crate) resolver: ArcResolver,
pub(crate) middleware: Vec<Box<dyn Middleware>>,
}
impl Agent {
/// Creates an Agent with default settings.
///
/// Same as `AgentBuilder::new().build()`.
pub fn new() -> Self {
AgentBuilder::new().build()
}
/// Make a request with the HTTP verb as a parameter.
///
/// This allows making requests with verbs that don't have a dedicated
/// method.
///
/// If you've got an already-parsed [Url], try [request_url][Agent::request_url].
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// use ureq::Response;
/// let agent = ureq::agent();
///
/// let resp: Response = agent
/// .request("OPTIONS", "http://example.com/")
/// .call()?;
/// # Ok(())
/// # }
/// ```
pub fn request(&self, method: &str, path: &str) -> Request {
Request::new(self.clone(), method.into(), path.into())
}
/// Make a request using an already-parsed [Url].
///
/// This is useful if you've got a parsed Url from some other source, or if
/// you want to parse the URL and then modify it before making the request.
/// If you'd just like to pass a String or a `&str`, try [request][Agent::request].
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// use {url::Url, ureq::Response};
/// let agent = ureq::agent();
///
/// let mut url: Url = "http://example.com/some-page".parse()?;
/// url.set_path("/robots.txt");
/// let resp: Response = agent
/// .request_url("GET", &url)
/// .call()?;
/// # Ok(())
/// # }
/// ```
pub fn request_url(&self, method: &str, url: &Url) -> Request {
Request::new(self.clone(), method.into(), url.to_string())
}
/// Make a GET request from this agent.
pub fn get(&self, path: &str) -> Request {
self.request("GET", path)
}
/// Make a HEAD request from this agent.
pub fn head(&self, path: &str) -> Request {
self.request("HEAD", path)
}
/// Make a PATCH request from this agent.
pub fn patch(&self, path: &str) -> Request {
self.request("PATCH", path)
}
/// Make a POST request from this agent.
pub fn post(&self, path: &str) -> Request {
self.request("POST", path)
}
/// Make a PUT request from this agent.
pub fn put(&self, path: &str) -> Request {
self.request("PUT", path)
}
/// Make a DELETE request from this agent.
pub fn delete(&self, path: &str) -> Request {
self.request("DELETE", path)
}
/// Read access to the cookie store.
///
/// Used to persist the cookies to an external writer.
///
/// ```no_run
/// use std::io::Write;
/// use std::fs::File;
///
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let agent = ureq::agent();
///
/// // Cookies set by www.google.com are stored in agent.
/// agent.get("https://www.google.com/").call()?;
///
/// // Saves (persistent) cookies
/// let mut file = File::create("cookies.json")?;
/// agent.cookie_store().save_json(&mut file).unwrap();
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "cookies")]
pub fn cookie_store(&self) -> CookieStoreGuard<'_> {
self.state.cookie_tin.read_lock()
}
pub(crate) fn weak_state(&self) -> std::sync::Weak<AgentState> {
Arc::downgrade(&self.state)
}
}
const DEFAULT_MAX_IDLE_CONNECTIONS: usize = 100;
const DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST: usize = 1;
impl AgentBuilder {
pub fn new() -> Self {
AgentBuilder {
config: AgentConfig {
proxy: None,
timeout_connect: Some(Duration::from_secs(30)),
timeout_read: None,
timeout_write: None,
timeout: None,
https_only: false,
no_delay: true,
redirects: 5,
redirect_auth_headers: RedirectAuthHeaders::Never,
user_agent: format!("ureq/{}", env!("CARGO_PKG_VERSION")),
tls_config: TlsConfig(crate::default_tls_config()),
},
max_idle_connections: DEFAULT_MAX_IDLE_CONNECTIONS,
max_idle_connections_per_host: DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST,
resolver: StdResolver.into(),
#[cfg(feature = "cookies")]
cookie_store: None,
middleware: vec![],
}
}
/// Create a new agent.
// Note: This could take &self as the first argument, allowing one
// AgentBuilder to be used multiple times, except CookieStore does
// not implement clone, so we have to give ownership to the newly
// built Agent.
pub fn build(self) -> Agent {
Agent {
config: Arc::new(self.config),
state: Arc::new(AgentState {
pool: ConnectionPool::new_with_limits(
self.max_idle_connections,
self.max_idle_connections_per_host,
),
#[cfg(feature = "cookies")]
cookie_tin: CookieTin::new(self.cookie_store.unwrap_or_else(CookieStore::default)),
resolver: self.resolver,
middleware: self.middleware,
}),
}
}
/// Set the proxy server to use for all connections from this Agent.
///
/// Example:
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let proxy = ureq::Proxy::new("user:password@cool.proxy:9090")?;
/// let agent = ureq::AgentBuilder::new()
/// .proxy(proxy)
/// .build();
/// # Ok(())
/// # }
/// ```
pub fn proxy(mut self, proxy: Proxy) -> Self {
self.config.proxy = Some(proxy);
self
}
/// Enforce the client to only perform HTTPS requests.
/// This setting also makes the client refuse HTTPS to HTTP redirects.
/// Default is false
///
/// Example:
/// ```
/// let agent = ureq::AgentBuilder::new()
/// .https_only(true)
/// .build();
/// ```
pub fn https_only(mut self, enforce: bool) -> Self {
self.config.https_only = enforce;
self
}
/// Sets the maximum number of connections allowed in the connection pool.
/// By default, this is set to 100. Setting this to zero would disable
/// connection pooling.
///
/// ```
/// let agent = ureq::AgentBuilder::new()
/// .max_idle_connections(200)
/// .build();
/// ```
pub fn max_idle_connections(mut self, max: usize) -> Self {
self.max_idle_connections = max;
self
}
/// Sets the maximum number of connections per host to keep in the
/// connection pool. By default, this is set to 1. Setting this to zero
/// would disable connection pooling.
///
/// ```
/// let agent = ureq::AgentBuilder::new()
/// .max_idle_connections_per_host(200)
/// .build();
/// ```
pub fn max_idle_connections_per_host(mut self, max: usize) -> Self {
self.max_idle_connections_per_host = max;
self
}
/// Configures a custom resolver to be used by this agent. By default,
/// address-resolution is done by std::net::ToSocketAddrs. This allows you
/// to override that resolution with your own alternative. Useful for
/// testing and special-cases like DNS-based load balancing.
///
/// A `Fn(&str) -> io::Result<Vec<SocketAddr>>` is a valid resolver,
/// passing a closure is a simple way to override. Note that you might need
/// explicit type `&str` on the closure argument for type inference to
/// succeed.
/// ```
/// use std::net::ToSocketAddrs;
///
/// let mut agent = ureq::AgentBuilder::new()
/// .resolver(|addr: &str| match addr {
/// "example.com" => Ok(vec![([127,0,0,1], 8096).into()]),
/// addr => addr.to_socket_addrs().map(Iterator::collect),
/// })
/// .build();
/// ```
pub fn resolver(mut self, resolver: impl crate::Resolver + 'static) -> Self {
self.resolver = resolver.into();
self
}
/// Timeout for the socket connection to be successful.
/// If both this and `.timeout()` are both set, `.timeout_connect()`
/// takes precedence.
///
/// The default is 30 seconds.
///
/// ```
/// use std::time::Duration;
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let agent = ureq::builder()
/// .timeout_connect(Duration::from_secs(1))
/// .build();
/// let result = agent.get("http://httpbin.org/delay/20").call();
/// # Ok(())
/// # }
/// ```
pub fn timeout_connect(mut self, timeout: Duration) -> Self {
self.config.timeout_connect = Some(timeout);
self
}
/// Timeout for the individual reads of the socket.
/// If both this and `.timeout()` are both set, `.timeout()`
/// takes precedence.
///
/// The default is no timeout. In other words, requests may block forever on reads by default.
///
/// ```
/// use std::time::Duration;
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let agent = ureq::builder()
/// .timeout_read(Duration::from_secs(1))
/// .build();
/// let result = agent.get("http://httpbin.org/delay/20").call();
/// # Ok(())
/// # }
/// ```
pub fn timeout_read(mut self, timeout: Duration) -> Self {
self.config.timeout_read = Some(timeout);
self
}
/// Timeout for the individual writes to the socket.
/// If both this and `.timeout()` are both set, `.timeout()`
/// takes precedence.
///
/// The default is no timeout. In other words, requests may block forever on writes by default.
///
/// ```
/// use std::time::Duration;
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let agent = ureq::builder()
/// .timeout_read(Duration::from_secs(1))
/// .build();
/// let result = agent.get("http://httpbin.org/delay/20").call();
/// # Ok(())
/// # }
/// ```
pub fn timeout_write(mut self, timeout: Duration) -> Self {
self.config.timeout_write = Some(timeout);
self
}
/// Timeout for the overall request, including DNS resolution, connection
/// time, redirects, and reading the response body. Slow DNS resolution
/// may cause a request to exceed the timeout, because the DNS request
/// cannot be interrupted with the available APIs.
///
/// This takes precedence over `.timeout_read()` and `.timeout_write()`, but
/// not `.timeout_connect()`.
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// // wait max 1 second for whole request to complete.
/// let agent = ureq::builder()
/// .timeout(std::time::Duration::from_secs(1))
/// .build();
/// let result = agent.get("http://httpbin.org/delay/20").call();
/// # Ok(())
/// # }
/// ```
pub fn timeout(mut self, timeout: Duration) -> Self {
self.config.timeout = Some(timeout);
self
}
/// Whether no_delay will be set on the tcp socket.
/// Setting this to true disables Nagle's algorithm.
///
/// Defaults to true.
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let agent = ureq::builder()
/// .no_delay(false)
/// .build();
/// let result = agent.get("http://httpbin.org/get").call();
/// # Ok(())
/// # }
/// ```
pub fn no_delay(mut self, no_delay: bool) -> Self {
self.config.no_delay = no_delay;
self
}
/// How many redirects to follow.
///
/// Defaults to `5`. Set to `0` to avoid redirects and instead
/// get a response object with the 3xx status code.
///
/// If the redirect count hits this limit (and it's > 0), TooManyRedirects is returned.
///
/// WARNING: for 307 and 308 redirects, this value is ignored for methods that have a body.
/// You must handle 307 redirects yourself when sending a PUT, POST, PATCH, or DELETE request.
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let result = ureq::builder()
/// .redirects(1)
/// .build()
/// # ;
/// # let result = ureq::agent()
/// .get("http://httpbin.org/status/301")
/// .call()?;
/// assert_ne!(result.status(), 301);
///
/// let result = ureq::post("http://httpbin.org/status/307")
/// .send_bytes(b"some data")?;
/// assert_eq!(result.status(), 307);
/// # Ok(())
/// # }
/// ```
pub fn redirects(mut self, n: u32) -> Self {
self.config.redirects = n;
self
}
/// Set the strategy for propagation of authorization headers in redirects.
///
/// Defaults to [`RedirectAuthHeaders::Never`].
///
pub fn redirect_auth_headers(mut self, v: RedirectAuthHeaders) -> Self {
self.config.redirect_auth_headers = v;
self
}
/// The user-agent header to associate with all requests from this agent by default.
///
/// Defaults to `ureq/[VERSION]`. You can override the user-agent on an individual request by
/// setting the `User-Agent` header when constructing the request.
///
/// ```
/// # #[cfg(feature = "json")]
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// let agent = ureq::builder()
/// .user_agent("ferris/1.0")
/// .build();
///
/// // Uses agent's header
/// let result: serde_json::Value =
/// agent.get("http://httpbin.org/headers").call()?.into_json()?;
/// assert_eq!(&result["headers"]["User-Agent"], "ferris/1.0");
///
/// // Overrides user-agent set on the agent
/// let result: serde_json::Value = agent.get("http://httpbin.org/headers")
/// .set("User-Agent", "super-ferris/2.0")
/// .call()?.into_json()?;
/// assert_eq!(&result["headers"]["User-Agent"], "super-ferris/2.0");
/// # Ok(())
/// # }
/// # #[cfg(not(feature = "json"))]
/// # fn main() {}
/// ```
pub fn user_agent(mut self, user_agent: &str) -> Self {
self.config.user_agent = user_agent.into();
self
}
/// Configure TLS options for rustls to use when making HTTPS connections from this Agent.
///
/// This overrides any previous call to tls_config or tls_connector.
///
/// ```
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// use std::sync::Arc;
/// let mut root_store = rustls::RootCertStore::empty();
/// root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(|ta| {
/// rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
/// ta.subject,
/// ta.spki,
/// ta.name_constraints,
/// )
/// }));
///
/// let tls_config = rustls::ClientConfig::builder()
/// .with_safe_defaults()
/// .with_root_certificates(root_store)
/// .with_no_client_auth();
/// let agent = ureq::builder()
/// .tls_config(Arc::new(tls_config))
/// .build();
/// # Ok(())
/// # }
#[cfg(feature = "tls")]
pub fn tls_config(mut self, tls_config: Arc<rustls::ClientConfig>) -> Self {
self.config.tls_config = TlsConfig(Arc::new(tls_config));
self
}
/// Configure TLS options for a backend other than rustls. The parameter can be a
/// any type which implements the [`TlsConnector`] trait. If you enable the native-tls
/// feature, we provide `impl TlsConnector for native_tls::TlsConnector` so you can pass
/// [`Arc<native_tls::TlsConnector>`](https://docs.rs/native-tls/0.2.7/native_tls/struct.TlsConnector.html).
///
/// This overrides any previous call to tls_config or tls_connector.
///
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # ureq::is_test(true);
/// use std::sync::Arc;
/// # #[cfg(feature = "native-tls")]
/// let tls_connector = Arc::new(native_tls::TlsConnector::new()?);
/// # #[cfg(feature = "native-tls")]
/// let agent = ureq::builder()
/// .tls_connector(tls_connector.clone())
/// .build();
/// # Ok(())
/// # }
/// ```
pub fn tls_connector<T: TlsConnector + 'static>(mut self, tls_config: Arc<T>) -> Self {
self.config.tls_config = TlsConfig(tls_config);
self
}
/// Provide the cookie store to be used for all requests using this agent.
///
/// This is useful in two cases. First when there is a need to persist cookies
/// to some backing store, and second when there's a need to prepare the agent
/// with some pre-existing cookies.
///
/// Example
/// ```no_run
/// # fn main() -> Result<(), ureq::Error> {
/// # ureq::is_test(true);
/// use cookie_store::CookieStore;
/// use std::fs::File;
/// use std::io::BufReader;
/// let file = File::open("cookies.json")?;
/// let read = BufReader::new(file);
///
/// // Read persisted cookies from cookies.json
/// let my_store = CookieStore::load_json(read).unwrap();
///
/// // Cookies will be used for requests done through agent.
/// let agent = ureq::builder()
/// .cookie_store(my_store)
/// .build();
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "cookies")]
pub fn cookie_store(mut self, cookie_store: CookieStore) -> Self {
self.cookie_store = Some(cookie_store);
self
}
/// Add middleware handler to this agent.
///
/// All requests made by the agent will use this middleware. Middleware is invoked
/// in the order they are added to the builder.
pub fn middleware(mut self, m: impl Middleware) -> Self {
self.middleware.push(Box::new(m));
self
}
}
#[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()
}
}
impl fmt::Debug for AgentBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AgentBuilder")
.field("config", &self.config)
.field("max_idle_connections", &self.max_idle_connections)
.field(
"max_idle_connections_per_host",
&self.max_idle_connections_per_host,
)
.field("resolver", &self.resolver)
// self.cookies missing because it's feature flagged.
// self.middleware missing because we don't want to force Debug on Middleware trait.
.finish_non_exhaustive()
}
}
impl fmt::Debug for AgentState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AgentState")
.field("pool", &self.pool)
.field("resolver", &self.resolver)
// self.cookie_tin missing because it's feature flagged.
// self.middleware missing because we don't want to force Debug on Middleware trait.
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
///////////////////// AGENT TESTS //////////////////////////////
#[test]
fn agent_implements_send_and_sync() {
let _agent: Box<dyn Send> = Box::new(AgentBuilder::new().build());
let _agent: Box<dyn Sync> = Box::new(AgentBuilder::new().build());
}
#[test]
fn agent_config_debug() {
let agent = AgentBuilder::new().build();
let debug_format = format!("{:?}", agent);
assert!(debug_format.contains("Agent"));
assert!(debug_format.contains("config:"));
assert!(debug_format.contains("proxy:"));
assert!(debug_format.contains("timeout_connect:"));
assert!(debug_format.contains("timeout_read:"));
assert!(debug_format.contains("timeout_write:"));
assert!(debug_format.contains("timeout:"));
assert!(debug_format.contains("https_only:"));
assert!(debug_format.contains("no_delay:"));
assert!(debug_format.contains("redirects:"));
assert!(debug_format.contains("redirect_auth_headers:"));
assert!(debug_format.contains("user_agent:"));
assert!(debug_format.contains("tls_config:"));
assert!(debug_format.contains("state:"));
assert!(debug_format.contains("pool:"));
}
}