Skip to main content

stygian_browser/
proxy.rs

1//! Proxy source port for browser context pools.
2//!
3//! This module provides the [`ProxySource`] and [`ProxyLease`] traits that
4//! decouple `stygian-browser` from any concrete proxy implementation.  The
5//! browser crate owns the trait definitions; `stygian-proxy` implements them.
6//!
7//! Wire in a real proxy pool by setting [`BrowserConfigBuilder::proxy_source`](crate::config::BrowserConfigBuilder::proxy_source) to an
8//! `Arc<dyn ProxySource>`.  `stygian-proxy` provides a ready-made
9//! implementation via `ProxyManagerBridge` when compiled with its `browser`
10//! feature.
11//!
12//! # Example
13//!
14//! ```rust,no_run
15//! use std::sync::Arc;
16//! use async_trait::async_trait;
17//! use stygian_browser::proxy::{ProxyLease, ProxySource, DirectLease};
18//! use stygian_browser::error::Result;
19//!
20//! #[derive(Debug)]
21//! struct StaticProxy {
22//!     url: String,
23//! }
24//!
25//! #[async_trait]
26//! impl ProxySource for StaticProxy {
27//!     async fn bind_proxy(&self) -> Result<(String, Box<dyn ProxyLease>)> {
28//!         Ok((self.url.clone(), Box::new(DirectLease)))
29//!     }
30//! }
31//!
32//! let cfg = stygian_browser::BrowserConfig::builder()
33//!     .proxy_source(Arc::new(StaticProxy { url: "http://proxy.example.com:8080".into() }))
34//!     .build();
35//! ```
36
37use std::fmt;
38
39use async_trait::async_trait;
40
41use crate::error::Result;
42
43// ─── ProxyLease ───────────────────────────────────────────────────────────────
44
45/// RAII guard for a proxy acquired from a [`ProxySource`].
46///
47/// Held for the lifetime of the browser instance using the proxy.  Call
48/// [`mark_success`](ProxyLease::mark_success) when the browser session
49/// completes cleanly.  Dropping without calling it signals a failure to the
50/// underlying circuit breaker (if any).
51///
52/// # Example
53///
54/// ```
55/// use stygian_browser::proxy::{ProxyLease, DirectLease};
56/// let lease: Box<dyn ProxyLease> = Box::new(DirectLease);
57/// lease.mark_success(); // no-op for DirectLease
58/// ```
59pub trait ProxyLease: Send + Sync + 'static {
60    /// Record that the browser session using this proxy completed successfully.
61    fn mark_success(&self);
62}
63
64// ─── DirectLease ─────────────────────────────────────────────────────────────
65
66/// A no-op [`ProxyLease`] for use when no proxy is configured.
67///
68/// All methods are no-ops.  Use this as the lease type in [`ProxySource`]
69/// implementations that do not need circuit-breaker tracking.
70///
71/// # Example
72///
73/// ```
74/// use stygian_browser::proxy::{ProxyLease, DirectLease};
75/// let lease = DirectLease;
76/// lease.mark_success(); // no-op
77/// ```
78pub struct DirectLease;
79
80impl ProxyLease for DirectLease {
81    fn mark_success(&self) {}
82}
83
84// ─── ProxySource ──────────────────────────────────────────────────────────────
85
86/// Source of proxies for browser context pools.
87///
88/// Implement this trait and pass an `Arc<dyn ProxySource>` to
89/// [`BrowserConfig::builder().proxy_source(...)`](crate::config::BrowserConfigBuilder::proxy_source)
90/// to enable per-context proxy rotation with circuit-breaker support.
91///
92/// Each call to [`bind_proxy`](ProxySource::bind_proxy) acquires a proxy URL
93/// and an RAII [`ProxyLease`] that must be held for the lifetime of the
94/// browser instance.
95///
96/// `stygian-proxy` provides a ready-made implementation via
97/// `ProxyManagerBridge` when compiled with the `browser` feature.
98///
99/// # Example
100///
101/// ```rust,no_run
102/// use std::sync::Arc;
103/// use async_trait::async_trait;
104/// use stygian_browser::proxy::{ProxyLease, ProxySource, DirectLease};
105/// use stygian_browser::error::Result;
106///
107/// #[derive(Debug)]
108/// struct RoundRobinProxy {
109///     urls: Vec<String>,
110/// }
111///
112/// #[async_trait]
113/// impl ProxySource for RoundRobinProxy {
114///     async fn bind_proxy(&self) -> Result<(String, Box<dyn ProxyLease>)> {
115///         let url = self.urls[0].clone(); // simplified — real impl would rotate
116///         Ok((url, Box::new(DirectLease)))
117///     }
118/// }
119///
120/// let source = Arc::new(RoundRobinProxy { urls: vec!["http://p.example.com:8080".into()] });
121/// let cfg = stygian_browser::BrowserConfig::builder()
122///     .proxy_source(source)
123///     .build();
124/// ```
125#[async_trait]
126pub trait ProxySource: Send + Sync + fmt::Debug + 'static {
127    /// Acquire the next proxy URL and an RAII lease handle.
128    ///
129    /// The returned `(url, lease)` pair must be used together:
130    /// - `url` is passed as the `--proxy-server` Chrome launch argument.
131    /// - `lease` must be held for the lifetime of that browser instance.
132    ///   Call [`ProxyLease::mark_success`] on clean session exit.
133    ///
134    /// # Errors
135    ///
136    /// Returns [`crate::error::BrowserError::ProxyUnavailable`] if no proxy
137    /// is currently available (e.g. circuit breaker open, pool empty).
138    async fn bind_proxy(&self) -> Result<(String, Box<dyn ProxyLease>)>;
139}