Files
ureq/src/agent.rs
Jacob Hoffman-Andrews 72e7e06334 Tweak import paths
Group together all cookie imports in agent.rs; consistently use `Duration`.
2020-10-25 15:10:03 -07:00

459 lines
14 KiB
Rust

use std::sync::Arc;
use crate::header::{self, Header};
use crate::pool::ConnectionPool;
use crate::proxy::Proxy;
use crate::request::Request;
use crate::resolve::{ArcResolver, StdResolver};
use std::time::Duration;
#[cfg(feature = "cookies")]
use {crate::cookies::CookieTin, cookie::Cookie, cookie_store::CookieStore, url::Url};
#[derive(Debug)]
pub struct AgentBuilder {
headers: Vec<Header>,
config: AgentConfig,
/// Cookies saved between requests.
/// Invariant: All cookies must have a nonempty domain and path.
#[cfg(feature = "cookies")]
cookie_store: Option<CookieStore>,
resolver: ArcResolver,
}
/// Config as built by AgentBuilder and then static for the lifetime of the Agent.
#[derive(Debug, Clone)]
pub(crate) struct AgentConfig {
pub max_idle_connections: usize,
pub max_idle_connections_per_host: usize,
pub proxy: Option<Proxy>,
pub timeout_connect: Option<Duration>,
pub timeout_read: Option<Duration>,
pub timeout_write: Option<Duration>,
pub timeout: Option<Duration>,
pub redirects: u32,
#[cfg(feature = "tls")]
pub tls_config: Option<TLSClientConfig>,
}
/// 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.
///
/// ```
/// let mut agent = ureq::agent();
///
/// agent.set("x-my-secret-header", "very secret");
///
/// let auth = agent
/// .post("/login")
/// .call(); // blocks.
///
/// if auth.is_err() {
/// println!("Noes!");
/// }
///
/// let secret = agent
/// .get("/my-protected-page")
/// .call(); // blocks and waits for request.
///
/// if secret.is_err() {
/// println!("Wot?!");
/// } else {
/// println!("Secret is: {}", secret.unwrap().into_string().unwrap());
/// }
/// ```
///
/// 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>,
/// Copied into each request of this agent.
pub(crate) headers: Vec<Header>,
/// Reused agent state for repeated requests from this agent.
pub(crate) state: Arc<AgentState>,
}
/// Container of the state
///
/// *Internal API*.
#[derive(Debug)]
pub(crate) struct AgentState {
/// Reused connections between requests.
pub(crate) pool: ConnectionPool,
pub(crate) proxy: Option<Proxy>,
/// 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,
}
impl Agent {
/// Creates an Agent with default settings.
///
/// Same as `AgentBuilder::new().build()`.
pub fn new() -> Self {
AgentBuilder::new().build()
}
/// Set a extra header field that will be present in all following requests using the agent.
///
/// This is useful for cases like auth, where we do a number of requests before getting
/// some credential that later must be presented in a header.
///
/// Notice that fixed headers can also be set in the `AgentBuilder`.
///
/// ```
/// let mut agent = ureq::agent();
///
/// agent.set("X-API-Key", "foobar");
/// agent.set("Accept", "text/plain");
///
/// let r = agent
/// .get("/my-page")
/// .call();
/// ```
pub fn set(&mut self, header: &str, value: &str) {
header::add_header(&mut self.headers, Header::new(header, value));
}
/// Request by providing the HTTP verb such as `GET`, `POST`...
///
/// ```
/// let agent = ureq::agent();
///
/// let r = agent
/// .request("GET", "/my_page")
/// .call();
/// println!("{:?}", r);
/// ```
pub fn request(&self, method: &str, path: &str) -> Request {
Request::new(self.clone(), method.into(), path.into())
}
/// Store a cookie in this agent.
///
/// ```
/// let agent = ureq::agent();
///
/// let cookie = ureq::Cookie::build("name", "value")
/// .secure(true)
/// .finish();
/// agent.set_cookie(cookie, &"https://example.com/".parse().unwrap());
/// ```
#[cfg(feature = "cookies")]
pub fn set_cookie(&self, cookie: Cookie<'static>, url: &Url) {
self.state
.cookie_tin
.store_response_cookies(Some(cookie).into_iter(), url);
}
/// 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 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)
}
/// Make a TRACE request from this agent.
pub fn trace(&self, path: &str) -> Request {
self.request("TRACE", path)
}
/// Make a OPTIONS request from this agent.
pub fn options(&self, path: &str) -> Request {
self.request("OPTIONS", path)
}
/// Make a PATCH request from this agent.
pub fn patch(&self, path: &str) -> Request {
self.request("PATCH", path)
}
}
impl AgentBuilder {
pub fn new() -> Self {
AgentBuilder {
headers: vec![],
config: AgentConfig {
max_idle_connections: crate::pool::DEFAULT_MAX_IDLE_CONNECTIONS,
max_idle_connections_per_host: crate::pool::DEFAULT_MAX_IDLE_CONNECTIONS_PER_HOST,
proxy: None,
timeout_connect: Some(Duration::from_secs(30)),
timeout_read: None,
timeout_write: None,
timeout: None,
redirects: 5,
#[cfg(feature = "tls")]
tls_config: None,
},
resolver: StdResolver.into(),
#[cfg(feature = "cookies")]
cookie_store: None,
}
}
/// 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 {
let config = Arc::new(self.config);
Agent {
headers: self.headers,
state: Arc::new(AgentState {
pool: ConnectionPool::new_with_limits(
config.max_idle_connections,
config.max_idle_connections_per_host,
),
proxy: config.proxy.clone(),
#[cfg(feature = "cookies")]
cookie_tin: CookieTin::new(
self.cookie_store.unwrap_or_else(|| CookieStore::default()),
),
resolver: self.resolver,
}),
config,
}
}
/// Set a header field that will be present in all requests using the agent.
///
/// ```
/// let agent = ureq::builder()
/// .set("X-API-Key", "foobar")
/// .set("Accept", "text/plain")
/// .build();
///
/// let r = agent
/// .get("/my-page")
/// .call();
///
/// if let Ok(resp) = r {
/// println!("yay got {}", resp.into_string().unwrap());
/// } else {
/// println!("Oh no error!");
/// }
/// ```
pub fn set(mut self, header: &str, value: &str) -> Self {
header::add_header(&mut self.headers, Header::new(header, value));
self
}
/// Set the proxy server to use for all connections from this Agent.
///
/// Example:
/// ```
/// let proxy = ureq::Proxy::new("user:password@cool.proxy:9090").unwrap();
/// let agent = ureq::AgentBuilder::new()
/// .proxy(proxy)
/// .build();
/// ```
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.config.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.config.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.
///
/// ```
/// let agent = ureq::builder()
/// .timeout_connect(std::time::Duration::from_secs(1))
/// .build();
/// let r = agent.get("/my_page").call();
/// ```
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 `0`, which means it can block forever.
///
/// ```
/// let agent = ureq::builder()
/// .timeout_read(std::time::Duration::from_secs(1))
/// .build();
/// let r = agent.get("/my_page").call();
/// ```
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 `0`, which means it can block forever.
///
/// ```
/// let agent = ureq::builder()
/// .timeout_write(std::time::Duration::from_secs(1))
/// .build();
/// let r = agent.get("/my_page").call();
/// ```
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()`.
///
/// ```
/// // wait max 1 second for whole request to complete.
/// let agent = ureq::builder()
/// .timeout(std::time::Duration::from_secs(1))
/// .build();
/// let r = agent.get("/my_page").call();
/// ```
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.
///
/// ```
/// let r = ureq::builder()
/// .redirects(10)
/// .build()
/// .get("/my_page")
/// .call();
/// println!("{:?}", r);
/// ```
pub fn redirects(mut self, n: u32) -> Self {
self.config.redirects = n;
self
}
/// Set the TLS client config to use for the connection. See [`ClientConfig`](https://docs.rs/rustls/latest/rustls/struct.ClientConfig.html).
///
/// Example:
/// ```
/// let tls_config = std::sync::Arc::new(rustls::ClientConfig::new());
/// let agent = ureq::builder()
/// .set_tls_config(tls_config.clone())
/// .build();
/// let req = agent.post("https://cool.server");
/// ```
#[cfg(feature = "tls")]
pub fn set_tls_config(mut self, tls_config: Arc<rustls::ClientConfig>) -> Self {
self.config.tls_config = Some(TLSClientConfig(tls_config));
self
}
}
#[cfg(feature = "tls")]
#[derive(Clone)]
pub(crate) struct TLSClientConfig(pub(crate) Arc<rustls::ClientConfig>);
#[cfg(feature = "tls")]
impl std::fmt::Debug for TLSClientConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TLSClientConfig").finish()
}
}
#[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());
}
}