Skip to main content

leyline/core/session/
helpers.rs

1use http::Method;
2
3use crate::cookie::Jar;
4use crate::profile::{Browser, Preset};
5
6use super::{Session, SessionBuilder};
7use crate::core::request::RequestBuilder;
8use crate::core::retry::RetryPolicy;
9use crate::core::{
10    Body, IntoUrl, ProxyConfig, ProxyUrl, RedirectPolicy, Response, Result, TimeoutConfig,
11};
12
13impl Session {
14    pub fn builder() -> SessionBuilder {
15        SessionBuilder::new()
16    }
17
18    pub fn new() -> Self {
19        Self::builder().into_builtin()
20    }
21
22    pub fn browser(browser: Browser) -> Self {
23        Self::builder().browser(browser).into_builtin()
24    }
25
26    pub fn tab(&self) -> crate::core::Tab {
27        crate::core::Tab::new(self.clone())
28    }
29
30    pub fn cookies(&self) -> &Jar {
31        &self.inner.cookie_jar
32    }
33
34    pub fn with_proxy(&self, config: impl Into<ProxyConfig>) -> Self {
35        let config = config.into();
36        self.derive(|inner| inner.proxy_config = config)
37    }
38
39    pub fn proxy_url(&self) -> Option<ProxyUrl> {
40        self.inner.proxy_config.primary().map(ProxyUrl::configured)
41    }
42
43    pub fn fresh_pool(&self) -> Self {
44        self.derive(|inner| {
45            inner.pool = std::sync::Arc::new(inner.pool.fresh());
46            inner.connector = inner.connector.with_fresh_session_cache();
47        })
48    }
49
50    pub fn with_cookie_jar(&self, jar: Jar) -> Self {
51        self.derive(|inner| inner.cookie_jar = jar)
52    }
53
54    pub fn with_redirect(&self, policy: RedirectPolicy) -> Self {
55        self.derive(|inner| inner.redirect_policy = policy)
56    }
57
58    pub fn with_base_url(&self, url: impl IntoUrl) -> Result<Self> {
59        let url = base_url(url)?;
60        Ok(self.derive(|inner| inner.base_url = Some(url)))
61    }
62
63    pub(crate) fn base_url(&self) -> Option<&url::Url> {
64        self.inner.base_url.as_ref()
65    }
66
67    fn derive(&self, change: impl FnOnce(&mut super::SessionInner)) -> Self {
68        let mut s = self.clone();
69        change(std::sync::Arc::make_mut(&mut s.inner));
70        s
71    }
72
73    pub fn host_stats(&self) -> Vec<crate::core::config::HostStats> {
74        self.inner.host_limits.stats()
75    }
76
77    pub(crate) fn host_limits(&self) -> &crate::core::config::HostLimits {
78        &self.inner.host_limits
79    }
80
81    pub(crate) fn proxy_pool(&self) -> Option<&crate::core::proxy_pool::ProxyPool> {
82        self.inner.proxy_pool.as_ref()
83    }
84
85    pub(crate) fn without_proxy_pool(&self) -> Self {
86        self.derive(|inner| inner.proxy_pool = None)
87    }
88
89    pub(crate) fn downgrade(&self) -> std::sync::Weak<super::SessionInner> {
90        std::sync::Arc::downgrade(&self.inner)
91    }
92
93    pub(crate) fn is_inner(&self, weak: &std::sync::Weak<super::SessionInner>) -> bool {
94        std::ptr::eq(weak.as_ptr(), std::sync::Arc::as_ptr(&self.inner))
95    }
96
97    pub(crate) fn impersonates(&self) -> bool {
98        self.inner.impersonates
99    }
100
101    pub(crate) fn default_retry(&self) -> &crate::core::retry::RetryPolicy {
102        &self.inner.default_retry
103    }
104
105    pub fn pool_stats(&self) -> crate::PoolStats {
106        self.inner.pool.stats()
107    }
108
109    pub async fn preconnect(&self, url: impl IntoUrl) -> Result<()> {
110        let url = url.into_url()?;
111        self.unless_shut_down(self.warm(url)).await
112    }
113
114    async fn warm(&self, url: url::Url) -> Result<()> {
115        if url.scheme() != "https" || self.inner.protocol_policy == super::ProtocolPolicy::Http1 {
116            return Ok(());
117        }
118        let host = url.host_str().unwrap_or("");
119        let port = url.port_or_known_default().unwrap_or(443);
120        let proxy = self.proxy_for(&url, None)?;
121        let opened = crate::pool::checkout_handle(
122            &self.inner.pool,
123            &self.inner.connector,
124            &self.inner.h2_config,
125            host,
126            port,
127            proxy,
128        )
129        .await;
130        match opened {
131            Ok(_) => Ok(()),
132            Err(error) if error.alpn().is_some() => {
133                self.inner.pool.note_h1_only(host, port, proxy);
134                Ok(())
135            }
136            Err(error) => Err(error),
137        }
138    }
139
140    pub fn request(&self, method: Method, url: impl IntoUrl) -> RequestBuilder {
141        RequestBuilder::from_url(self, method, url)
142    }
143
144    pub async fn execute(&self, req: http::Request<Body>) -> Result<Response> {
145        self.http_request(req).send().await
146    }
147
148    pub(crate) fn http_request(&self, req: http::Request<Body>) -> RequestBuilder {
149        let (mut parts, body) = req.into_parts();
150        let mut builder = RequestBuilder::new(self, parts.method, &parts.uri.to_string());
151        for (name, value) in &parts.headers {
152            builder = builder.header(name.clone(), value.clone());
153        }
154        builder = builder.body(body);
155        if let Some(preset) = parts.extensions.remove::<Preset>() {
156            builder = builder.preset(preset);
157        }
158        if let Some(timeouts) = parts.extensions.remove::<TimeoutConfig>() {
159            builder = builder.timeout(timeouts);
160        }
161        if let Some(policy) = parts.extensions.remove::<RetryPolicy>() {
162            builder = builder.retry(policy);
163        }
164        if let Some(policy) = parts.extensions.remove::<RedirectPolicy>() {
165            builder = builder.redirect(policy);
166        }
167        if let Some(proxy) = parts.extensions.remove::<ProxyConfig>() {
168            builder = builder.proxy(proxy);
169        }
170        builder
171    }
172
173    pub fn get(&self, url: impl IntoUrl) -> RequestBuilder {
174        RequestBuilder::from_url(self, Method::GET, url)
175    }
176
177    pub fn post(&self, url: impl IntoUrl) -> RequestBuilder {
178        RequestBuilder::from_url(self, Method::POST, url)
179    }
180
181    pub fn put(&self, url: impl IntoUrl) -> RequestBuilder {
182        RequestBuilder::from_url(self, Method::PUT, url)
183    }
184
185    pub fn patch(&self, url: impl IntoUrl) -> RequestBuilder {
186        RequestBuilder::from_url(self, Method::PATCH, url)
187    }
188
189    pub fn delete(&self, url: impl IntoUrl) -> RequestBuilder {
190        RequestBuilder::from_url(self, Method::DELETE, url)
191    }
192
193    pub fn head(&self, url: impl IntoUrl) -> RequestBuilder {
194        RequestBuilder::from_url(self, Method::HEAD, url)
195    }
196}
197
198pub(super) fn base_url(url: impl IntoUrl) -> Result<url::Url> {
199    let url = url.into_url()?;
200    if url.cannot_be_a_base() {
201        return Err(
202            crate::core::error::Error::new(crate::core::error::Kind::Config)
203                .with_message(format!("base URL {url} cannot be a base")),
204        );
205    }
206    Ok(url)
207}
208
209impl Default for Session {
210    fn default() -> Self {
211        Self::new()
212    }
213}
214
215impl std::fmt::Debug for Session {
216    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217        let mut debug = f.debug_struct("Session");
218        debug
219            .field("browser", &self.inner.browser)
220            .field("platform", &self.inner.platform)
221            .field(
222                "proxy",
223                &self.inner.proxy_config.primary().map(crate::util::redact),
224            )
225            .field("timeout", &self.inner.timeouts.total_limit())
226            .field("protocol_policy", &self.inner.protocol_policy);
227        if let Some(audit) = &self.inner.audit_tls {
228            debug.field("ja4", &audit.ja4);
229        }
230        debug.finish()
231    }
232}
233
234impl std::fmt::Display for Session {
235    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236        let label = match self.inner.browser {
237            Some(browser) => browser.to_string(),
238            None if self.inner.impersonates => self.inner.profile.meta.name.clone(),
239            None => "bare".to_string(),
240        };
241        let config = &self.inner.proxy_config;
242        let proxy = match config.primary() {
243            _ if config.rejects() => "rejected".into(),
244            Some(url) => crate::util::redact(url),
245            None => "none".into(),
246        };
247        write!(
248            f,
249            "Session({label}, {}, proxy={proxy})",
250            self.inner.platform
251        )
252    }
253}