use std::borrow::Cow;
use std::sync::Arc;
use async_trait::async_trait;
use http::HeaderMap;
use url::Url;
pub mod auto;
#[cfg(feature = "cdp-backend")]
pub mod render;
pub use auto::{AutoFetcher, AutoOutcome};
#[cfg(feature = "cdp-backend")]
pub use render::RenderFetcher;
use crate::discovery::assets::SecFetchDest;
use crate::impersonate::{ImpersonateClient, Response};
use crate::queue::Job;
use crate::Result;
use super::SessionContext;
pub enum FetchOutput {
Http(Response),
#[cfg(feature = "cdp-backend")]
Rendered(Box<crate::render::RenderedPage>),
}
impl FetchOutput {
pub fn status(&self) -> u16 {
match self {
FetchOutput::Http(r) => r.status.as_u16(),
#[cfg(feature = "cdp-backend")]
FetchOutput::Rendered(p) => p.status,
}
}
pub fn body(&self) -> &[u8] {
match self {
FetchOutput::Http(r) => &r.body,
#[cfg(feature = "cdp-backend")]
FetchOutput::Rendered(p) => p.html_post_js.as_bytes(),
}
}
pub fn final_url(&self) -> &Url {
match self {
FetchOutput::Http(r) => &r.final_url,
#[cfg(feature = "cdp-backend")]
FetchOutput::Rendered(p) => &p.final_url,
}
}
pub fn headers(&self) -> Cow<'_, HeaderMap> {
match self {
FetchOutput::Http(r) => Cow::Borrowed(&r.headers),
#[cfg(feature = "cdp-backend")]
FetchOutput::Rendered(_) => Cow::Owned(HeaderMap::new()),
}
}
#[cfg(feature = "cdp-backend")]
pub fn is_rendered(&self) -> bool {
matches!(self, FetchOutput::Rendered(_))
}
#[cfg(not(feature = "cdp-backend"))]
pub fn is_rendered(&self) -> bool {
false
}
}
#[async_trait]
pub trait Fetcher: Send + Sync {
async fn fetch(&self, job: &Job, ctx: &SessionContext) -> Result<FetchOutput>;
}
pub struct SpoofFetcher {
client: Arc<ImpersonateClient>,
}
impl SpoofFetcher {
pub fn new(client: Arc<ImpersonateClient>) -> Self {
Self { client }
}
pub fn client(&self) -> &ImpersonateClient {
&self.client
}
pub async fn fetch_with(
&self,
url: &Url,
dest: SecFetchDest,
proxy: Option<&Url>,
timed: bool,
) -> Result<Response> {
match (proxy, timed) {
(Some(p), true) => self.client.get_timed_via(url, Some(p), dest).await,
(Some(p), false) => self.client.get_via(url, Some(p), dest).await,
(None, true) => self.client.get_timed_with_dest(url, dest).await,
(None, false) => {
if matches!(dest, SecFetchDest::Document) {
self.client.get(url).await
} else {
self.client.get_with_dest(url, dest).await
}
}
}
}
}
#[async_trait]
impl Fetcher for SpoofFetcher {
async fn fetch(&self, job: &Job, _ctx: &SessionContext) -> Result<FetchOutput> {
let resp = self.client.get(&job.url).await?;
Ok(FetchOutput::Http(resp))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_send_sync<T: Send + Sync>() {}
#[test]
fn fetcher_trait_is_object_safe() {
fn _accepts(_f: Arc<dyn Fetcher>) {}
}
#[test]
fn spoof_fetcher_is_send_sync() {
assert_send_sync::<SpoofFetcher>();
}
#[test]
fn spoof_fetcher_constructs() {
let profile = crate::impersonate::Profile::Chrome131Stable;
let client = Arc::new(ImpersonateClient::new(profile).expect("client builds"));
let f = SpoofFetcher::new(client.clone());
assert!(std::ptr::eq(&*f.client as *const _, &*client as *const _));
}
}