axum_reverse_proxy/lib.rs
1//! A flexible and efficient reverse proxy implementation for Axum web applications.
2//!
3//! This crate provides a reverse proxy that can be easily integrated into Axum applications,
4//! allowing for seamless forwarding of HTTP requests and WebSocket connections to upstream servers.
5//! It supports:
6//!
7//! - Path-based routing
8//! - Optional retry mechanism via a [`tower::Layer`]
9//! - Header forwarding
10//! - Configurable HTTP client settings
11//! - Round-robin load balancing across multiple upstreams
12//! - WebSocket proxying with:
13//! - Automatic upgrade handling
14//! - Bidirectional message forwarding
15//! - Text and binary message support
16//! - Proper close frame handling
17//! - Easy integration with Axum's Router
18//! - Full Tower middleware support
19//!
20//! # Basic Example
21//!
22//! ```rust
23//! use axum::Router;
24//! use axum_reverse_proxy::ReverseProxy;
25//!
26//! // Create a reverse proxy that forwards requests from /api to httpbin.org
27//! let proxy = ReverseProxy::new("/api", "https://httpbin.org");
28//!
29//! // Convert the proxy to a router and use it in your Axum application
30//! let app: Router = proxy.into();
31//! ```
32//!
33//! # Load Balanced Example
34//!
35//! ```rust
36//! use axum::Router;
37//! use axum_reverse_proxy::BalancedProxy;
38//!
39//! let proxy = BalancedProxy::new("/api", vec!["https://api1.example.com", "https://api2.example.com"]);
40//! let app: Router = proxy.into();
41//! ```
42//!
43//! # Service Discovery Example
44//!
45//! For dynamic service discovery, use the `DiscoverableBalancedProxy` with any implementation
46//! of the `tower::discover::Discover` trait:
47//!
48//! ```rust,no_run
49//! use axum::Router;
50//! use axum_reverse_proxy::{DiscoverableBalancedProxy, LoadBalancingStrategy};
51//! use futures_util::stream::Stream;
52//! use tower::discover::Change;
53//! use std::pin::Pin;
54//! use std::task::{Context, Poll};
55//!
56//! // Example discovery stream that implements Stream<Item = Result<Change<Key, Service>, Error>>
57//! #[derive(Clone)]
58//! struct MyDiscoveryStream {
59//! // Your discovery implementation here
60//! }
61//!
62//! impl Stream for MyDiscoveryStream {
63//! type Item = Result<Change<usize, String>, Box<dyn std::error::Error + Send + Sync>>;
64//!
65//! fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
66//! // Poll your service discovery system here
67//! Poll::Pending
68//! }
69//! }
70//!
71//! # async fn example() {
72//! use hyper_util::client::legacy::{connect::HttpConnector, Client};
73//!
74//! let discovery = MyDiscoveryStream { /* ... */ };
75//! let connector = HttpConnector::new();
76//! let client = Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector);
77//!
78//! // Use round-robin load balancing (default)
79//! let mut proxy = DiscoverableBalancedProxy::new_with_client("/api", client.clone(), discovery.clone());
80//!
81//! // Or specify a load balancing strategy
82//! let mut proxy_p2c = DiscoverableBalancedProxy::new_with_client_and_strategy(
83//! "/api",
84//! client,
85//! discovery,
86//! LoadBalancingStrategy::P2cPendingRequests,
87//! );
88//!
89//! proxy.start_discovery().await;
90//!
91//! let app: Router = Router::new().nest_service("/", proxy);
92//! # }
93//! ```
94//!
95//! # DNS-Based Service Discovery
96//!
97//! For DNS-based service discovery, use the built-in `DnsDiscovery` (requires the `dns` feature):
98//!
99//! ```toml
100//! [dependencies]
101//! axum-reverse-proxy = { version = "*", features = ["dns"] }
102//! # Or to enable all features:
103//! # axum-reverse-proxy = { version = "*", features = ["full"] }
104//! ```
105//!
106//! ```rust,no_run
107//! # #[cfg(feature = "dns")]
108//! # {
109//! use axum::Router;
110//! use axum_reverse_proxy::{DiscoverableBalancedProxy, DnsDiscovery, DnsDiscoveryConfig};
111//! use std::time::Duration;
112//!
113//! # async fn example() {
114//! // Create DNS discovery configuration
115//! let dns_config = DnsDiscoveryConfig::new("api.example.com", 80)
116//! .with_refresh_interval(Duration::from_secs(30))
117//! .with_https(false);
118//!
119//! // Create the DNS discovery instance
120//! let discovery = DnsDiscovery::new(dns_config).expect("Failed to create DNS discovery");
121//!
122//! // Create the discoverable balanced proxy with DNS discovery
123//! let mut proxy = DiscoverableBalancedProxy::new_with_client("/api",
124//! hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
125//! .build(hyper_util::client::legacy::connect::HttpConnector::new()),
126//! discovery);
127//!
128//! // Start the discovery process
129//! proxy.start_discovery().await;
130//!
131//! let app: Router = Router::new().nest_service("/", proxy);
132//! # }
133//! # }
134//! ```
135//!
136//! # Load Balancing Strategies
137//!
138//! The `DiscoverableBalancedProxy` supports multiple load balancing strategies:
139//!
140//! - **`RoundRobin`** (default): Simple round-robin distribution, good for homogeneous services
141//! - **`P2cPendingRequests`**: Power of Two Choices algorithm using pending request count as load metric
142//! - **`P2cPeakEwma`**: Power of Two Choices algorithm using peak EWMA latency as load metric
143//!
144//! ```rust,no_run
145//! # use axum_reverse_proxy::{DiscoverableBalancedProxy, LoadBalancingStrategy};
146//! # use hyper_util::client::legacy::{connect::HttpConnector, Client};
147//! # use futures_util::stream::Stream;
148//! # use tower::discover::Change;
149//! # use std::pin::Pin;
150//! # use std::task::{Context, Poll};
151//! # #[derive(Clone)]
152//! # struct MyDiscoveryStream;
153//! # impl Stream for MyDiscoveryStream {
154//! # type Item = Result<Change<usize, String>, Box<dyn std::error::Error + Send + Sync>>;
155//! # fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
156//! # Poll::Pending
157//! # }
158//! # }
159//! # let discovery = MyDiscoveryStream;
160//! # let connector = HttpConnector::new();
161//! # let client = Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector);
162//!
163//! // Round-robin (default)
164//! let proxy_rr = DiscoverableBalancedProxy::new_with_client("/api", client.clone(), discovery.clone());
165//!
166//! // P2C with pending requests load measurement
167//! let proxy_p2c = DiscoverableBalancedProxy::new_with_client_and_strategy(
168//! "/api", client, discovery, LoadBalancingStrategy::P2cPendingRequests
169//! );
170//! ```
171//!
172//! # Using Tower Middleware
173//!
174//! The proxy integrates seamlessly with Tower middleware, allowing you to transform requests
175//! and responses, add authentication, logging, timeouts, and more:
176//!
177//! ```rust
178//! use axum::{body::Body, Router};
179//! use axum_reverse_proxy::ReverseProxy;
180//! use http::Request;
181//! use tower::ServiceBuilder;
182//! use tower_http::{
183//! timeout::TimeoutLayer,
184//! validate_request::ValidateRequestHeaderLayer,
185//! };
186//! use std::time::Duration;
187//!
188//! // Create a reverse proxy
189//! let proxy = ReverseProxy::new("/api", "https://api.example.com");
190//!
191//! // Convert to router
192//! let proxy_router: Router = proxy.into();
193//!
194//! // Add middleware layers
195//! let app = proxy_router.layer(
196//! ServiceBuilder::new()
197//! // Add request timeout
198//! .layer(TimeoutLayer::new(Duration::from_secs(10)))
199//! // Require API key
200//! .layer(ValidateRequestHeaderLayer::bearer("secret-token"))
201//! // Transform requests
202//! .map_request(|mut req: Request<Body>| {
203//! req.headers_mut().insert(
204//! "X-Custom-Header",
205//! "custom-value".parse().unwrap(),
206//! );
207//! req
208//! })
209//! );
210//! ```
211//!
212//! Common middleware use cases include:
213//! - Request/response transformation
214//! - Authentication and authorization
215//! - Rate limiting
216//! - Request validation
217//! - Logging and tracing
218//! - Timeouts and retries
219//! - Caching
220//! - Compression
221//! - Request buffering (via tower-buffer)
222//!
223//! See the `tower_middleware` example for a complete working example.
224//!
225//! # State Management
226//!
227//! You can merge the proxy with an existing router that has state:
228//!
229//! ```rust
230//! use axum::{routing::get, Router, response::IntoResponse, extract::State};
231//! use axum_reverse_proxy::ReverseProxy;
232//!
233//! #[derive(Clone)]
234//! struct AppState { foo: usize }
235//!
236//! async fn root_handler(State(state): State<AppState>) -> impl IntoResponse {
237//! (axum::http::StatusCode::OK, format!("Hello, World! {}", state.foo))
238//! }
239//!
240//! let app: Router<AppState> = Router::new()
241//! .route("/", get(root_handler))
242//! .merge(ReverseProxy::new("/api", "https://httpbin.org"))
243//! .with_state(AppState { foo: 42 });
244//! ```
245//!
246//! # WebSocket Support
247//!
248//! The proxy automatically detects WebSocket upgrade requests and handles them appropriately:
249//!
250//! ```rust
251//! use axum::Router;
252//! use axum_reverse_proxy::ReverseProxy;
253//!
254//! // Create a reverse proxy that forwards both HTTP and WebSocket requests
255//! let proxy = ReverseProxy::new("/ws", "http://websocket.example.com");
256//!
257//! // WebSocket connections to /ws will be automatically proxied
258//! let app: Router = proxy.into();
259//! ```
260//!
261//! The proxy handles:
262//! - WebSocket upgrade handshake
263//! - Bidirectional message forwarding
264//! - Text and binary messages
265//! - Ping/Pong frames
266//! - Connection close frames
267//! - Multiple concurrent connections
268//!
269//! # TLS Configuration
270//!
271//! By default, this library uses [rustls](https://github.com/rustls/rustls) for TLS connections,
272//! which provides a pure-Rust, secure, and modern TLS implementation.
273//!
274//! ## Default TLS (rustls)
275//!
276//! ```toml
277//! [dependencies]
278//! axum-reverse-proxy = "1.0"
279//! # or explicitly enable the default TLS feature
280//! axum-reverse-proxy = { version = "1.0", features = ["tls"] }
281//! ```
282//!
283//! ## Using native-tls
284//!
285//! If you need to use the system's native TLS implementation (OpenSSL on Linux,
286//! Secure Transport on macOS, SChannel on Windows), you can opt into the `native-tls` feature:
287//!
288//! ```toml
289//! [dependencies]
290//! axum-reverse-proxy = { version = "1.0", features = ["native-tls"] }
291//! ```
292//!
293//! ## Feature Combinations
294//!
295//! - `default = ["tls"]` - Uses rustls (recommended)
296//! - `features = ["native-tls"]` - Uses native TLS implementation
297//! - `features = ["tls", "native-tls"]` - Both available, native-tls takes precedence
298//! - `features = ["full"]` - Includes `tls` (rustls) and `dns` features
299//! - `features = []` - No TLS support (HTTP only)
300//!
301//! **Note:** When both `tls` and `native-tls` features are enabled, `native-tls` takes precedence
302//! since explicit selection of native-tls indicates a preference for the system's TLS implementation.
303//! The `native-tls` feature is a separate opt-in and is not included in the `full` feature set.
304
305mod balanced_proxy;
306#[cfg(any(feature = "tls", feature = "native-tls"))]
307mod danger;
308#[cfg(feature = "dns")]
309mod dns_discovery;
310mod proxy;
311mod retry;
312mod rfc9110;
313mod router;
314mod websocket;
315
316pub use balanced_proxy::BalancedProxy;
317pub use balanced_proxy::DiscoverableBalancedProxy;
318pub use balanced_proxy::LoadBalancingStrategy;
319pub use balanced_proxy::StandardBalancedProxy;
320pub use balanced_proxy::StandardDiscoverableBalancedProxy;
321#[cfg(feature = "native-tls")]
322pub use danger::create_dangerous_native_tls_connector;
323#[cfg(all(feature = "tls", not(feature = "native-tls")))]
324pub use danger::create_dangerous_rustls_config;
325#[cfg(feature = "dns")]
326pub use dns_discovery::{DnsDiscovery, DnsDiscoveryConfig, StaticDnsDiscovery};
327pub use proxy::ReverseProxy;
328pub use retry::RetryLayer;
329pub use rfc9110::{Rfc9110Config, Rfc9110Layer};