rama_proxy/lib.rs
1//! rama proxy types and utilities
2//!
3//! Proxy protocols are implemented in their relevant crates:
4//!
5//! - HaProxy: `rama-haproxy`
6//! - HttpProxy: `rama-http-backend`
7//!
8//! See the [`ProxyFilter`] for more information on how to select a proxy,
9//! and the [`ProxyDB`] trait for how to implement a proxy database.
10//!
11//! If you wish to support proxy filters directly from the username,
12//! you can use the [`ProxyFilterUsernameParser`] to extract the proxy filter
13//! so it will be added to the input [`Extensions`].
14//!
15//! The [`ProxyDB`] is used by Connection Pools to connect via a proxy,
16//! in case a [`ProxyFilter`] is present in the input [`Extensions`].
17//!
18//! # DB Live Reloads
19//!
20//! [`ProxyDB`] implementations like the [`MemoryProxyDB`] feel static in nature, and they are.
21//! The goal is really to load it once and read it often as fast as possible.
22//!
23//! In fact, given that we access everything through shared references,
24//! there is also no cheap way to mutate it all the time.
25//!
26//! As such the normal way to update data such as your proxy list
27//! is by performing a rolling update of your actual rama-driven proxy workloads.
28//!
29//! That said. By using crates such as [left-right](https://crates.io/crates/left-right)
30//! you can relatively affordable perform live reloads by having the writer on its own tokio
31//! task and wrap the reader in a [`ProxyDB`] implementation. This way you can live reload based upon
32//! a signal, or more realistically, every x minutes.
33//!
34//! [`Extensions`]: rama_core::extensions::Extensions
35//!
36//! ## ProxyDB layer
37//!
38//! [`ProxyDB`] layer support to select a proxy based on the given input [`Extensions`].
39//!
40//! This layer expects a [`ProxyFilter`] to be available in the input [`Extensions`],
41//! which can be added by using the `HeaderConfigLayer` (`rama-http`)
42//! when operating on the HTTP layer and/or by parsing it via the TCP proxy username labels (e.g. `john-country-us-residential`),
43//! in case you support that as part of your transport-layer authentication. And of course you can
44//! combine the two approaches.
45//!
46//! You can also give a single [`Proxy`] as "proxy db".
47//!
48//! By default the layer publishes at most five matching proxies as an ordered
49//! [`ProxyRoutes`] plan. The limit is configurable, including an unbounded mode.
50//! Each route carries its [`Proxy`] and [`ProxyID`] as route-specific
51//! extensions. [`ProxyRoutesConnector`] installs only the selected route's
52//! metadata on its isolated connection attempt and returned input.
53//!
54//! [`ProxyDBLayer::with_single_proxy`] opts into the legacy behaviour of
55//! using the database's singular selection and inserting one route.
56//! `MemoryProxyDB` keeps its random selection for this mode. A database miss
57//! remains an error in either mode; no direct route is appended implicitly.
58//! Existing singular routes are preserved by default in both modes.
59//! [`ProxyDBLayer::with_overwrite_proxy`] explicitly lets the database selection
60//! replace them by publishing the newer decision. [`ProxyRoutesLayer`] resolves
61//! singular and plural decisions by insertion order before route-aware
62//! middleware runs.
63//!
64//! [`ProxyRoute`]: rama_net::client::ProxyRoute
65//! [`ProxyRoutes`]: rama_net::client::ProxyRoutes
66//! [`ProxyRoutesLayer`]: rama_net::client::ProxyRoutesLayer
67//! [`ProxyRoutesConnector`]: rama_net::client::ProxyRoutesConnector
68//! [`ProxyDB`]: ProxyDB
69//!
70//! # Example
71//!
72//! ```rust
73//! use rama_http_types::{Body, Version, Request};
74//! use rama_proxy::{
75//! Proxy,
76//! ProxyDBLayer, ProxyFilterMode,
77//! ProxyFilter,
78//! };
79//! # #[cfg(feature = "memory-db")]
80//! # use rama_proxy::MemoryProxyDB;
81//! use rama_core::{
82//! service::service_fn,
83//! extensions::{ExtensionsRef},
84//! Service, Layer,
85//! };
86//! use rama_net::client::ProxyRoute;
87//! use rama_utils::str::non_empty_str;
88//! use std::{convert::Infallible, sync::Arc};
89//!
90//! # #[cfg(feature = "memory-db")]
91//! #[tokio::main]
92//! async fn main() {
93//! let db = MemoryProxyDB::try_from_iter([
94//! Proxy {
95//! id: non_empty_str!("42"),
96//! address: "12.34.12.34:8080".try_into().unwrap(),
97//! tcp: true,
98//! udp: true,
99//! http: true,
100//! https: false,
101//! socks5: true,
102//! socks5h: false,
103//! datacenter: false,
104//! residential: true,
105//! mobile: true,
106//! pool_id: None,
107//! continent: Some("*".into()),
108//! country: Some("*".into()),
109//! state: Some("*".into()),
110//! city: Some("*".into()),
111//! carrier: Some("*".into()),
112//! asn: None,
113//! },
114//! Proxy {
115//! id: non_empty_str!("100"),
116//! address: "123.123.123.123:8080".try_into().unwrap(),
117//! tcp: true,
118//! udp: false,
119//! http: true,
120//! https: false,
121//! socks5: false,
122//! socks5h: false,
123//! datacenter: true,
124//! residential: false,
125//! mobile: false,
126//! pool_id: None,
127//! continent: None,
128//! country: Some("US".into()),
129//! state: None,
130//! city: None,
131//! carrier: None,
132//! asn: None,
133//! },
134//! ])
135//! .unwrap();
136//!
137//! // Singular mode keeps this small example independent of a connector.
138//! // The normal client stack uses the default plural mode together with
139//! // ProxyRoutesConnector.
140//! let service = ProxyDBLayer::new(Arc::new(db))
141//! .with_filter_mode(ProxyFilterMode::Default)
142//! .with_single_proxy(true)
143//! .into_layer(service_fn(async |req: Request| {
144//! Ok::<_, Infallible>(req)
145//! }));
146//!
147//! let req = Request::builder()
148//! .version(Version::HTTP_3)
149//! .method("GET")
150//! .uri("https://example.com")
151//! .body(Body::empty())
152//! .unwrap();
153//!
154//! req.extensions().insert(ProxyFilter {
155//! country: Some(vec!["BE".into()]),
156//! mobile: Some(true),
157//! residential: Some(true),
158//! ..Default::default()
159//! });
160//!
161//! let output = service.serve(req).await.unwrap();
162//! let proxy_address = output
163//! .extensions()
164//! .get_ref::<ProxyRoute>()
165//! .and_then(ProxyRoute::proxy_address)
166//! .unwrap();
167//! assert_eq!(proxy_address.address.to_string(), "12.34.12.34:8080");
168//! assert_eq!(output.extensions().get_ref::<Proxy>().unwrap().id, "42");
169//! }
170//! # #[cfg(not(feature = "memory-db"))]
171//! # fn main() {}
172//! ```
173//!
174//! ## Single Proxy Router
175//!
176//! Another example is a single proxy through which
177//! one can connect with config for further downstream proxies
178//! passed by username labels.
179//!
180//! Note that the username formatter is available for any proxy db,
181//! it is not specific to the usage of a single proxy.
182//!
183//! ```rust
184//! use rama_http_types::{Body, Version, Request};
185//! use rama_proxy::{
186//! Proxy,
187//! ProxyDBLayer, ProxyFilterMode,
188//! ProxyFilter,
189//! };
190//! use rama_core::{
191//! service::service_fn,
192//! extensions::{ExtensionsRef},
193//! Service, Layer,
194//! };
195//! use rama_net::client::ProxyRoute;
196//! use rama_utils::str::non_empty_str;
197//! use std::{convert::Infallible, sync::Arc};
198//!
199//! #[tokio::main]
200//! async fn main() {
201//! let proxy = Proxy {
202//! id: non_empty_str!("1"),
203//! address: "john:secret@proxy.example.com:60000".try_into().unwrap(),
204//! tcp: true,
205//! udp: true,
206//! http: true,
207//! https: false,
208//! socks5: true,
209//! socks5h: false,
210//! datacenter: false,
211//! residential: true,
212//! mobile: false,
213//! pool_id: None,
214//! continent: Some("*".into()),
215//! country: Some("*".into()),
216//! state: Some("*".into()),
217//! city: Some("*".into()),
218//! carrier: Some("*".into()),
219//! asn: None,
220//! };
221//!
222//! let service = ProxyDBLayer::new(Arc::new(proxy))
223//! .with_filter_mode(ProxyFilterMode::Default)
224//! .with_single_proxy(true)
225//! .with_username_formatter(|_proxy: &Proxy, filter: &ProxyFilter, username: &str| {
226//! use std::fmt::Write;
227//!
228//! let mut output = String::new();
229//!
230//! if let Some(countries) =
231//! filter.country.as_ref().filter(|t| !t.is_empty())
232//! {
233//! _ = write!(output, "country-{}", countries[0]);
234//! }
235//! if let Some(states) =
236//! filter.state.as_ref().filter(|t| !t.is_empty())
237//! {
238//! _ = write!(output, "state-{}", states[0]);
239//! }
240//!
241//! (!output.is_empty()).then(|| format!("{username}-{output}"))
242//! })
243//! .into_layer(service_fn(async |req: Request| {
244//! Ok::<_, Infallible>(req)
245//! }));
246//!
247//! let req = Request::builder()
248//! .version(Version::HTTP_3)
249//! .method("GET")
250//! .uri("https://example.com")
251//! .body(Body::empty())
252//! .unwrap();
253//! req.extensions().insert(ProxyFilter {
254//! country: Some(vec!["BE".into()]),
255//! residential: Some(true),
256//! ..Default::default()
257//! });
258//! let output = service.serve(req).await.unwrap();
259//! let proxy_address = output
260//! .extensions()
261//! .get_ref::<ProxyRoute>()
262//! .and_then(ProxyRoute::proxy_address)
263//! .unwrap();
264//! assert_eq!(
265//! "socks5://john-country-be:secret@proxy.example.com:60000",
266//! proxy_address.to_string()
267//! );
268//! }
269//! ```
270
271#![doc(
272 html_favicon_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/rama_logo.svg"
273)]
274#![doc(
275 html_logo_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/rama_logo.svg"
276)]
277#![cfg_attr(docsrs, feature(doc_cfg))]
278#![cfg_attr(test, allow(clippy::float_cmp))]
279
280mod username;
281#[doc(inline)]
282pub use username::ProxyFilterUsernameParser;
283
284mod proxydb;
285
286#[doc(inline)]
287pub use proxydb::{
288 Proxy, ProxyContext, ProxyDB, ProxyFilter, ProxyID, ProxyQueryPredicate, StringFilter,
289};
290
291#[doc(inline)]
292pub use proxydb::layer::{
293 DEFAULT_PROXY_DB_MAX_PROXIES, ProxyDBLayer, ProxyDBService, ProxyFilterMode, UsernameFormatter,
294};
295
296#[cfg(feature = "live-update")]
297#[cfg_attr(docsrs, doc(cfg(feature = "live-update")))]
298#[doc(inline)]
299pub use proxydb::{LiveUpdateProxyDB, LiveUpdateProxyDBSetter, proxy_db_updater};
300
301#[cfg(feature = "memory-db")]
302#[cfg_attr(docsrs, doc(cfg(feature = "memory-db")))]
303#[doc(inline)]
304pub use proxydb::{
305 MemoryProxyDB, MemoryProxyDBInsertError, MemoryProxyDBInsertErrorKind, MemoryProxyDBQueryError,
306 MemoryProxyDBQueryErrorKind,
307};
308
309#[cfg(feature = "csv")]
310#[cfg_attr(docsrs, doc(cfg(feature = "csv")))]
311#[doc(inline)]
312pub use proxydb::{ProxyCsvRowReader, ProxyCsvRowReaderError, ProxyCsvRowReaderErrorKind};