use std::{any::Any, fmt, mem, pin::Pin};
#[cfg(feature = "http_cookie")]
use http::{HeaderValue, Uri};
use parking_lot::Mutex;
use crate::{
channel::IpcBytes,
http::{CacheKey, CachePolicy, Error, Request, Response, curl, file_cache},
};
type Fut<O> = Pin<Box<dyn Future<Output = O> + Send>>;
pub trait HttpCache: Send + Sync + Any {
fn policy(&'static self, key: CacheKey) -> Fut<Option<CachePolicy>>;
fn set_policy(&'static self, key: CacheKey, policy: CachePolicy) -> Fut<bool>;
fn body(&'static self, key: CacheKey) -> Fut<Option<IpcBytes>>;
fn set(&'static self, key: CacheKey, policy: CachePolicy, body: IpcBytes) -> Fut<()>;
fn remove(&'static self, key: CacheKey) -> Fut<()>;
#[cfg(feature = "http_cookie")]
fn cookie(&'static self, uri: Uri) -> Fut<Option<HeaderValue>>;
#[cfg(feature = "http_cookie")]
fn set_cookie(&'static self, uri: Uri, cookie: HeaderValue) -> Fut<()>;
#[cfg(feature = "http_cookie")]
fn remove_cookie(&'static self, uri: Uri) -> Fut<()>;
fn purge(&'static self) -> Fut<()>;
fn prune(&'static self) -> Fut<()>;
}
pub trait HttpClient: Send + Sync + Any {
fn is_cache_manager(&self) -> bool {
true
}
fn send(&'static self, request: Request) -> Fut<Result<Response, Error>>;
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct AlreadyInitedError {}
impl fmt::Display for AlreadyInitedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "default client already initialized, can only set before first use")
}
}
impl std::error::Error for AlreadyInitedError {}
pub fn http_client() -> &'static dyn HttpClient {
use once_cell::sync::Lazy;
static SHARED: Lazy<Box<dyn HttpClient>> = Lazy::new(|| {
let ci = mem::replace(&mut *CLIENT_INIT.lock(), ClientInit::Inited);
if let ClientInit::Set(init) = ci {
init()
} else {
Box::new(curl::CurlProcessClient::default())
}
});
&**SHARED
}
static CLIENT_INIT: Mutex<ClientInit> = Mutex::new(ClientInit::None);
enum ClientInit {
None,
Set(Box<dyn FnOnce() -> Box<dyn HttpClient> + Send>),
Inited,
}
pub fn set_http_client<I>(init: I) -> Result<(), AlreadyInitedError>
where
I: FnOnce() -> Box<dyn HttpClient> + Send + 'static,
{
let mut ci = CLIENT_INIT.lock();
if let ClientInit::Inited = &*ci {
Err(AlreadyInitedError {})
} else {
*ci = ClientInit::Set(Box::new(init));
Ok(())
}
}
pub fn http_cache() -> &'static dyn HttpCache {
use once_cell::sync::Lazy;
static SHARED: Lazy<Box<dyn HttpCache>> = Lazy::new(|| {
let ci = mem::replace(&mut *CACHE_INIT.lock(), CacheInit::Inited);
if let CacheInit::Set(init) = ci {
init()
} else {
Box::new(file_cache::FileSystemCache::new(zng_env::cache("zng-task-http-cache")).unwrap())
}
});
&**SHARED
}
static CACHE_INIT: Mutex<CacheInit> = Mutex::new(CacheInit::None);
enum CacheInit {
None,
Set(Box<dyn FnOnce() -> Box<dyn HttpCache> + Send>),
Inited,
}
pub fn set_http_cache<I>(init: I) -> Result<(), AlreadyInitedError>
where
I: FnOnce() -> Box<dyn HttpCache> + Send + 'static,
{
let mut ci = CACHE_INIT.lock();
if let CacheInit::Inited = &*ci {
Err(AlreadyInitedError {})
} else {
*ci = CacheInit::Set(Box::new(init));
Ok(())
}
}
pub fn set_request_default(d: Request) {
*REQUEST_DEFAULT.lock() = Some(d);
}
pub(super) static REQUEST_DEFAULT: Mutex<Option<Request>> = Mutex::new(None);