#[cfg(feature = "cookie")]
use cookie::Cookie;
#[cfg(feature = "cookie")]
use cookie_store::CookieStore;
use std::sync::Arc;
use std::sync::Mutex;
#[cfg(feature = "cookie")]
use url::Url;
use crate::header::{self, Header};
use crate::pool::ConnectionPool;
use crate::proxy::Proxy;
use crate::request::Request;
use crate::resolve::ArcResolver;
#[derive(Debug, Default, Clone)]
pub struct Agent {
pub(crate) headers: Vec<Header>,
pub(crate) state: Arc<Mutex<AgentState>>,
}
#[derive(Debug, Default)]
pub(crate) struct AgentState {
pub(crate) pool: ConnectionPool,
pub(crate) proxy: Option<Proxy>,
#[cfg(feature = "cookie")]
pub(crate) jar: CookieStore,
pub(crate) resolver: ArcResolver,
}
impl AgentState {
fn new() -> Self {
Self::default()
}
pub fn pool(&mut self) -> &mut ConnectionPool {
&mut self.pool
}
}
impl Agent {
pub fn new() -> Agent {
Default::default()
}
pub fn build(&self) -> Self {
Agent {
headers: self.headers.clone(),
state: Arc::new(Mutex::new(AgentState::new())),
}
}
pub fn set(&mut self, header: &str, value: &str) -> &mut Agent {
header::add_header(&mut self.headers, Header::new(header, value));
self
}
pub fn auth(&mut self, user: &str, pass: &str) -> &mut Agent {
let pass = basic_auth(user, pass);
self.auth_kind("Basic", &pass)
}
pub fn auth_kind(&mut self, kind: &str, pass: &str) -> &mut Agent {
let value = format!("{} {}", kind, pass);
self.set("Authorization", &value);
self
}
pub fn request(&self, method: &str, path: &str) -> Request {
Request::new(&self, method.into(), path.into())
}
pub fn set_max_pool_connections(&self, max_connections: usize) {
let mut state = self.state.lock().unwrap();
state.pool.set_max_idle_connections(max_connections);
}
pub fn set_max_pool_connections_per_host(&self, max_connections: usize) {
let mut state = self.state.lock().unwrap();
state
.pool
.set_max_idle_connections_per_host(max_connections);
}
pub fn set_resolver(&mut self, resolver: impl crate::Resolver + 'static) -> &mut Self {
self.state.lock().unwrap().resolver = resolver.into();
self
}
pub fn set_proxy(&mut self, proxy: Proxy) -> &mut Agent {
let mut state = self.state.lock().unwrap();
state.proxy = Some(proxy);
drop(state);
self
}
#[cfg(feature = "cookie")]
pub fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
let state = self.state.lock().unwrap();
let first_found = state.jar.iter_any().find(|c| c.name() == name);
if let Some(first_found) = first_found {
let c: &Cookie = &*first_found;
Some(c.clone())
} else {
None
}
}
#[cfg(feature = "cookie")]
pub fn set_cookie(&self, cookie: Cookie<'static>) {
let mut cookie = cookie.clone();
if cookie.domain().is_none() {
return;
}
if cookie.path().is_none() {
cookie.set_path("/");
}
let path = cookie.path().unwrap();
let domain = cookie.domain().unwrap();
let fake_url: Url = match format!("http://{}{}", domain, path).parse() {
Ok(u) => u,
Err(_) => return,
};
let mut state = self.state.lock().unwrap();
let cs_cookie = match cookie_store::Cookie::try_from_raw_cookie(&cookie, &fake_url) {
Ok(c) => c,
Err(_) => return,
};
state.jar.insert(cs_cookie, &fake_url).ok();
}
pub fn get(&self, path: &str) -> Request {
self.request("GET", path)
}
pub fn head(&self, path: &str) -> Request {
self.request("HEAD", 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)
}
pub fn trace(&self, path: &str) -> Request {
self.request("TRACE", path)
}
pub fn options(&self, path: &str) -> Request {
self.request("OPTIONS", path)
}
pub fn connect(&self, path: &str) -> Request {
self.request("CONNECT", path)
}
pub fn patch(&self, path: &str) -> Request {
self.request("PATCH", path)
}
}
pub(crate) fn basic_auth(user: &str, pass: &str) -> String {
let safe = match user.find(':') {
Some(idx) => &user[..idx],
None => user,
};
base64::encode(&format!("{}:{}", safe, pass))
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn agent_implements_send() {
let mut agent = Agent::new();
thread::spawn(move || {
agent.set("Foo", "Bar");
});
}
#[test]
#[cfg(any(feature = "tls", feature = "native-tls"))]
fn agent_pool() {
use std::io::Read;
let agent = crate::agent();
let url = "https://ureq.s3.eu-central-1.amazonaws.com/sherlock.txt";
let resp = agent.get(url).call();
assert!(resp.ok());
let mut reader = resp.into_reader();
let mut buf = vec![];
reader.read_to_end(&mut buf).unwrap();
fn poolsize(agent: &Agent) -> usize {
let mut state = agent.state.lock().unwrap();
state.pool().len()
}
assert_eq!(poolsize(&agent), 1);
let resp = agent.get(url).call();
assert!(resp.ok());
assert_eq!(poolsize(&agent), 0);
let mut reader = resp.into_reader();
let mut buf = vec![];
reader.read_to_end(&mut buf).unwrap();
}
#[test]
fn request_implements_send() {
let agent = Agent::new();
let mut request = Request::new(&agent, "GET".to_string(), "/foo".to_string());
thread::spawn(move || {
request.set("Foo", "Bar");
});
}
}