leyline/core/session/
helpers.rs1use http::Method;
2
3use crate::cookie::Jar;
4use crate::profile::{Browser, Platform, 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 let browser = Browser::default_browser();
20 let builder = Self::builder().browser(browser).platform(Platform::Windows);
21 #[cfg(feature = "http3")]
22 let builder = if browser.profile().h3.as_ref().is_some_and(|h3| h3.race) {
23 builder.protocol(super::ProtocolPolicy::Race)
24 } else {
25 builder
26 };
27 builder.into_builtin()
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 fn derive(&self, change: impl FnOnce(&mut super::SessionInner)) -> Self {
59 let mut s = self.clone();
60 change(std::sync::Arc::make_mut(&mut s.inner));
61 s
62 }
63
64 #[cfg(test)]
65 pub(crate) fn browser(&self) -> Option<Browser> {
66 self.inner.browser
67 }
68
69 pub(crate) fn impersonates(&self) -> bool {
70 self.inner.impersonates
71 }
72
73 pub(crate) fn default_retry(&self) -> &crate::core::retry::RetryPolicy {
74 &self.inner.default_retry
75 }
76
77 pub fn pool_stats(&self) -> crate::PoolStats {
78 self.inner.pool.stats()
79 }
80
81 pub async fn preconnect(&self, url: impl IntoUrl) -> Result<()> {
82 let url = url.into_url()?;
83 if url.scheme() != "https" || self.inner.protocol_policy == super::ProtocolPolicy::Http1 {
84 return Ok(());
85 }
86 let host = url.host_str().unwrap_or("");
87 let port = url.port_or_known_default().unwrap_or(443);
88 let proxy = self.proxy_for(&url, None)?;
89 let opened = crate::pool::checkout_handle(
90 &self.inner.pool,
91 &self.inner.connector,
92 &self.inner.h2_config,
93 host,
94 port,
95 proxy,
96 )
97 .await;
98 match opened {
99 Ok(_) => Ok(()),
100 Err(error) if error.alpn().is_some() => {
101 self.inner.pool.note_h1_only(host, port, proxy);
102 Ok(())
103 }
104 Err(error) => Err(error),
105 }
106 }
107
108 pub fn request(&self, method: Method, url: impl IntoUrl) -> RequestBuilder {
109 RequestBuilder::from_url(self, method, url)
110 }
111
112 pub async fn execute(&self, req: http::Request<Body>) -> Result<Response> {
113 let (mut parts, body) = req.into_parts();
114 let mut builder = RequestBuilder::new(self, parts.method, &parts.uri.to_string());
115 for (name, value) in &parts.headers {
116 builder = builder.header(name.clone(), value.clone());
117 }
118 builder = builder.body(body);
119 if let Some(preset) = parts.extensions.remove::<Preset>() {
120 builder = builder.preset(preset);
121 }
122 if let Some(timeouts) = parts.extensions.remove::<TimeoutConfig>() {
123 builder = builder.timeout(timeouts);
124 }
125 if let Some(policy) = parts.extensions.remove::<RetryPolicy>() {
126 builder = builder.retry(policy);
127 }
128 if let Some(policy) = parts.extensions.remove::<RedirectPolicy>() {
129 builder = builder.redirect(policy);
130 }
131 builder.send().await
132 }
133
134 pub fn get(&self, url: impl IntoUrl) -> RequestBuilder {
135 RequestBuilder::from_url(self, Method::GET, url)
136 }
137
138 pub fn post(&self, url: impl IntoUrl) -> RequestBuilder {
139 RequestBuilder::from_url(self, Method::POST, url)
140 }
141
142 pub fn put(&self, url: impl IntoUrl) -> RequestBuilder {
143 RequestBuilder::from_url(self, Method::PUT, url)
144 }
145
146 pub fn patch(&self, url: impl IntoUrl) -> RequestBuilder {
147 RequestBuilder::from_url(self, Method::PATCH, url)
148 }
149
150 pub fn delete(&self, url: impl IntoUrl) -> RequestBuilder {
151 RequestBuilder::from_url(self, Method::DELETE, url)
152 }
153
154 pub fn head(&self, url: impl IntoUrl) -> RequestBuilder {
155 RequestBuilder::from_url(self, Method::HEAD, url)
156 }
157}
158
159impl Default for Session {
160 fn default() -> Self {
161 Self::new()
162 }
163}
164
165impl std::fmt::Debug for Session {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 let mut debug = f.debug_struct("Session");
168 debug
169 .field("browser", &self.inner.browser)
170 .field("platform", &self.inner.platform)
171 .field(
172 "proxy",
173 &self.inner.proxy_config.primary().map(crate::util::redact),
174 )
175 .field("timeout", &self.inner.timeouts.total_limit())
176 .field("protocol_policy", &self.inner.protocol_policy);
177 if let Some(audit) = &self.inner.audit_tls {
178 debug.field("ja4", &audit.ja4);
179 }
180 debug.finish()
181 }
182}
183
184impl std::fmt::Display for Session {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 let label = match self.inner.browser {
187 Some(browser) => browser.to_string(),
188 None if self.inner.impersonates => self.inner.profile.meta.name.clone(),
189 None => "bare".to_string(),
190 };
191 let config = &self.inner.proxy_config;
192 let proxy = match config.primary() {
193 _ if config.rejects() => "rejected".into(),
194 Some(url) => crate::util::redact(url),
195 None => "none".into(),
196 };
197 write!(
198 f,
199 "Session({label}, {}, proxy={proxy})",
200 self.inner.platform
201 )
202 }
203}