Expand description
A flexible and efficient reverse proxy implementation for Axum web applications.
This crate provides a reverse proxy that can be easily integrated into Axum applications, allowing for seamless forwarding of HTTP requests and WebSocket connections to upstream servers. It supports:
- Path-based routing
- Optional retry mechanism via a
tower::Layer
- Header forwarding
- Configurable HTTP client settings
- Round-robin load balancing across multiple upstreams
- WebSocket proxying with:
- Automatic upgrade handling
- Bidirectional message forwarding
- Text and binary message support
- Proper close frame handling
- Easy integration with Axum’s Router
- Full Tower middleware support
§Basic Example
use axum::Router;
use axum_reverse_proxy::ReverseProxy;
// Create a reverse proxy that forwards requests from /api to httpbin.org
let proxy = ReverseProxy::new("/api", "https://httpbin.org");
// Convert the proxy to a router and use it in your Axum application
let app: Router = proxy.into();
§Load Balanced Example
use axum::Router;
use axum_reverse_proxy::BalancedProxy;
let proxy = BalancedProxy::new("/api", vec!["https://api1.example.com", "https://api2.example.com"]);
let app: Router = proxy.into();
§Service Discovery Example
For dynamic service discovery, use the DiscoverableBalancedProxy
with any implementation
of the tower::discover::Discover
trait:
use axum::Router;
use axum_reverse_proxy::{DiscoverableBalancedProxy, LoadBalancingStrategy};
use futures_util::stream::Stream;
use tower::discover::Change;
use std::pin::Pin;
use std::task::{Context, Poll};
// Example discovery stream that implements Stream<Item = Result<Change<Key, Service>, Error>>
#[derive(Clone)]
struct MyDiscoveryStream {
// Your discovery implementation here
}
impl Stream for MyDiscoveryStream {
type Item = Result<Change<usize, String>, Box<dyn std::error::Error + Send + Sync>>;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
// Poll your service discovery system here
Poll::Pending
}
}
use hyper_util::client::legacy::{connect::HttpConnector, Client};
let discovery = MyDiscoveryStream { /* ... */ };
let connector = HttpConnector::new();
let client = Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector);
// Use round-robin load balancing (default)
let mut proxy = DiscoverableBalancedProxy::new_with_client("/api", client.clone(), discovery.clone());
// Or specify a load balancing strategy
let mut proxy_p2c = DiscoverableBalancedProxy::new_with_client_and_strategy(
"/api",
client,
discovery,
LoadBalancingStrategy::P2cPendingRequests,
);
proxy.start_discovery().await;
let app: Router = Router::new().nest_service("/", proxy);
§DNS-Based Service Discovery
For DNS-based service discovery, use the built-in DnsDiscovery
(requires the dns
feature):
[dependencies]
axum-reverse-proxy = { version = "*", features = ["dns"] }
# Or to enable all features:
# axum-reverse-proxy = { version = "*", features = ["full"] }
use axum::Router;
use axum_reverse_proxy::{DiscoverableBalancedProxy, DnsDiscovery, DnsDiscoveryConfig};
use std::time::Duration;
// Create DNS discovery configuration
let dns_config = DnsDiscoveryConfig::new("api.example.com", 80)
.with_refresh_interval(Duration::from_secs(30))
.with_https(false);
// Create the DNS discovery instance
let discovery = DnsDiscovery::new(dns_config).expect("Failed to create DNS discovery");
// Create the discoverable balanced proxy with DNS discovery
let mut proxy = DiscoverableBalancedProxy::new_with_client("/api",
hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
.build(hyper_util::client::legacy::connect::HttpConnector::new()),
discovery);
// Start the discovery process
proxy.start_discovery().await;
let app: Router = Router::new().nest_service("/", proxy);
§Load Balancing Strategies
The DiscoverableBalancedProxy
supports multiple load balancing strategies:
RoundRobin
(default): Simple round-robin distribution, good for homogeneous servicesP2cPendingRequests
: Power of Two Choices algorithm using pending request count as load metricP2cPeakEwma
: Power of Two Choices algorithm using peak EWMA latency as load metric
// Round-robin (default)
let proxy_rr = DiscoverableBalancedProxy::new_with_client("/api", client.clone(), discovery.clone());
// P2C with pending requests load measurement
let proxy_p2c = DiscoverableBalancedProxy::new_with_client_and_strategy(
"/api", client, discovery, LoadBalancingStrategy::P2cPendingRequests
);
§Using Tower Middleware
The proxy integrates seamlessly with Tower middleware, allowing you to transform requests and responses, add authentication, logging, timeouts, and more:
use axum::{body::Body, Router};
use axum_reverse_proxy::ReverseProxy;
use http::Request;
use tower::ServiceBuilder;
use tower_http::{
timeout::TimeoutLayer,
validate_request::ValidateRequestHeaderLayer,
};
use std::time::Duration;
// Create a reverse proxy
let proxy = ReverseProxy::new("/api", "https://api.example.com");
// Convert to router
let proxy_router: Router = proxy.into();
// Add middleware layers
let app = proxy_router.layer(
ServiceBuilder::new()
// Add request timeout
.layer(TimeoutLayer::new(Duration::from_secs(10)))
// Require API key
.layer(ValidateRequestHeaderLayer::bearer("secret-token"))
// Transform requests
.map_request(|mut req: Request<Body>| {
req.headers_mut().insert(
"X-Custom-Header",
"custom-value".parse().unwrap(),
);
req
})
);
Common middleware use cases include:
- Request/response transformation
- Authentication and authorization
- Rate limiting
- Request validation
- Logging and tracing
- Timeouts and retries
- Caching
- Compression
- Request buffering (via tower-buffer)
See the tower_middleware
example for a complete working example.
§State Management
You can merge the proxy with an existing router that has state:
use axum::{routing::get, Router, response::IntoResponse, extract::State};
use axum_reverse_proxy::ReverseProxy;
#[derive(Clone)]
struct AppState { foo: usize }
async fn root_handler(State(state): State<AppState>) -> impl IntoResponse {
(axum::http::StatusCode::OK, format!("Hello, World! {}", state.foo))
}
let app: Router<AppState> = Router::new()
.route("/", get(root_handler))
.merge(ReverseProxy::new("/api", "https://httpbin.org"))
.with_state(AppState { foo: 42 });
§WebSocket Support
The proxy automatically detects WebSocket upgrade requests and handles them appropriately:
use axum::Router;
use axum_reverse_proxy::ReverseProxy;
// Create a reverse proxy that forwards both HTTP and WebSocket requests
let proxy = ReverseProxy::new("/ws", "http://websocket.example.com");
// WebSocket connections to /ws will be automatically proxied
let app: Router = proxy.into();
The proxy handles:
- WebSocket upgrade handshake
- Bidirectional message forwarding
- Text and binary messages
- Ping/Pong frames
- Connection close frames
- Multiple concurrent connections
§TLS Configuration
By default, this library uses rustls for TLS connections, which provides a pure-Rust, secure, and modern TLS implementation.
§Default TLS (rustls)
[dependencies]
axum-reverse-proxy = "1.0"
# or explicitly enable the default TLS feature
axum-reverse-proxy = { version = "1.0", features = ["tls"] }
§Using native-tls
If you need to use the system’s native TLS implementation (OpenSSL on Linux,
Secure Transport on macOS, SChannel on Windows), you can opt into the native-tls
feature:
[dependencies]
axum-reverse-proxy = { version = "1.0", features = ["native-tls"] }
§Feature Combinations
default = ["tls"]
- Uses rustls (recommended)features = ["native-tls"]
- Uses native TLS implementationfeatures = ["tls", "native-tls"]
- Both available, native-tls takes precedencefeatures = ["full"]
- Includestls
(rustls) anddns
featuresfeatures = []
- No TLS support (HTTP only)
Note: When both tls
and native-tls
features are enabled, native-tls
takes precedence
since explicit selection of native-tls indicates a preference for the system’s TLS implementation.
The native-tls
feature is a separate opt-in and is not included in the full
feature set.
Structs§
- Balanced
Proxy - Discoverable
Balanced Proxy - A balanced proxy that supports dynamic service discovery.
- Retry
Layer - Reverse
Proxy - A reverse proxy that forwards HTTP requests to an upstream server.
- Rfc9110
Config - Configuration for RFC9110 middleware
- Rfc9110
Layer - Layer that applies RFC9110 middleware
Enums§
- Load
Balancing Strategy - Load balancing strategy for distributing requests across discovered services