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,
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum RedirectAuthHeaders {
Never,
SameHost,
}
pub struct AgentBuilder {
config: AgentConfig,
max_idle_connections: usize,
max_idle_connections_per_host: usize,
#[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
}
}
#[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,
}
#[derive(Debug, Clone)]
pub struct Agent {
pub(crate) config: Arc<AgentConfig>,
pub(crate) state: Arc<AgentState>,
}
pub(crate) struct AgentState {
pub(crate) pool: ConnectionPool,
#[cfg(feature = "cookies")]
pub(crate) cookie_tin: CookieTin,
pub(crate) resolver: ArcResolver,
pub(crate) middleware: Vec<Box<dyn Middleware>>,
}
impl Agent {
pub fn new() -> Self {
AgentBuilder::new().build()
}
pub fn request(&self, method: &str, path: &str) -> Request {
Request::new(self.clone(), method.into(), path.into())
}
pub fn request_url(&self, method: &str, url: &Url) -> Request {
Request::new(self.clone(), method.into(), url.to_string())
}
pub fn get(&self, path: &str) -> Request {
self.request("GET", path)
}
pub fn head(&self, path: &str) -> Request {
self.request("HEAD", path)
}
pub fn patch(&self, path: &str) -> Request {
self.request("PATCH", path)
}
pub fn post(&self, path: &str) -> Request {
self.request("POST", path)
}
pub fn put(&self, path: &str) -> Request {
self.request("PUT", path)
}
pub fn delete(&self, path: &str) -> Request {
self.request("DELETE", path)
}
#[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![],
}
}
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,
}),
}
}
pub fn proxy(mut self, proxy: Proxy) -> Self {
self.config.proxy = Some(proxy);
self
}
pub fn https_only(mut self, enforce: bool) -> Self {
self.config.https_only = enforce;
self
}
pub fn max_idle_connections(mut self, max: usize) -> Self {
self.max_idle_connections = max;
self
}
pub fn max_idle_connections_per_host(mut self, max: usize) -> Self {
self.max_idle_connections_per_host = max;
self
}
pub fn resolver(mut self, resolver: impl crate::Resolver + 'static) -> Self {
self.resolver = resolver.into();
self
}
pub fn timeout_connect(mut self, timeout: Duration) -> Self {
self.config.timeout_connect = Some(timeout);
self
}
pub fn timeout_read(mut self, timeout: Duration) -> Self {
self.config.timeout_read = Some(timeout);
self
}
pub fn timeout_write(mut self, timeout: Duration) -> Self {
self.config.timeout_write = Some(timeout);
self
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.config.timeout = Some(timeout);
self
}
pub fn no_delay(mut self, no_delay: bool) -> Self {
self.config.no_delay = no_delay;
self
}
pub fn redirects(mut self, n: u32) -> Self {
self.config.redirects = n;
self
}
pub fn redirect_auth_headers(mut self, v: RedirectAuthHeaders) -> Self {
self.config.redirect_auth_headers = v;
self
}
pub fn user_agent(mut self, user_agent: &str) -> Self {
self.config.user_agent = user_agent.into();
self
}
#[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
}
pub fn tls_connector<T: TlsConnector + 'static>(mut self, tls_config: Arc<T>) -> Self {
self.config.tls_config = TlsConfig(tls_config);
self
}
#[cfg(feature = "cookies")]
pub fn cookie_store(mut self, cookie_store: CookieStore) -> Self {
self.cookie_store = Some(cookie_store);
self
}
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)
.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)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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:"));
}
}