leyline-http 0.1.1

HTTP client that mimics browsers on the wire
Documentation
use crate::core::Result;
use crate::core::config::{Origin, ProxyConfig};
use crate::core::proxy_pool::{Lease, ProxyPool};
use crate::core::response::Response;
use crate::core::retry::RetryPolicy;
use crate::core::session::Session;
use crate::core::session::execute::Attempt;

pub(super) struct Route<'a> {
    session: &'a Session,
    url: Option<url::Url>,
    origin: Option<Origin>,
    pool: Option<&'a ProxyPool>,
    policy: &'a RetryPolicy,
    last: Option<usize>,
}

impl<'a> Route<'a> {
    pub(super) fn new(session: &'a Session, attempt: &Attempt, policy: &'a RetryPolicy) -> Self {
        let url = url::Url::parse(&attempt.url).ok();
        let origin = url.as_ref().and_then(Origin::of);
        let pool = session.proxy_pool().filter(|_| attempt.proxy.is_none());
        Self {
            session,
            url,
            origin,
            pool,
            policy,
            last: None,
        }
    }

    pub(super) async fn send(
        &mut self,
        mut this: Attempt,
        rotation: Option<&ProxyConfig>,
    ) -> Result<Response> {
        let lease = self.lease(&mut this, rotation);
        let used = self.used_proxy(this.proxy.as_ref());
        crate::trace::note_proxy(used.as_deref());
        let session = match &lease {
            Some(lease) => lease.session(self.session)?,
            None => self.session.clone(),
        };
        crate::trace::note_browser(session.identity().browser());
        let mut result = session.attempt(this).await;
        let answered = match result.as_mut() {
            Ok(response) => {
                response.set_proxy(used.as_deref());
                self.observe(response);
                Origin::of(response.url())
            }
            Err(error) => {
                error.set_proxy(used.as_deref());
                None
            }
        };
        if let Some(lease) = lease {
            lease.record(&result, self.origin.as_ref(), answered.as_ref());
            self.last = Some(lease.index());
        }
        result
    }

    fn lease(&self, this: &mut Attempt, rotation: Option<&ProxyConfig>) -> Option<Lease> {
        let Some(pool) = self.pool else {
            if let Some(proxy) = rotation {
                this.proxy = Some(proxy.clone());
            }
            return None;
        };
        let lease = pool.lease(self.origin.as_ref(), self.last)?;
        this.proxy = Some(lease.config().clone());
        Some(lease)
    }

    fn observe(&self, response: &Response) {
        self.session.host_limits().observe(
            response.url(),
            response.status(),
            self.policy.server_wait(response),
        );
    }

    fn used_proxy(&self, proxy: Option<&ProxyConfig>) -> Option<String> {
        let used = match (proxy, &self.url) {
            (Some(proxy), Some(url)) => proxy.proxy_for(url).ok().flatten(),
            (Some(proxy), None) => proxy.primary(),
            (None, Some(url)) => self.session.proxy_for(url, None).ok().flatten(),
            (None, None) => None,
        };
        used.map(str::to_owned)
    }
}