use std::fmt; 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, resolver: ArcResolver, middleware: Vec>, } /// Config as built by AgentBuilder and then static for the lifetime of the Agent. #[derive(Clone)] pub(crate) struct AgentConfig { pub proxy: Option, pub timeout_connect: Option, pub timeout_read: Option, pub timeout_write: Option, pub timeout: Option, pub redirects: u32, pub redirect_auth_headers: RedirectAuthHeaders, pub user_agent: String, pub tls_config: Arc, } impl fmt::Debug for AgentConfig { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { todo!() } } /// 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, /// Reused agent state for repeated requests from this agent. pub(crate) state: Arc, } /// 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>, } 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() } } 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, redirects: 5, redirect_auth_headers: RedirectAuthHeaders::Never, user_agent: format!("ureq/{}", env!("CARGO_PKG_VERSION")), tls_config: 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 } /// 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>` 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 } /// 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) -> Self { self.config.tls_config = 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`](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> { /// # 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(mut self, tls_config: Arc) -> Self { self.config.tls_config = 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); #[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 = Box::new(AgentBuilder::new().build()); let _agent: Box = Box::new(AgentBuilder::new().build()); } }