salvo-proxy
1
总安装量
1
周安装量
#51323
全站排名
安装命令
npx skills add https://github.com/salvo-rs/salvo-skills --skill salvo-proxy
Agent 安装分布
amp
1
opencode
1
cursor
1
kimi-cli
1
github-copilot
1
Skill 文档
Salvo Reverse Proxy
This skill helps implement reverse proxy functionality in Salvo applications.
What is Reverse Proxy?
A reverse proxy accepts client requests and forwards them to backend servers. Benefits:
- Load Balancing: Distribute requests across multiple servers
- Security: Hide backend server details
- Caching: Cache responses for better performance
- Path Rewriting: Flexibly route requests
Setup
[dependencies]
salvo = { version = "0.89.0", features = ["proxy"] }
Basic Proxy
Using HyperClient (High Performance)
use salvo::prelude::*;
use salvo::proxy::{Proxy, HyperClient};
#[tokio::main]
async fn main() {
// Proxy all requests to backend
let proxy = Proxy::new(
vec!["http://localhost:3000"],
HyperClient::default(),
);
let router = Router::new()
.push(Router::with_path("{**rest}").goal(proxy));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Using ReqwestClient (Simple)
use salvo::prelude::*;
use salvo::proxy::{Proxy, ReqwestClient};
let proxy = Proxy::new(
vec!["http://localhost:3000"],
ReqwestClient::default(),
);
Load Balancing
Distribute requests across multiple backends:
use salvo::prelude::*;
use salvo::proxy::{Proxy, HyperClient};
let proxy = Proxy::new(
vec![
"http://backend1:3000",
"http://backend2:3000",
"http://backend3:3000",
],
HyperClient::default(),
);
let router = Router::new()
.push(Router::with_path("{**rest}").goal(proxy));
Path-Based Routing
Route different paths to different backends:
use salvo::prelude::*;
use salvo::proxy::{Proxy, HyperClient};
#[tokio::main]
async fn main() {
// API requests to API server
let api_proxy = Proxy::new(
vec!["http://api-server:3000"],
HyperClient::default(),
);
// Static files to static server
let static_proxy = Proxy::new(
vec!["http://static-server:8080"],
HyperClient::default(),
);
// WebSocket to WS server
let ws_proxy = Proxy::new(
vec!["http://ws-server:9000"],
HyperClient::default(),
);
let router = Router::new()
.push(Router::with_path("api/{**rest}").goal(api_proxy))
.push(Router::with_path("static/{**rest}").goal(static_proxy))
.push(Router::with_path("ws/{**rest}").goal(ws_proxy));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
API Gateway Pattern
use salvo::prelude::*;
use salvo::proxy::{Proxy, HyperClient};
#[tokio::main]
async fn main() {
// User service
let user_proxy = Proxy::new(
vec!["http://user-service:3001"],
HyperClient::default(),
);
// Order service
let order_proxy = Proxy::new(
vec!["http://order-service:3002"],
HyperClient::default(),
);
// Product service
let product_proxy = Proxy::new(
vec!["http://product-service:3003"],
HyperClient::default(),
);
let router = Router::new()
.push(
Router::with_path("api/v1")
.hoop(auth_middleware) // Apply auth to all routes
.push(Router::with_path("users/{**rest}").goal(user_proxy))
.push(Router::with_path("orders/{**rest}").goal(order_proxy))
.push(Router::with_path("products/{**rest}").goal(product_proxy))
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Proxy with Authentication
Add authentication before proxying:
use salvo::prelude::*;
use salvo::proxy::{Proxy, HyperClient};
#[handler]
async fn auth_middleware(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let token = req.header::<String>("Authorization");
match token {
Some(t) if validate_token(&t) => {
ctrl.call_next(req, depot, res).await;
}
_ => {
res.status_code(StatusCode::UNAUTHORIZED);
res.render("Unauthorized");
ctrl.skip_rest();
}
}
}
#[tokio::main]
async fn main() {
let proxy = Proxy::new(
vec!["http://backend:3000"],
HyperClient::default(),
);
let router = Router::new()
.push(
Router::with_path("api/{**rest}")
.hoop(auth_middleware)
.goal(proxy)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Adding Headers
Add headers before forwarding:
use salvo::prelude::*;
#[handler]
async fn add_proxy_headers(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
// Add forwarded headers
if let Some(addr) = req.remote_addr() {
req.headers_mut().insert("X-Forwarded-For", addr.to_string().parse().unwrap());
}
req.headers_mut().insert("X-Forwarded-Proto", "https".parse().unwrap());
ctrl.call_next(req, depot, res).await;
}
Health Check for Backends
use salvo::prelude::*;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
struct HealthyBackends {
backends: Arc<RwLock<Vec<String>>>,
}
impl HealthyBackends {
async fn get_backends(&self) -> Vec<String> {
self.backends.read().await.clone()
}
async fn update_health(&self, all_backends: &[&str]) {
let mut healthy = Vec::new();
for backend in all_backends {
if check_health(backend).await {
healthy.push(backend.to_string());
}
}
*self.backends.write().await = healthy;
}
}
async fn check_health(backend: &str) -> bool {
// Implement health check logic
reqwest::get(format!("{}/health", backend))
.await
.map(|r| r.status().is_success())
.unwrap_or(false)
}
WebSocket Proxy
Proxy WebSocket connections:
use salvo::prelude::*;
use salvo::proxy::{Proxy, HyperClient};
let ws_proxy = Proxy::new(
vec!["http://ws-backend:9000"],
HyperClient::default(),
);
let router = Router::new()
.push(Router::with_path("ws").goal(ws_proxy));
Rate Limiting at Proxy
use salvo::prelude::*;
use salvo::proxy::{Proxy, HyperClient};
use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
#[tokio::main]
async fn main() {
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_second(100),
);
let proxy = Proxy::new(
vec!["http://backend:3000"],
HyperClient::default(),
);
let router = Router::new()
.push(
Router::with_path("{**rest}")
.hoop(limiter)
.goal(proxy)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Complete Gateway Example
use salvo::prelude::*;
use salvo::proxy::{Proxy, HyperClient};
use salvo::cors::Cors;
use salvo::compression::Compression;
#[handler]
async fn logging(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let start = std::time::Instant::now();
let method = req.method().clone();
let path = req.uri().path().to_string();
ctrl.call_next(req, depot, res).await;
let duration = start.elapsed();
let status = res.status_code().unwrap_or(StatusCode::OK);
println!("{} {} -> {} ({:?})", method, path, status, duration);
}
#[tokio::main]
async fn main() {
let cors = Cors::permissive();
let compression = Compression::new();
// Backend services
let user_proxy = Proxy::new(vec!["http://users:3001"], HyperClient::default());
let order_proxy = Proxy::new(vec!["http://orders:3002"], HyperClient::default());
let router = Router::new()
.hoop(logging)
.hoop(cors)
.hoop(compression)
.push(Router::with_path("users/{**rest}").goal(user_proxy))
.push(Router::with_path("orders/{**rest}").goal(order_proxy));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Best Practices
- Use HyperClient for production: Better performance than ReqwestClient
- Implement health checks: Remove unhealthy backends from rotation
- Add timeout handling: Prevent slow backends from blocking
- Log proxy requests: Monitor traffic patterns
- Set proper headers: X-Forwarded-For, X-Forwarded-Proto
- Apply rate limiting: Protect backends from overload
- Use HTTPS between proxy and backends: Secure internal traffic
- Handle WebSocket upgrades: Ensure protocol support