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};