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 [`Context`]'s [`Extensions`].
14//!
15//! The [`ProxyDB`] is used by Connection Pools to connect via a proxy,
16//! in case a [`ProxyFilter`] is present in the [`Context`]'s [`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//! [`Context`]: rama_core::Context
35//! [`Extensions`]: rama_core::context::Extensions
36//!
37//! ## ProxyDB layer
38//!
39//! [`ProxyDB`] layer support to select a proxy based on the given [`Context`].
40//!
41//! This layer expects a [`ProxyFilter`] to be available in the [`Context`],
42//! which can be added by using the `HeaderConfigLayer` (`rama-http`)
43//! when operating on the HTTP layer and/or by parsing it via the TCP proxy username labels (e.g. `john-country-us-residential`),
44//! in case you support that as part of your transport-layer authentication. And of course you can
45//! combine the two approaches.
46//!
47//! You can also give a single [`Proxy`] as "proxy db".
48//!
49//! The end result is that a [`ProxyAddress`] will be set in case a proxy was selected,
50//! an error is returned in case no proxy could be selected while one was expected
51//! or of course because the inner [`Service`][`rama_core::Service`] failed.
52//!
53//! [`ProxyAddress`]: rama_net::address::ProxyAddress
54//! [`ProxyDB`]: ProxyDB
55//! [`Context`]: rama_core::Context
56//!
57//! # Example
58//!
59//! ```rust
60//! use rama_http_types::{Body, Version, Request};
61//! use rama_proxy::{
62//! MemoryProxyDB, MemoryProxyDBQueryError, ProxyCsvRowReader, Proxy,
63//! ProxyDBLayer, ProxyFilterMode,
64//! ProxyFilter,
65//! };
66//! use rama_core::{
67//! service::service_fn,
68//! Context, Service, Layer,
69//! };
70//! use rama_net::address::ProxyAddress;
71//! use rama_utils::str::NonEmptyString;
72//! use itertools::Itertools;
73//! use std::{convert::Infallible, sync::Arc};
74//!
75//! #[tokio::main]
76//! async fn main() {
77//! let db = MemoryProxyDB::try_from_iter([
78//! Proxy {
79//! id: NonEmptyString::from_static("42"),
80//! address: "12.34.12.34:8080".try_into().unwrap(),
81//! tcp: true,
82//! udp: true,
83//! http: true,
84//! https: false,
85//! socks5: true,
86//! socks5h: false,
87//! datacenter: false,
88//! residential: true,
89//! mobile: true,
90//! pool_id: None,
91//! continent: Some("*".into()),
92//! country: Some("*".into()),
93//! state: Some("*".into()),
94//! city: Some("*".into()),
95//! carrier: Some("*".into()),
96//! asn: None,
97//! },
98//! Proxy {
99//! id: NonEmptyString::from_static("100"),
100//! address: "123.123.123.123:8080".try_into().unwrap(),
101//! tcp: true,
102//! udp: false,
103//! http: true,
104//! https: false,
105//! socks5: false,
106//! socks5h: false,
107//! datacenter: true,
108//! residential: false,
109//! mobile: false,
110//! pool_id: None,
111//! continent: None,
112//! country: Some("US".into()),
113//! state: None,
114//! city: None,
115//! carrier: None,
116//! asn: None,
117//! },
118//! ])
119//! .unwrap();
120//!
121//! let service =
122//! ProxyDBLayer::new(Arc::new(db)).filter_mode(ProxyFilterMode::Default)
123//! .into_layer(service_fn(async |ctx: Context<()>, _: Request| {
124//! Ok::<_, Infallible>(ctx.get::<ProxyAddress>().unwrap().clone())
125//! }));
126//!
127//! let mut ctx = Context::default();
128//! ctx.insert(ProxyFilter {
129//! country: Some(vec!["BE".into()]),
130//! mobile: Some(true),
131//! residential: Some(true),
132//! ..Default::default()
133//! });
134//!
135//! let req = Request::builder()
136//! .version(Version::HTTP_3)
137//! .method("GET")
138//! .uri("https://example.com")
139//! .body(Body::empty())
140//! .unwrap();
141//!
142//! let proxy_address = service.serve(ctx, req).await.unwrap();
143//! assert_eq!(proxy_address.authority.to_string(), "12.34.12.34:8080");
144//! }
145//! ```
146//!
147//! ## Single Proxy Router
148//!
149//! Another example is a single proxy through which
150//! one can connect with config for further downstream proxies
151//! passed by username labels.
152//!
153//! Note that the username formatter is available for any proxy db,
154//! it is not specific to the usage of a single proxy.
155//!
156//! ```rust
157//! use rama_http_types::{Body, Version, Request};
158//! use rama_proxy::{
159//! Proxy,
160//! ProxyDBLayer, ProxyFilterMode,
161//! ProxyFilter,
162//! };
163//! use rama_core::{
164//! service::service_fn,
165//! Context, Service, Layer,
166//! };
167//! use rama_net::address::ProxyAddress;
168//! use rama_utils::str::NonEmptyString;
169//! use itertools::Itertools;
170//! use std::{convert::Infallible, sync::Arc};
171//!
172//! #[tokio::main]
173//! async fn main() {
174//! let proxy = Proxy {
175//! id: NonEmptyString::from_static("1"),
176//! address: "john:secret@proxy.example.com:60000".try_into().unwrap(),
177//! tcp: true,
178//! udp: true,
179//! http: true,
180//! https: false,
181//! socks5: true,
182//! socks5h: false,
183//! datacenter: false,
184//! residential: true,
185//! mobile: false,
186//! pool_id: None,
187//! continent: Some("*".into()),
188//! country: Some("*".into()),
189//! state: Some("*".into()),
190//! city: Some("*".into()),
191//! carrier: Some("*".into()),
192//! asn: None,
193//! };
194//!
195//! let service = ProxyDBLayer::new(Arc::new(proxy))
196//! .filter_mode(ProxyFilterMode::Default)
197//! .username_formatter(|_ctx: &Context<()>, proxy: &Proxy, filter: &ProxyFilter, username: &str| {
198//! use std::fmt::Write;
199//!
200//! let mut output = String::new();
201//!
202//! if let Some(countries) =
203//! filter.country.as_ref().filter(|t| !t.is_empty())
204//! {
205//! let _ = write!(output, "country-{}", countries[0]);
206//! }
207//! if let Some(states) =
208//! filter.state.as_ref().filter(|t| !t.is_empty())
209//! {
210//! let _ = write!(output, "state-{}", states[0]);
211//! }
212//!
213//! (!output.is_empty()).then(|| format!("{username}-{output}"))
214//! })
215//! .into_layer(service_fn(async |ctx: Context<()>, _: Request| {
216//! Ok::<_, Infallible>(ctx.get::<ProxyAddress>().unwrap().clone())
217//! }));
218//!
219//! let mut ctx = Context::default();
220//! ctx.insert(ProxyFilter {
221//! country: Some(vec!["BE".into()]),
222//! residential: Some(true),
223//! ..Default::default()
224//! });
225//!
226//! let req = Request::builder()
227//! .version(Version::HTTP_3)
228//! .method("GET")
229//! .uri("https://example.com")
230//! .body(Body::empty())
231//! .unwrap();
232//!
233//! let proxy_address = service.serve(ctx, req).await.unwrap();
234//! assert_eq!(
235//! "socks5://john-country-be:secret@proxy.example.com:60000",
236//! proxy_address.to_string()
237//! );
238//! }
239//! ```
240
241#![doc(
242 html_favicon_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/old_logo.png"
243)]
244#![doc(html_logo_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/old_logo.png")]
245#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))]
246#![cfg_attr(test, allow(clippy::float_cmp))]
247#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))]
248
249mod username;
250#[doc(inline)]
251pub use username::ProxyFilterUsernameParser;
252
253mod proxydb;
254
255#[doc(inline)]
256pub use proxydb::{
257 Proxy, ProxyContext, ProxyDB, ProxyFilter, ProxyID, ProxyQueryPredicate, StringFilter,
258};
259
260#[doc(inline)]
261pub use proxydb::layer::{ProxyDBLayer, ProxyDBService, ProxyFilterMode, UsernameFormatter};
262
263#[cfg(feature = "live-update")]
264#[doc(inline)]
265pub use proxydb::{LiveUpdateProxyDB, LiveUpdateProxyDBSetter, proxy_db_updater};
266
267#[cfg(feature = "memory-db")]
268#[doc(inline)]
269pub use proxydb::{
270 MemoryProxyDB, MemoryProxyDBInsertError, MemoryProxyDBInsertErrorKind, MemoryProxyDBQueryError,
271 MemoryProxyDBQueryErrorKind,
272};
273
274#[cfg(feature = "csv")]
275#[doc(inline)]
276pub use proxydb::{ProxyCsvRowReader, ProxyCsvRowReaderError, ProxyCsvRowReaderErrorKind};