use std::convert::TryFrom;
use std::fmt;
use std::sync::Arc;
use http::{Method, Request, Response, Uri};
use ureq_proto::BodyMode;
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::request::ForceSendBody;
use crate::resolver::{DefaultResolver, Resolver};
use crate::send_body::AsSendBody;
use crate::transport::{boxed_connector, Connector, DefaultConnector, Transport};
use crate::unversioned::transport::{ConnectionDetails, RunConnector};
use crate::{Error, RequestBuilder, SendBody};
use crate::{WithBody, WithoutBody};
#[derive(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>,
pub(crate) run_connector: Arc<RunConnector>,
}
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));
let run_connector = {
let pool = pool.clone();
Arc::new(move |details: &ConnectionDetails| pool.run_connector(details))
};
Agent {
config: Arc::new(config),
pool,
resolver: Arc::new(resolver),
#[cfg(feature = "cookies")]
jar: Arc::new(crate::cookies::SharedCookieJar::new()),
run_connector,
}
}
#[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 mut body = body.as_body();
let mut request = Request::from_parts(parts, ());
let has_body = !matches!(body.body_mode(), Ok(BodyMode::NoBody));
if has_body {
request.extensions_mut().insert(ForceSendBody);
}
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())
}
#[must_use]
pub fn get<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<WithoutBody>::new(self.clone(), Method::GET, uri)
}
#[must_use]
pub fn post<T>(&self, uri: T) -> RequestBuilder<WithBody>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<WithBody>::new(self.clone(), Method::POST, uri)
}
#[must_use]
pub fn put<T>(&self, uri: T) -> RequestBuilder<WithBody>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<WithBody>::new(self.clone(), Method::PUT, uri)
}
#[must_use]
pub fn delete<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<WithoutBody>::new(self.clone(), Method::DELETE, uri)
}
#[must_use]
pub fn head<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<WithoutBody>::new(self.clone(), Method::HEAD, uri)
}
#[must_use]
pub fn options<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<WithoutBody>::new(self.clone(), Method::OPTIONS, uri)
}
#[must_use]
pub fn connect<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<WithoutBody>::new(self.clone(), Method::CONNECT, uri)
}
#[must_use]
pub fn patch<T>(&self, uri: T) -> RequestBuilder<WithBody>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<WithBody>::new(self.clone(), Method::PATCH, uri)
}
#[must_use]
pub fn trace<T>(&self, uri: T) -> RequestBuilder<WithoutBody>
where
Uri: TryFrom<T>,
<Uri as TryFrom<T>>::Error: Into<http::Error>,
{
RequestBuilder::<WithoutBody>::new(self.clone(), Method::TRACE, uri)
}
}
impl From<Config> for Agent {
fn from(value: Config) -> Self {
Agent::new_with_config(value)
}
}
impl fmt::Debug for Agent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut dbg = f.debug_struct("Agent");
dbg.field("config", &self.config)
.field("pool", &self.pool)
.field("resolver", &self.resolver);
#[cfg(feature = "cookies")]
{
dbg.field("jar", &self.jar);
}
dbg.finish()
}
}
#[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());
}
}