Skip to content

Commit 4924b6f

Browse files
committed
docs(proxy): add README.md and wire readme field in Cargo.toml
1 parent 7374ddd commit 4924b6f

File tree

2 files changed

+212
-0
lines changed

2 files changed

+212
-0
lines changed

crates/stygian-proxy/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ rust-version.workspace = true
77
authors.workspace = true
88
license.workspace = true
99
repository.workspace = true
10+
readme = "README.md"
1011
keywords = ["proxy", "rotation", "scraping", "http", "socks"]
1112
categories = ["network-programming", "web-programming"]
1213

crates/stygian-proxy/README.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# stygian-proxy
2+
3+
High-performance, resilient proxy rotation for the Stygian scraping ecosystem.
4+
5+
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](../../LICENSE)
6+
[![Crates.io](https://img.shields.io/crates/v/stygian-proxy.svg)](https://crates.io/crates/stygian-proxy)
7+
8+
---
9+
10+
## Features
11+
12+
| Feature | Description |
13+
| --------- | ------------- |
14+
| **Rotation strategies** | Round-robin, random, weighted, least-used — all pluggable via trait |
15+
| **Circuit breakers** | Per-proxy Closed → Open → HalfOpen state machine; unhealthy proxies are skipped automatically |
16+
| **Health checking** | Background async health checker with configurable interval and per-proxy scoring |
17+
| **RAII proxy handles** | `ProxyHandle` records success/failure on drop; no manual bookkeeping |
18+
| **SOCKS support** | SOCKS4/5 via reqwest (`socks` feature flag) |
19+
| **Graph integration** | `ProxyManagerPort` trait wires into stygian-graph HTTP adapters (`graph` feature) |
20+
| **Browser integration** | `BrowserProxySource` binds pool proxies into stygian-browser contexts (`browser` feature) |
21+
| **Zero external deps** | In-memory storage only — no database required |
22+
23+
---
24+
25+
## Installation
26+
27+
```toml
28+
[dependencies]
29+
stygian-proxy = "0.1"
30+
tokio = { version = "1", features = ["full"] }
31+
```
32+
33+
Enable optional features:
34+
35+
```toml
36+
# SOCKS4/5 proxy support
37+
stygian-proxy = { version = "0.1", features = ["socks"] }
38+
39+
# Integration with stygian-graph HTTP adapters
40+
stygian-proxy = { version = "0.1", features = ["graph"] }
41+
42+
# Integration with stygian-browser pool
43+
stygian-proxy = { version = "0.1", features = ["browser"] }
44+
```
45+
46+
---
47+
48+
## Quick Start
49+
50+
```rust,no_run
51+
use stygian_proxy::{ProxyManager, Proxy, types::ProxyType};
52+
use std::time::Duration;
53+
54+
#[tokio::main]
55+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
56+
// Build a pool with round-robin rotation
57+
let manager = ProxyManager::with_round_robin(Default::default())?;
58+
59+
// Add proxies
60+
manager.add_proxy(Proxy {
61+
url: "http://proxy1.example.com:8080".into(),
62+
proxy_type: ProxyType::Http,
63+
username: None,
64+
password: None,
65+
}).await?;
66+
67+
manager.add_proxy(Proxy {
68+
url: "http://proxy2.example.com:8080".into(),
69+
proxy_type: ProxyType::Http,
70+
username: Some("user".into()),
71+
password: Some("pass".into()),
72+
}).await?;
73+
74+
// Start the background health checker
75+
let (cancel, _task) = manager.start();
76+
77+
// Acquire a proxy — skips any with an open circuit breaker
78+
let handle = manager.acquire_proxy().await?;
79+
println!("Using proxy: {}", handle.proxy_url);
80+
81+
// Mark success to keep the circuit breaker closed
82+
handle.mark_success();
83+
84+
// Pool stats
85+
let stats = manager.pool_stats().await?;
86+
println!("Pool: {}/{} healthy, {} open", stats.healthy, stats.total, stats.open);
87+
88+
cancel.cancel();
89+
Ok(())
90+
}
91+
```
92+
93+
---
94+
95+
## Rotation Strategies
96+
97+
| Strategy | Constructor | Behaviour |
98+
| --------- | ----------- | --------- |
99+
| `RoundRobinStrategy` | `ProxyManager::with_round_robin` | Cycles through healthy proxies in order |
100+
| `RandomStrategy` | `ProxyManager::with_random` | Picks a healthy proxy at random each time |
101+
| `WeightedStrategy` | `ProxyManager::with_weighted` | Selects proportionally to each proxy's `weight` field |
102+
| `LeastUsedStrategy` | `ProxyManager::with_least_used` | Prefers the proxy with the lowest total request count |
103+
104+
Custom strategies implement `RotationStrategy`:
105+
106+
```rust,no_run
107+
use stygian_proxy::strategy::{RotationStrategy, ProxyCandidate};
108+
109+
#[derive(Debug)]
110+
struct MyStrategy;
111+
112+
impl RotationStrategy for MyStrategy {
113+
fn select<'a>(&self, candidates: &'a [ProxyCandidate]) -> Option<&'a ProxyCandidate> {
114+
// pick the candidate with the best success rate
115+
candidates.iter().max_by(|a, b| {
116+
a.metrics.success_rate().partial_cmp(&b.metrics.success_rate())
117+
.unwrap_or(std::cmp::Ordering::Equal)
118+
})
119+
}
120+
}
121+
```
122+
123+
---
124+
125+
## Circuit Breaker
126+
127+
Each proxy has its own `CircuitBreaker`. After `circuit_open_threshold` consecutive failures the breaker opens, and the proxy is excluded from rotation for `circuit_half_open_after`. After that window the proxy is tried once in HalfOpen state — a success closes it; another failure reopens it.
128+
129+
```rust,no_run
130+
use stygian_proxy::{ProxyManager, types::ProxyConfig};
131+
use std::time::Duration;
132+
133+
let config = ProxyConfig {
134+
// Open after 5 consecutive failures
135+
circuit_open_threshold: 5,
136+
// Try again after 60 seconds
137+
circuit_half_open_after: Duration::from_secs(60),
138+
..Default::default()
139+
};
140+
141+
let manager = ProxyManager::with_round_robin(config)?;
142+
```
143+
144+
If a `ProxyHandle` is dropped without calling `mark_success()`, the circuit breaker records a failure automatically.
145+
146+
---
147+
148+
## Health Checking
149+
150+
`ProxyManager::start()` spawns a background task that probes each proxy on a configurable interval and updates per-proxy health scores:
151+
152+
```rust,no_run
153+
use stygian_proxy::{ProxyManager, types::ProxyConfig};
154+
use std::time::Duration;
155+
156+
let config = ProxyConfig {
157+
health_check_interval: Duration::from_secs(30),
158+
health_check_timeout: Duration::from_secs(5),
159+
..Default::default()
160+
};
161+
162+
let manager = ProxyManager::with_round_robin(config)?;
163+
let (cancel_token, health_task) = manager.start();
164+
165+
// Graceful shutdown
166+
cancel_token.cancel();
167+
```
168+
169+
---
170+
171+
## stygian-graph Integration
172+
173+
With the `graph` feature, the pool implements `ProxyManagerPort` so stygian-graph adapters can rotate proxies per-request:
174+
175+
```toml
176+
stygian-proxy = { version = "0.1", features = ["graph"] }
177+
stygian-graph = "0.1"
178+
```
179+
180+
```rust,no_run
181+
use stygian_proxy::{ProxyManager, graph::ProxyManagerPort};
182+
183+
let manager = ProxyManager::with_round_robin(Default::default())?;
184+
// Pass as Arc<dyn ProxyManagerPort> to RestApiAdapter or HttpAdapter
185+
```
186+
187+
---
188+
189+
## stygian-browser Integration
190+
191+
With the `browser` feature, `BrowserProxySource` feeds live pool proxies into stygian-browser contexts:
192+
193+
```toml
194+
stygian-proxy = { version = "0.1", features = ["browser"] }
195+
stygian-browser = "0.1"
196+
```
197+
198+
```rust,no_run
199+
use stygian_proxy::{ProxyManager, browser::ProxyManagerBridge};
200+
use stygian_browser::BrowserConfig;
201+
202+
let manager = std::sync::Arc::new(ProxyManager::with_round_robin(Default::default())?);
203+
let bridge = ProxyManagerBridge::new(manager);
204+
// bridge.next_proxy_url().await? → inject into BrowserConfig::proxy
205+
```
206+
207+
---
208+
209+
## License
210+
211+
`AGPL-3.0-only OR LicenseRef-Commercial` — see [LICENSE](../../LICENSE) and [LICENSE-COMMERCIAL.md](../../LICENSE-COMMERCIAL.md).

0 commit comments

Comments
 (0)