use crate::body::Body;
use crate::client::policy::{PolicyDecision, RequestPolicy};
use crate::client::request_executor::RequestExecutor;
use crate::config::Config;
use crate::dns::DnsResolver;
use crate::error::Error;
use crate::parser::Response;
use crate::parser::uri::Uri;
use crate::request_builder::ClientRequestBuilder;
use crate::socket::BlockingSocket;
use crate::transport::ConnectionPool;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
#[cfg(feature = "cookie-jar")]
use crate::cookie_jar::CookieStore;
pub struct HttpClient<S, D> {
pool: Arc<ConnectionPool<S>>,
dns: Arc<D>,
config: Arc<Config>,
#[cfg(feature = "cookie-jar")]
cookie_store: Arc<CookieStore>,
}
impl<S, D> Clone for HttpClient<S, D> {
fn clone(&self) -> Self {
Self {
pool: Arc::clone(&self.pool),
dns: Arc::clone(&self.dns),
config: Arc::clone(&self.config),
#[cfg(feature = "cookie-jar")]
cookie_store: Arc::clone(&self.cookie_store),
}
}
}
impl HttpClient<crate::socket::blocking::OsBlockingSocket, crate::dns::resolver::OsDnsResolver> {
pub fn new() -> Result<Self, Error> {
let config = Config::default();
Ok(Self {
pool: Arc::new(ConnectionPool::new(config.max_idle_per_host, config.idle_timeout)),
dns: Arc::new(crate::dns::resolver::OsDnsResolver::new()),
config: Arc::new(config),
#[cfg(feature = "cookie-jar")]
cookie_store: Arc::new(CookieStore::new()),
})
}
pub fn with_config(config: Config) -> Result<Self, Error> {
Ok(Self {
pool: Arc::new(ConnectionPool::new(config.max_idle_per_host, config.idle_timeout)),
dns: Arc::new(crate::dns::resolver::OsDnsResolver::new()),
config: Arc::new(config),
#[cfg(feature = "cookie-jar")]
cookie_store: Arc::new(CookieStore::new()),
})
}
}
impl<S, D> HttpClient<S, D>
where
S: BlockingSocket,
D: DnsResolver,
{
pub fn new_with_adapters(dns: D) -> Self {
let config = Config::default();
Self {
pool: Arc::new(ConnectionPool::new(config.max_idle_per_host, config.idle_timeout)),
dns: Arc::new(dns),
config: Arc::new(config),
#[cfg(feature = "cookie-jar")]
cookie_store: Arc::new(CookieStore::new()),
}
}
#[allow(clippy::missing_const_for_fn)]
pub fn with_adapters_and_config(
dns: D,
config: Config,
) -> Self {
Self {
pool: Arc::new(ConnectionPool::new(config.max_idle_per_host, config.idle_timeout)),
dns: Arc::new(dns),
config: Arc::new(config),
#[cfg(feature = "cookie-jar")]
cookie_store: Arc::new(CookieStore::new()),
}
}
pub fn get(
&self,
url: impl Into<String>,
) -> ClientRequestBuilder<S, D, crate::request_builder::WithoutBody> {
ClientRequestBuilder::<S, D, crate::request_builder::WithoutBody>::new(
self.clone(),
crate::method::Method::Get,
url,
)
}
pub fn post(
&self,
url: impl Into<String>,
) -> ClientRequestBuilder<S, D, crate::request_builder::WithBody> {
ClientRequestBuilder::<S, D, crate::request_builder::WithBody>::new(self.clone(), crate::method::Method::Post, url)
}
pub fn put(
&self,
url: impl Into<String>,
) -> ClientRequestBuilder<S, D, crate::request_builder::WithBody> {
ClientRequestBuilder::<S, D, crate::request_builder::WithBody>::new(self.clone(), crate::method::Method::Put, url)
}
pub fn delete(
&self,
url: impl Into<String>,
) -> ClientRequestBuilder<S, D, crate::request_builder::WithoutBody> {
ClientRequestBuilder::<S, D, crate::request_builder::WithoutBody>::new(
self.clone(),
crate::method::Method::Delete,
url,
)
}
pub fn head(
&self,
url: impl Into<String>,
) -> ClientRequestBuilder<S, D, crate::request_builder::WithoutBody> {
ClientRequestBuilder::<S, D, crate::request_builder::WithoutBody>::new(
self.clone(),
crate::method::Method::Head,
url,
)
}
pub fn options(
&self,
url: impl Into<String>,
) -> ClientRequestBuilder<S, D, crate::request_builder::WithoutBody> {
ClientRequestBuilder::<S, D, crate::request_builder::WithoutBody>::new(
self.clone(),
crate::method::Method::Options,
url,
)
}
pub fn patch(
&self,
url: impl Into<String>,
) -> ClientRequestBuilder<S, D, crate::request_builder::WithBody> {
ClientRequestBuilder::<S, D, crate::request_builder::WithBody>::new(self.clone(), crate::method::Method::Patch, url)
}
pub fn trace(
&self,
url: impl Into<String>,
) -> ClientRequestBuilder<S, D, crate::request_builder::WithoutBody> {
ClientRequestBuilder::<S, D, crate::request_builder::WithoutBody>::new(
self.clone(),
crate::method::Method::Trace,
url,
)
}
pub fn connect(
&self,
url: impl Into<String>,
) -> ClientRequestBuilder<S, D, crate::request_builder::WithoutBody> {
ClientRequestBuilder::<S, D, crate::request_builder::WithoutBody>::new(
self.clone(),
crate::method::Method::Connect,
url,
)
}
#[cfg(feature = "cookie-jar")]
#[must_use]
pub const fn cookie_store(&self) -> &Arc<CookieStore> {
&self.cookie_store
}
pub fn run(
&self,
request: crate::request::Request,
) -> Result<Response, Error> {
let (method, url, headers, body) = request.into_parts();
self.request(method, &url, &headers, body.map(Body::into_bytes), None)
}
pub(crate) fn request(
&self,
method: crate::method::Method,
url: &str,
custom_headers: &crate::headers::Headers,
body: Option<Vec<u8>>,
request_config: Option<&Config>,
) -> Result<Response, Error> {
let config = request_config.unwrap_or_else(|| self.config.as_ref());
let mut current_url = String::from(url);
let mut current_method = method;
let mut current_body = body;
let mut policy = RequestPolicy::new(config);
loop {
let uri = Uri::parse(¤t_url).map_err(Error::Parse)?;
policy.validate_protocol(&uri)?;
#[cfg(feature = "cookie-jar")]
let mut headers_with_cookies = custom_headers.clone();
#[cfg(feature = "cookie-jar")]
{
let is_secure = current_url.starts_with("https://");
let cookie_header = self
.cookie_store
.get_request_cookies(¤t_url, is_secure);
if !cookie_header.is_empty() {
headers_with_cookies.insert(crate::headers::HeaderName::COOKIE, &cookie_header);
}
}
#[cfg(feature = "cookie-jar")]
let headers_to_use = &headers_with_cookies;
#[cfg(not(feature = "cookie-jar"))]
let headers_to_use = custom_headers;
let executor = RequestExecutor::new(&self.pool, self.dns.as_ref(), config);
let body_slice = current_body.as_deref();
let raw = executor.execute(&uri, current_method, headers_to_use, body_slice)?;
#[cfg(feature = "cookie-jar")]
{
let set_cookie_headers: Vec<String> = raw
.headers
.get_all(crate::headers::HeaderName::SET_COOKIE)
.into_iter()
.map(alloc::string::ToString::to_string)
.collect();
if !set_cookie_headers.is_empty() {
self
.cookie_store
.store_response_cookies(¤t_url, &set_cookie_headers);
}
}
match policy.process_raw_response(raw, &uri, ¤t_url, current_method, current_body)? {
PolicyDecision::Return(response) => return Ok(response),
PolicyDecision::Redirect {
next_uri,
next_method,
next_body,
} => {
current_url = next_uri;
current_method = next_method;
current_body = next_body;
},
}
}
}
}