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 #[cfg(feature = "html")]
78 pub(crate) fn host_limits(&self) -> &crate::core::config::HostLimits {
79 &self.inner.host_limits
80 }
81
82 #[cfg(feature = "html")]
83 pub(crate) fn upgraded(&self, url: &url::Url) -> url::Url {
84 url::Url::clone(&self.hsts_upgrade(&std::sync::Arc::new(url.clone())))
85 }
86
87 #[cfg(feature = "html")]
88 pub(crate) fn with_crawl_pace(&self, pace: crate::core::config::HostLimits) -> Self {
89 self.derive(|inner| inner.crawl_pace = Some(pace))
90 }
91
92 pub(crate) async fn admit_host(&self, url: &url::Url) -> Option<crate::core::config::HostPass> {
93 #[cfg(feature = "html")]
94 let pace = self.inner.crawl_pace.as_ref();
95 #[cfg(not(feature = "html"))]
96 let pace = None;
97 self.inner.host_limits.admit_with(pace, url).await
98 }
99
100 #[cfg(feature = "html")]
101 pub(crate) fn shut_down_signal(&self) -> tokio_util::sync::WaitForCancellationFutureOwned {
102 self.inner.shutdown.clone().cancelled_owned()
103 }
104
105 pub(crate) fn observe_host(
106 &self,
107 url: &url::Url,
108 status: http::StatusCode,
109 wait: Option<std::time::Duration>,
110 ) {
111 self.inner.host_limits.observe(url, status, wait);
112 #[cfg(feature = "html")]
113 if let Some(pace) = &self.inner.crawl_pace {
114 pace.observe(url, status, wait);
115 }
116 }
117
118 #[cfg(feature = "html")]
119 pub(crate) fn crawl_limits(
120 &self,
121 polite: impl FnOnce() -> crate::core::config::HostLimits,
122 ) -> crate::core::config::HostLimits {
123 self.inner
124 .crawl_limits
125 .get_or_init(|| polite().armed())
126 .clone()
127 }
128
129 pub(crate) fn proxy_pool(&self) -> Option<&crate::core::proxy_pool::ProxyPool> {
130 self.inner.proxy_pool.as_ref()
131 }
132
133 pub(crate) fn without_proxy_pool(&self) -> Self {
134 self.derive(|inner| inner.proxy_pool = None)
135 }
136
137 pub(crate) fn downgrade(&self) -> std::sync::Weak<super::SessionInner> {
138 std::sync::Arc::downgrade(&self.inner)
139 }
140
141 pub(crate) fn is_inner(&self, weak: &std::sync::Weak<super::SessionInner>) -> bool {
142 std::ptr::eq(weak.as_ptr(), std::sync::Arc::as_ptr(&self.inner))
143 }
144
145 pub(crate) fn impersonates(&self) -> bool {
146 self.inner.impersonates
147 }
148
149 pub(crate) fn default_retry(&self) -> &crate::core::retry::RetryPolicy {
150 &self.inner.default_retry
151 }
152
153 pub fn pool_stats(&self) -> crate::PoolStats {
154 self.inner.pool.stats()
155 }
156
157 pub async fn preconnect(&self, url: impl IntoUrl) -> Result<()> {
158 let url = url.into_url()?;
159 self.unless_shut_down(self.warm(url)).await
160 }
161
162 async fn warm(&self, url: url::Url) -> Result<()> {
163 if url.scheme() != "https" || self.inner.protocol_policy == super::ProtocolPolicy::Http1 {
164 return Ok(());
165 }
166 let host = url.host_str().unwrap_or("");
167 let port = url.port_or_known_default().unwrap_or(443);
168 let proxy = self.proxy_for(&url, None)?;
169 let opened = crate::pool::checkout_handle(
170 &self.inner.pool,
171 &self.inner.connector,
172 &self.inner.h2_config,
173 host,
174 port,
175 proxy,
176 )
177 .await;
178 match opened {
179 Ok(_) => Ok(()),
180 Err(error) if error.alpn().is_some() => {
181 self.inner.pool.note_h1_only(host, port, proxy);
182 Ok(())
183 }
184 Err(error) => Err(error),
185 }
186 }
187
188 pub fn request(&self, method: Method, url: impl IntoUrl) -> RequestBuilder {
189 RequestBuilder::from_url(self, method, url)
190 }
191
192 pub async fn execute(&self, req: http::Request<Body>) -> Result<Response> {
193 self.http_request(req).send().await
194 }
195
196 pub(crate) fn http_request(&self, req: http::Request<Body>) -> RequestBuilder {
197 let (mut parts, body) = req.into_parts();
198 let mut builder = RequestBuilder::new(self, parts.method, &parts.uri.to_string());
199 for (name, value) in &parts.headers {
200 builder = builder.header(name.clone(), value.clone());
201 }
202 builder = builder.body(body);
203 if let Some(preset) = parts.extensions.remove::<Preset>() {
204 builder = builder.preset(preset);
205 }
206 if let Some(timeouts) = parts.extensions.remove::<TimeoutConfig>() {
207 builder = builder.timeout(timeouts);
208 }
209 if let Some(policy) = parts.extensions.remove::<RetryPolicy>() {
210 builder = builder.retry(policy);
211 }
212 if let Some(policy) = parts.extensions.remove::<RedirectPolicy>() {
213 builder = builder.redirect(policy);
214 }
215 if let Some(proxy) = parts.extensions.remove::<ProxyConfig>() {
216 builder = builder.proxy(proxy);
217 }
218 builder
219 }
220
221 pub fn get(&self, url: impl IntoUrl) -> RequestBuilder {
222 RequestBuilder::from_url(self, Method::GET, url)
223 }
224
225 pub fn post(&self, url: impl IntoUrl) -> RequestBuilder {
226 RequestBuilder::from_url(self, Method::POST, url)
227 }
228
229 pub fn put(&self, url: impl IntoUrl) -> RequestBuilder {
230 RequestBuilder::from_url(self, Method::PUT, url)
231 }
232
233 pub fn patch(&self, url: impl IntoUrl) -> RequestBuilder {
234 RequestBuilder::from_url(self, Method::PATCH, url)
235 }
236
237 pub fn delete(&self, url: impl IntoUrl) -> RequestBuilder {
238 RequestBuilder::from_url(self, Method::DELETE, url)
239 }
240
241 pub fn head(&self, url: impl IntoUrl) -> RequestBuilder {
242 RequestBuilder::from_url(self, Method::HEAD, url)
243 }
244}
245
246pub(super) fn base_url(url: impl IntoUrl) -> Result<url::Url> {
247 let url = url.into_url()?;
248 if url.cannot_be_a_base() {
249 return Err(
250 crate::core::error::Error::new(crate::core::error::Kind::Config)
251 .with_message(format!("base URL {url} cannot be a base")),
252 );
253 }
254 Ok(url)
255}
256
257impl Default for Session {
258 fn default() -> Self {
259 Self::new()
260 }
261}
262
263impl std::fmt::Debug for Session {
264 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
265 let mut debug = f.debug_struct("Session");
266 debug
267 .field("browser", &self.inner.browser)
268 .field("platform", &self.inner.platform)
269 .field(
270 "proxy",
271 &self.inner.proxy_config.primary().map(crate::util::redact),
272 )
273 .field("timeout", &self.inner.timeouts.total_limit())
274 .field("protocol_policy", &self.inner.protocol_policy);
275 if let Some(audit) = &self.inner.audit_tls {
276 debug.field("ja4", &audit.ja4);
277 }
278 debug.finish()
279 }
280}
281
282impl std::fmt::Display for Session {
283 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284 let label = match self.inner.browser {
285 Some(browser) => browser.to_string(),
286 None if self.inner.impersonates => self.inner.profile.meta.name.clone(),
287 None => "bare".to_string(),
288 };
289 let config = &self.inner.proxy_config;
290 let proxy = match config.primary() {
291 _ if config.rejects() => "rejected".into(),
292 Some(url) => crate::util::redact(url),
293 None => "none".into(),
294 };
295 write!(
296 f,
297 "Session({label}, {}, proxy={proxy})",
298 self.inner.platform
299 )
300 }
301}