use std::fmt;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use http::Uri;
use crate::http;
use crate::middleware::{Middleware, MiddlewareChain};
use crate::{Agent, AsSendBody, Proxy, RequestBuilder};
#[cfg(feature = "_tls")]
use crate::tls::TlsConfig;
pub use ureq_proto::client::flow::RedirectAuthHeaders;
mod private {
use super::Config;
pub trait ConfigScope {
fn config(&mut self) -> &mut Config;
}
}
pub(crate) mod typestate {
use super::*;
pub struct AgentScope(pub(crate) Config);
pub struct RequestScope<Any>(pub(crate) RequestBuilder<Any>);
pub struct HttpCrateScope<S: AsSendBody>(pub(crate) http::Request<S>);
impl private::ConfigScope for AgentScope {
fn config(&mut self) -> &mut Config {
&mut self.0
}
}
impl<Any> private::ConfigScope for RequestScope<Any> {
fn config(&mut self) -> &mut Config {
self.0.request_level_config()
}
}
impl<S: AsSendBody> private::ConfigScope for HttpCrateScope<S> {
fn config(&mut self) -> &mut Config {
let req_level: &mut RequestLevelConfig = self.0.extensions_mut().get_mut().unwrap();
&mut req_level.0
}
}
}
use typestate::AgentScope;
use typestate::HttpCrateScope;
use typestate::RequestScope;
#[derive(Clone)]
pub struct Config {
http_status_as_error: bool,
https_only: bool,
ip_family: IpFamily,
#[cfg(feature = "_tls")]
tls_config: TlsConfig,
proxy: Option<Proxy>,
no_delay: bool,
max_redirects: u32,
max_redirects_will_error: bool,
redirect_auth_headers: RedirectAuthHeaders,
user_agent: AutoHeaderValue,
accept: AutoHeaderValue,
accept_encoding: AutoHeaderValue,
timeouts: Timeouts,
max_response_header_size: usize,
input_buffer_size: usize,
output_buffer_size: usize,
max_idle_connections: usize,
max_idle_connections_per_host: usize,
max_idle_age: Duration,
pub(crate) middleware: MiddlewareChain,
pub(crate) force_send_body: bool,
}
impl Config {
pub fn builder() -> ConfigBuilder<AgentScope> {
ConfigBuilder(AgentScope(Config::default()))
}
pub fn new_agent(&self) -> Agent {
self.clone().into()
}
pub(crate) fn connect_proxy_uri(&self) -> Option<&Uri> {
let proxy = self.proxy.as_ref()?;
if !proxy.proto().is_connect() {
return None;
}
Some(proxy.uri())
}
pub(crate) fn max_redirects_do_error(&self) -> bool {
self.max_redirects > 0 && self.max_redirects_will_error
}
}
impl Config {
pub fn http_status_as_error(&self) -> bool {
self.http_status_as_error
}
pub fn https_only(&self) -> bool {
self.https_only
}
pub fn ip_family(&self) -> IpFamily {
self.ip_family
}
#[cfg(feature = "_tls")]
pub fn tls_config(&self) -> &TlsConfig {
&self.tls_config
}
pub fn proxy(&self) -> Option<&Proxy> {
self.proxy.as_ref()
}
pub fn no_delay(&self) -> bool {
self.no_delay
}
pub fn max_redirects(&self) -> u32 {
self.max_redirects
}
pub fn max_redirects_will_error(&self) -> bool {
self.max_redirects_will_error
}
pub fn redirect_auth_headers(&self) -> RedirectAuthHeaders {
self.redirect_auth_headers
}
pub fn user_agent(&self) -> &AutoHeaderValue {
&self.user_agent
}
pub fn accept(&self) -> &AutoHeaderValue {
&self.accept
}
pub fn accept_encoding(&self) -> &AutoHeaderValue {
&self.accept_encoding
}
pub fn timeouts(&self) -> Timeouts {
self.timeouts
}
pub fn max_response_header_size(&self) -> usize {
self.max_response_header_size
}
pub fn input_buffer_size(&self) -> usize {
self.input_buffer_size
}
pub fn output_buffer_size(&self) -> usize {
self.output_buffer_size
}
pub fn max_idle_connections(&self) -> usize {
self.max_idle_connections
}
pub fn max_idle_connections_per_host(&self) -> usize {
self.max_idle_connections_per_host
}
pub fn max_idle_age(&self) -> Duration {
self.max_idle_age
}
}
pub struct ConfigBuilder<Scope: private::ConfigScope>(pub(crate) Scope);
impl<Scope: private::ConfigScope> ConfigBuilder<Scope> {
fn config(&mut self) -> &mut Config {
self.0.config()
}
pub fn http_status_as_error(mut self, v: bool) -> Self {
self.config().http_status_as_error = v;
self
}
pub fn https_only(mut self, v: bool) -> Self {
self.config().https_only = v;
self
}
pub fn ip_family(mut self, v: IpFamily) -> Self {
self.config().ip_family = v;
self
}
#[cfg(feature = "_tls")]
pub fn tls_config(mut self, v: TlsConfig) -> Self {
self.config().tls_config = v;
self
}
pub fn proxy(mut self, v: Option<Proxy>) -> Self {
self.config().proxy = v;
self
}
pub fn no_delay(mut self, v: bool) -> Self {
self.config().no_delay = v;
self
}
pub fn max_redirects(mut self, v: u32) -> Self {
self.config().max_redirects = v;
self
}
pub fn max_redirects_will_error(mut self, v: bool) -> Self {
self.config().max_redirects_will_error = v;
self
}
pub fn redirect_auth_headers(mut self, v: RedirectAuthHeaders) -> Self {
self.config().redirect_auth_headers = v;
self
}
pub fn user_agent(mut self, v: impl Into<AutoHeaderValue>) -> Self {
self.config().user_agent = v.into();
self
}
pub fn accept(mut self, v: impl Into<AutoHeaderValue>) -> Self {
self.config().accept = v.into();
self
}
pub fn accept_encoding(mut self, v: impl Into<AutoHeaderValue>) -> Self {
self.config().accept_encoding = v.into();
self
}
pub fn max_response_header_size(mut self, v: usize) -> Self {
self.config().max_response_header_size = v;
self
}
pub fn input_buffer_size(mut self, v: usize) -> Self {
self.config().input_buffer_size = v;
self
}
pub fn output_buffer_size(mut self, v: usize) -> Self {
self.config().output_buffer_size = v;
self
}
pub fn max_idle_connections(mut self, v: usize) -> Self {
self.config().max_idle_connections = v;
self
}
pub fn max_idle_connections_per_host(mut self, v: usize) -> Self {
self.config().max_idle_connections_per_host = v;
self
}
pub fn max_idle_age(mut self, v: Duration) -> Self {
self.config().max_idle_age = v;
self
}
pub fn middleware(mut self, v: impl Middleware) -> Self {
self.config().middleware.add(v);
self
}
pub fn timeout_global(mut self, v: Option<Duration>) -> Self {
self.config().timeouts.global = v;
self
}
pub fn timeout_per_call(mut self, v: Option<Duration>) -> Self {
self.config().timeouts.per_call = v;
self
}
pub fn timeout_resolve(mut self, v: Option<Duration>) -> Self {
self.config().timeouts.resolve = v;
self
}
pub fn timeout_connect(mut self, v: Option<Duration>) -> Self {
self.config().timeouts.connect = v;
self
}
pub fn timeout_send_request(mut self, v: Option<Duration>) -> Self {
self.config().timeouts.send_request = v;
self
}
pub fn timeout_await_100(mut self, v: Option<Duration>) -> Self {
self.config().timeouts.await_100 = v;
self
}
pub fn timeout_send_body(mut self, v: Option<Duration>) -> Self {
self.config().timeouts.send_body = v;
self
}
pub fn timeout_recv_response(mut self, v: Option<Duration>) -> Self {
self.config().timeouts.recv_response = v;
self
}
pub fn timeout_recv_body(mut self, v: Option<Duration>) -> Self {
self.config().timeouts.recv_body = v;
self
}
}
#[derive(Debug, Clone)]
pub enum AutoHeaderValue {
None,
Default,
Provided(Arc<String>),
}
impl Default for AutoHeaderValue {
fn default() -> Self {
Self::Default
}
}
impl AutoHeaderValue {
pub(crate) fn as_str(&self, default: &'static str) -> Option<&str> {
let x = match self {
AutoHeaderValue::None => "",
AutoHeaderValue::Default => default,
AutoHeaderValue::Provided(v) => v.as_str(),
};
if x.is_empty() {
None
} else {
Some(x)
}
}
}
impl<S: AsRef<str>> From<S> for AutoHeaderValue {
fn from(value: S) -> Self {
match value.as_ref() {
"" => Self::None,
_ => Self::Provided(Arc::new(value.as_ref().to_owned())),
}
}
}
impl ConfigBuilder<AgentScope> {
pub fn build(self) -> Config {
self.0 .0
}
}
impl<Any> ConfigBuilder<RequestScope<Any>> {
pub fn build(self) -> RequestBuilder<Any> {
self.0 .0
}
}
impl<S: AsSendBody> ConfigBuilder<HttpCrateScope<S>> {
pub fn build(self) -> http::Request<S> {
self.0 .0
}
}
#[derive(Clone, Copy)]
pub struct Timeouts {
pub global: Option<Duration>,
pub per_call: Option<Duration>,
pub resolve: Option<Duration>,
pub connect: Option<Duration>,
pub send_request: Option<Duration>,
pub await_100: Option<Duration>,
pub send_body: Option<Duration>,
pub recv_response: Option<Duration>,
pub recv_body: Option<Duration>,
}
#[derive(Debug, Clone)]
pub(crate) struct RequestLevelConfig(pub Config);
pub(crate) static DEFAULT_USER_AGENT: &str =
concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
impl Default for Config {
fn default() -> Self {
Self {
http_status_as_error: true,
https_only: false,
ip_family: IpFamily::Any,
#[cfg(feature = "_tls")]
tls_config: TlsConfig::default(),
proxy: Proxy::try_from_env(),
no_delay: true,
max_redirects: 10,
max_redirects_will_error: true,
redirect_auth_headers: RedirectAuthHeaders::Never,
user_agent: AutoHeaderValue::default(),
accept: AutoHeaderValue::default(),
accept_encoding: AutoHeaderValue::default(),
timeouts: Timeouts::default(),
max_response_header_size: 64 * 1024,
input_buffer_size: 128 * 1024,
output_buffer_size: 128 * 1024,
max_idle_connections: 10,
max_idle_connections_per_host: 3,
max_idle_age: Duration::from_secs(15),
middleware: MiddlewareChain::default(),
force_send_body: false,
}
}
}
impl Default for Timeouts {
fn default() -> Self {
Self {
global: None,
per_call: None,
resolve: None,
connect: None,
send_request: None,
await_100: Some(Duration::from_secs(1)),
send_body: None,
recv_response: None,
recv_body: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IpFamily {
Any,
Ipv4Only,
Ipv6Only,
}
impl IpFamily {
pub fn keep_wanted<'a>(
&'a self,
iter: impl Iterator<Item = SocketAddr> + 'a,
) -> impl Iterator<Item = SocketAddr> + 'a {
iter.filter(move |a| self.is_wanted(a))
}
fn is_wanted(&self, addr: &SocketAddr) -> bool {
match self {
IpFamily::Any => true,
IpFamily::Ipv4Only => addr.is_ipv4(),
IpFamily::Ipv6Only => addr.is_ipv6(),
}
}
}
impl fmt::Debug for Config {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut dbg = f.debug_struct("Config");
dbg.field("http_status_as_error", &self.http_status_as_error)
.field("https_only", &self.https_only)
.field("ip_family", &self.ip_family)
.field("proxy", &self.proxy)
.field("no_delay", &self.no_delay)
.field("max_redirects", &self.max_redirects)
.field("redirect_auth_headers", &self.redirect_auth_headers)
.field("user_agent", &self.user_agent)
.field("timeouts", &self.timeouts)
.field("max_response_header_size", &self.max_response_header_size)
.field("input_buffer_size", &self.input_buffer_size)
.field("output_buffer_size", &self.output_buffer_size)
.field("max_idle_connections", &self.max_idle_connections)
.field(
"max_idle_connections_per_host",
&self.max_idle_connections_per_host,
)
.field("max_idle_age", &self.max_idle_age)
.field("middleware", &self.middleware);
#[cfg(feature = "_tls")]
{
dbg.field("tls_config", &self.tls_config);
}
dbg.finish()
}
}
impl fmt::Debug for Timeouts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Timeouts")
.field("global", &self.global)
.field("per_call", &self.per_call)
.field("resolve", &self.resolve)
.field("connect", &self.connect)
.field("send_request", &self.send_request)
.field("await_100", &self.await_100)
.field("send_body", &self.send_body)
.field("recv_response", &self.recv_response)
.field("recv_body", &self.recv_body)
.finish()
}
}
#[cfg(test)]
mod test {
use super::*;
use assert_no_alloc::*;
#[test]
fn default_config_clone_does_not_allocate() {
let c = Config::default();
assert_no_alloc(|| c.clone());
}
}