use std::convert::TryFrom;
use std::fmt::Debug;
use std::sync::Arc;
use http::{Method, Request, Response, Uri};
use crate::body::Body;
use crate::config::typestate::{AgentScope, HttpCrateScope};
use crate::config::{Config, ConfigBuilder, RequestLevelConfig};
use crate::http;
use crate::middleware::MiddlewareNext;
use crate::pool::ConnectionPool;
use crate::resolver::{DefaultResolver, Resolver};
use crate::send_body::AsSendBody;
use crate::transport::{boxed_connector, Connector, DefaultConnector, Transport};
use crate::{Error, RequestBuilder, SendBody};
use crate::{WithBody, WithoutBody};
#[derive(Debug, Clone)]
pub struct Agent {
pub(crate) config: Arc<Config>,
pub(crate) pool: Arc<ConnectionPool>,
pub(crate) resolver: Arc<dyn Resolver>,
#[cfg(feature = "cookies")]
pub(crate) jar: Arc<crate::cookies::SharedCookieJar>,
}
impl Agent {
pub fn new_with_defaults() -> Self {
Self::with_parts_inner(
Config::default(),
Box::new(DefaultConnector::default()),
DefaultResolver::default(),
)
}
pub fn new_with_config(config: Config) -> Self {
Self::with_parts_inner(
config,
Box::new(DefaultConnector::default()),
DefaultResolver::default(),
)
}
pub fn config_builder() -> ConfigBuilder<AgentScope> {
Config::builder()
}
pub fn with_parts(config: Config, connector: impl Connector, resolver: impl Resolver) -> Self {
let boxed = boxed_connector(connector);
Self::with_parts_inner(config, boxed, resolver)
}
fn with_parts_inner(
config: Config,
connector: Box<dyn Connector<(), Out = Box<dyn Transport>>>,
resolver: impl Resolver,
) -> Self {
let pool = Arc::new(ConnectionPool::new(connector, &config));
Agent {
config: Arc::new(config),
pool,
resolver: Arc::new(resolver),
#[cfg(feature = "cookies")]
jar: Arc::new(crate::cookies::SharedCookieJar::new()),
}
}
#[cfg(feature = "cookies")]
pub fn cookie_jar_lock(&self) -> crate::cookies::CookieJar<'_> {
self.jar.lock()
}
pub fn run(&self, request: Request<impl AsSendBody>) -> Result<Response<Body>, Error> {
let (parts, mut body) = request.into_parts();
let body = body.as_body();
let request = Request::from_parts(parts, ());
self.run_via_middleware(request, body)
}
pub(crate) fn run_via_middleware(
&self,
request: Request<()>,
body: SendBody,
) -> Result<Response<Body>, Error> {
let (parts, _) = request.into_parts();
let request = http::Request::from_parts(parts, body);
let next = MiddlewareNext::new(self);
next.handle(request)
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn configure_request<S: AsSendBody>(
&self,
mut request: Request<S>,
) -> ConfigBuilder<HttpCrateScope<S>> {
let exts = request.extensions_mut();
if exts.get::<RequestLevelConfig>().is_none() {
exts.insert(self.new_request_level_config());
}
ConfigBuilder(HttpCrateScope(request))
}
pub(crate) fn new_request_level_config(&self) -> RequestLevelConfig {
RequestLevelConfig(self.config.as_ref().clone())
}
}
macro_rules! mk_method {
($(($f:tt, $m:tt, $b:ty)),*) => {
impl Agent {
$(
#[doc = concat!("Make a ", stringify!($m), " request using this agent.")]
#[must_use]
pub fn $f<T>(&self, uri: T) -> RequestBuilder<$b>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<$b>::new(self.clone(), Method::$m, uri)
}
)*
}
};
}
mk_method!(
(get, GET, WithoutBody),
(post, POST, WithBody),
(put, PUT, WithBody),
(delete, DELETE, WithoutBody),
(head, HEAD, WithoutBody),
(options, OPTIONS, WithoutBody),
(connect, CONNECT, WithoutBody),
(patch, PATCH, WithBody),
(trace, TRACE, WithoutBody)
);
impl From<Config> for Agent {
fn from(value: Config) -> Self {
Agent::new_with_config(value)
}
}
#[cfg(test)]
impl Agent {
pub fn pool_count(&self) -> usize {
self.pool.pool_count()
}
}
#[cfg(test)]
mod test {
use super::*;
use assert_no_alloc::*;
#[test]
fn agent_clone_does_not_allocate() {
let a = Agent::new_with_defaults();
assert_no_alloc(|| a.clone());
}
}