use http::Method;
use crate::cookie::Jar;
use crate::profile::{Browser, Preset};
use super::{Session, SessionBuilder};
use crate::core::request::RequestBuilder;
use crate::core::retry::RetryPolicy;
use crate::core::{
Body, IntoUrl, ProxyConfig, ProxyUrl, RedirectPolicy, Response, Result, TimeoutConfig,
};
impl Session {
pub fn builder() -> SessionBuilder {
SessionBuilder::new()
}
pub fn new() -> Self {
Self::builder().into_builtin()
}
pub fn browser(browser: Browser) -> Self {
Self::builder().browser(browser).into_builtin()
}
pub fn tab(&self) -> crate::core::Tab {
crate::core::Tab::new(self.clone())
}
pub fn cookies(&self) -> &Jar {
&self.inner.cookie_jar
}
pub fn with_proxy(&self, config: impl Into<ProxyConfig>) -> Self {
let config = config.into();
self.derive(|inner| inner.proxy_config = config)
}
pub fn proxy_url(&self) -> Option<ProxyUrl> {
self.inner.proxy_config.primary().map(ProxyUrl::configured)
}
pub fn fresh_pool(&self) -> Self {
self.derive(|inner| {
inner.pool = std::sync::Arc::new(inner.pool.fresh());
inner.connector = inner.connector.with_fresh_session_cache();
})
}
pub fn with_cookie_jar(&self, jar: Jar) -> Self {
self.derive(|inner| inner.cookie_jar = jar)
}
pub fn with_redirect(&self, policy: RedirectPolicy) -> Self {
self.derive(|inner| inner.redirect_policy = policy)
}
pub fn with_base_url(&self, url: impl IntoUrl) -> Result<Self> {
let url = base_url(url)?;
Ok(self.derive(|inner| inner.base_url = Some(url)))
}
pub(crate) fn base_url(&self) -> Option<&url::Url> {
self.inner.base_url.as_ref()
}
fn derive(&self, change: impl FnOnce(&mut super::SessionInner)) -> Self {
let mut s = self.clone();
change(std::sync::Arc::make_mut(&mut s.inner));
s
}
pub fn host_stats(&self) -> Vec<crate::core::config::HostStats> {
self.inner.host_limits.stats()
}
#[cfg(feature = "html")]
pub(crate) fn host_limits(&self) -> &crate::core::config::HostLimits {
&self.inner.host_limits
}
#[cfg(feature = "html")]
pub(crate) fn upgraded(&self, url: &url::Url) -> url::Url {
url::Url::clone(&self.hsts_upgrade(&std::sync::Arc::new(url.clone())))
}
#[cfg(feature = "html")]
pub(crate) fn with_crawl_pace(&self, pace: crate::core::config::HostLimits) -> Self {
self.derive(|inner| inner.crawl_pace = Some(pace))
}
pub(crate) async fn admit_host(&self, url: &url::Url) -> Option<crate::core::config::HostPass> {
#[cfg(feature = "html")]
let pace = self.inner.crawl_pace.as_ref();
#[cfg(not(feature = "html"))]
let pace = None;
self.inner.host_limits.admit_with(pace, url).await
}
#[cfg(feature = "html")]
pub(crate) fn shut_down_signal(&self) -> tokio_util::sync::WaitForCancellationFutureOwned {
self.inner.shutdown.clone().cancelled_owned()
}
pub(crate) fn observe_host(
&self,
url: &url::Url,
status: http::StatusCode,
wait: Option<std::time::Duration>,
) {
self.inner.host_limits.observe(url, status, wait);
#[cfg(feature = "html")]
if let Some(pace) = &self.inner.crawl_pace {
pace.observe(url, status, wait);
}
}
#[cfg(feature = "html")]
pub(crate) fn crawl_limits(
&self,
polite: impl FnOnce() -> crate::core::config::HostLimits,
) -> crate::core::config::HostLimits {
self.inner
.crawl_limits
.get_or_init(|| polite().armed())
.clone()
}
pub(crate) fn proxy_pool(&self) -> Option<&crate::core::proxy_pool::ProxyPool> {
self.inner.proxy_pool.as_ref()
}
pub(crate) fn without_proxy_pool(&self) -> Self {
self.derive(|inner| inner.proxy_pool = None)
}
pub(crate) fn downgrade(&self) -> std::sync::Weak<super::SessionInner> {
std::sync::Arc::downgrade(&self.inner)
}
pub(crate) fn is_inner(&self, weak: &std::sync::Weak<super::SessionInner>) -> bool {
std::ptr::eq(weak.as_ptr(), std::sync::Arc::as_ptr(&self.inner))
}
pub(crate) fn impersonates(&self) -> bool {
self.inner.impersonates
}
pub(crate) fn default_retry(&self) -> &crate::core::retry::RetryPolicy {
&self.inner.default_retry
}
pub fn pool_stats(&self) -> crate::PoolStats {
self.inner.pool.stats()
}
pub async fn preconnect(&self, url: impl IntoUrl) -> Result<()> {
let url = url.into_url()?;
self.unless_shut_down(self.warm(url)).await
}
async fn warm(&self, url: url::Url) -> Result<()> {
if url.scheme() != "https" || self.inner.protocol_policy == super::ProtocolPolicy::Http1 {
return Ok(());
}
let host = url.host_str().unwrap_or("");
let port = url.port_or_known_default().unwrap_or(443);
let proxy = self.proxy_for(&url, None)?;
let opened = crate::pool::checkout_handle(
&self.inner.pool,
&self.inner.connector,
&self.inner.h2_config,
host,
port,
proxy,
)
.await;
match opened {
Ok(_) => Ok(()),
Err(error) if error.alpn().is_some() => {
self.inner.pool.note_h1_only(host, port, proxy);
Ok(())
}
Err(error) => Err(error),
}
}
pub fn request(&self, method: Method, url: impl IntoUrl) -> RequestBuilder {
RequestBuilder::from_url(self, method, url)
}
pub async fn execute(&self, req: http::Request<Body>) -> Result<Response> {
self.http_request(req).send().await
}
pub(crate) fn http_request(&self, req: http::Request<Body>) -> RequestBuilder {
let (mut parts, body) = req.into_parts();
let mut builder = RequestBuilder::new(self, parts.method, &parts.uri.to_string());
for (name, value) in &parts.headers {
builder = builder.header(name.clone(), value.clone());
}
builder = builder.body(body);
if let Some(preset) = parts.extensions.remove::<Preset>() {
builder = builder.preset(preset);
}
if let Some(timeouts) = parts.extensions.remove::<TimeoutConfig>() {
builder = builder.timeout(timeouts);
}
if let Some(policy) = parts.extensions.remove::<RetryPolicy>() {
builder = builder.retry(policy);
}
if let Some(policy) = parts.extensions.remove::<RedirectPolicy>() {
builder = builder.redirect(policy);
}
if let Some(proxy) = parts.extensions.remove::<ProxyConfig>() {
builder = builder.proxy(proxy);
}
builder
}
pub fn get(&self, url: impl IntoUrl) -> RequestBuilder {
RequestBuilder::from_url(self, Method::GET, url)
}
pub fn post(&self, url: impl IntoUrl) -> RequestBuilder {
RequestBuilder::from_url(self, Method::POST, url)
}
pub fn put(&self, url: impl IntoUrl) -> RequestBuilder {
RequestBuilder::from_url(self, Method::PUT, url)
}
pub fn patch(&self, url: impl IntoUrl) -> RequestBuilder {
RequestBuilder::from_url(self, Method::PATCH, url)
}
pub fn delete(&self, url: impl IntoUrl) -> RequestBuilder {
RequestBuilder::from_url(self, Method::DELETE, url)
}
pub fn head(&self, url: impl IntoUrl) -> RequestBuilder {
RequestBuilder::from_url(self, Method::HEAD, url)
}
}
pub(super) fn base_url(url: impl IntoUrl) -> Result<url::Url> {
let url = url.into_url()?;
if url.cannot_be_a_base() {
return Err(
crate::core::error::Error::new(crate::core::error::Kind::Config)
.with_message(format!("base URL {url} cannot be a base")),
);
}
Ok(url)
}
impl Default for Session {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for Session {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct("Session");
debug
.field("browser", &self.inner.browser)
.field("platform", &self.inner.platform)
.field(
"proxy",
&self.inner.proxy_config.primary().map(crate::util::redact),
)
.field("timeout", &self.inner.timeouts.total_limit())
.field("protocol_policy", &self.inner.protocol_policy);
if let Some(audit) = &self.inner.audit_tls {
debug.field("ja4", &audit.ja4);
}
debug.finish()
}
}
impl std::fmt::Display for Session {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let label = match self.inner.browser {
Some(browser) => browser.to_string(),
None if self.inner.impersonates => self.inner.profile.meta.name.clone(),
None => "bare".to_string(),
};
let config = &self.inner.proxy_config;
let proxy = match config.primary() {
_ if config.rejects() => "rejected".into(),
Some(url) => crate::util::redact(url),
None => "none".into(),
};
write!(
f,
"Session({label}, {}, proxy={proxy})",
self.inner.platform
)
}
}