5 releases
Uses new Rust 2024
| 0.3.16 | Dec 11, 2025 |
|---|---|
| 0.3.15 | Dec 11, 2025 |
| 0.3.14 | Dec 3, 2025 |
| 0.3.13 | Dec 2, 2025 |
| 0.3.12 | Dec 2, 2025 |
#1802 in HTTP server
110KB
2K
SLoC
lmrc-proxy
HTTP reverse proxy and API gateway utilities for LMRC Stack applications.
A flexible, trait-based reverse proxy library for building API gateways and microservices routers with Axum.
Features
- HTTP Reverse Proxy: Full-featured HTTP proxying with configurable behavior
- Subdomain Routing: Route requests based on subdomains
- Flexible Route Resolution: Trait-based routing for custom backend discovery
- Axum Integration: Ready-to-use middleware for Axum web framework
- Header Management: Automatic handling of hop-by-hop headers and X-Forwarded-* headers
- Type-Safe: Leverages Rust's type system for safe proxy operations
Installation
[dependencies]
lmrc-proxy = "0.3.11"
Quick Start
Basic Reverse Proxy
use lmrc_proxy::{proxy_request, ProxyConfig};
use axum::{Router, routing::any, extract::Request};
async fn proxy_handler(request: Request) -> Result<axum::response::Response, lmrc_proxy::ProxyError> {
proxy_request(request, "http://backend:8080", ProxyConfig::default()).await
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/*path", any(proxy_handler));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Subdomain-Based Gateway
use lmrc_proxy::{
routing::StaticRouteResolver,
middleware::{subdomain_extractor, subdomain_proxy},
ProxyConfig,
};
use axum::{Router, middleware};
use std::sync::Arc;
#[tokio::main]
async fn main() {
// Configure routes
let resolver = Arc::new(
StaticRouteResolver::new()
.add_route("api", "http://api-service:8080")
.add_route("admin", "http://admin-service:9000")
.add_route("auth", "http://auth-service:8081")
);
// Build router with subdomain routing
let app = Router::new()
.layer(middleware::from_fn(subdomain_extractor))
.layer(middleware::from_fn_with_state(
(resolver, ProxyConfig::default()),
subdomain_proxy
));
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Requests to api.example.com route to http://api-service:8080, requests to admin.example.com route to http://admin-service:9000, etc.
Core Concepts
ProxyConfig
Configure proxy behavior:
use lmrc_proxy::ProxyConfig;
use std::time::Duration;
let config = ProxyConfig {
preserve_host: false, // Forward original Host header
timeout: Duration::from_secs(30), // Request timeout
max_body_size: 10 * 1024 * 1024, // 10MB max body
add_forwarded_headers: true, // Add X-Forwarded-* headers
};
RouteResolver Trait
Implement custom routing logic:
use lmrc_proxy::routing::RouteResolver;
use async_trait::async_trait;
struct DatabaseRouteResolver {
// Database connection
}
#[async_trait]
impl RouteResolver for DatabaseRouteResolver {
async fn resolve(&self, subdomain: &str) -> Option<String> {
// Query database for backend URL
self.db.get_backend_url(subdomain).await
}
}
Subdomain Extraction
Extract subdomains from requests:
use lmrc_proxy::routing::extract_subdomain;
assert_eq!(extract_subdomain("api.example.com"), Some("api".to_string()));
assert_eq!(extract_subdomain("example.com"), None);
assert_eq!(extract_subdomain("localhost"), Some("infra".to_string())); // Development
Middleware
subdomain_extractor
Extracts subdomain from Host header and stores it in request extensions.
use axum::{Router, middleware};
use lmrc_proxy::middleware::subdomain_extractor;
let app = Router::new()
.layer(middleware::from_fn(subdomain_extractor));
subdomain_proxy
Routes requests to backends based on subdomain.
use lmrc_proxy::{middleware::subdomain_proxy, routing::StaticRouteResolver, ProxyConfig};
use axum::{Router, middleware};
use std::sync::Arc;
let resolver = Arc::new(StaticRouteResolver::new()
.add_route("api", "http://api-backend:8080"));
let app = Router::new()
.layer(middleware::from_fn_with_state(
(resolver, ProxyConfig::default()),
subdomain_proxy
));
Advanced Usage
Custom Route Resolver with Caching
use lmrc_proxy::routing::RouteResolver;
use async_trait::async_trait;
use std::collections::HashMap;
use std::sync::RwLock;
struct CachedRouteResolver {
cache: RwLock<HashMap<String, String>>,
backend: Box<dyn RouteResolver>,
}
#[async_trait]
impl RouteResolver for CachedRouteResolver {
async fn resolve(&self, key: &str) -> Option<String> {
// Check cache first
if let Some(url) = self.cache.read().unwrap().get(key) {
return Some(url.clone());
}
// Query backend
if let Some(url) = self.backend.resolve(key).await {
self.cache.write().unwrap().insert(key.to_string(), url.clone());
Some(url)
} else {
None
}
}
}
Dynamic Backend Discovery
use lmrc_proxy::routing::RouteResolver;
use async_trait::async_trait;
struct ServiceDiscoveryResolver {
consul_client: ConsulClient,
}
#[async_trait]
impl RouteResolver for ServiceDiscoveryResolver {
async fn resolve(&self, service_name: &str) -> Option<String> {
// Query service discovery (Consul, etcd, etc.)
self.consul_client
.get_service_address(service_name)
.await
.ok()
}
}
Configuration Examples
Development Setup
use lmrc_proxy::{routing::StaticRouteResolver, ProxyConfig};
let resolver = StaticRouteResolver::new()
.add_route("api", "http://localhost:8081")
.add_route("admin", "http://localhost:8082");
Production Setup
use std::time::Duration;
use lmrc_proxy::ProxyConfig;
let config = ProxyConfig {
preserve_host: false,
timeout: Duration::from_secs(60),
max_body_size: 50 * 1024 * 1024, // 50MB
add_forwarded_headers: true,
};
Testing
cargo test -p lmrc-proxy
Integration with lmrc-http-common
Works seamlessly with lmrc-http-common for error handling, logging, and HTTP utilities.
use lmrc_http_common::middleware::{add_request_id, log_request};
use lmrc_proxy::middleware::{subdomain_extractor, subdomain_proxy};
use axum::{Router, middleware};
let app = Router::new()
.layer(middleware::from_fn(log_request))
.layer(middleware::from_fn(add_request_id))
.layer(middleware::from_fn(subdomain_extractor))
.layer(middleware::from_fn_with_state(state, subdomain_proxy));
Error Handling
All errors implement IntoResponse for seamless Axum integration:
ProxyError::ClientCreation- Failed to create HTTP clientProxyError::RequestBody- Failed to read request bodyProxyError::BackendRequest- Backend request failed (returns 502 Bad Gateway)ProxyError::ResponseBody- Failed to read response bodyProxyError::RouteNotFound- No route found (returns 404 Not Found)
Performance Considerations
- Connection Pooling: Uses
reqwestwith automatic connection pooling - Body Streaming: Efficiently handles large request/response bodies
- Header Filtering: Automatically filters hop-by-hop headers
- Timeout Management: Configurable timeouts prevent hung connections
Security
- Header Sanitization: Hop-by-hop headers are automatically filtered
- X-Forwarded Headers: Properly sets X-Forwarded-Host for backend awareness
- Body Size Limits: Configurable max body size prevents memory exhaustion
- Timeout Protection: Request timeouts prevent resource exhaustion
Examples
See the gateway app for a complete example of an API gateway using lmrc-proxy.
Contributing
This library is part of the LMRC Stack monorepo.
License
Dual licensed under MIT OR Apache-2.0 (your choice).
Dependencies
~13–22MB
~315K SLoC