원클릭으로
salvo-proxy
Implement reverse proxy to forward requests to backend services. Use for load balancing, API gateways, and microservices routing.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Implement reverse proxy to forward requests to backend services. Use for load balancing, API gateways, and microservices routing.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Implement authentication and authorization using JWT, Basic Auth, or custom schemes. Use for securing API endpoints and user management.
Create basic Salvo web applications with handlers, routers, and server setup. Use when starting a new Salvo project or adding basic HTTP endpoints.
Implement caching strategies for improved performance. Use for reducing database load and speeding up responses.
Compress HTTP responses using gzip, brotli, zstd, or deflate. Use for reducing bandwidth and improving load times.
Limit concurrent requests to protect resources. Use for file uploads, expensive operations, and preventing resource exhaustion.
Configure Cross-Origin Resource Sharing (CORS) and security headers. Use for APIs accessed from browsers on different domains.
| name | salvo-proxy |
| description | Implement reverse proxy to forward requests to backend services. Use for load balancing, API gateways, and microservices routing. |
| version | 0.94.0 |
| tags | ["advanced","proxy","reverse-proxy","gateway"] |
[dependencies]
salvo = { version = "0.94.0", features = ["proxy"] }
The proxy feature enables the hyper-client subfeature by default. For
reqwest-client or unix-sock-client, enable those features explicitly on
salvo-proxy.
Proxy::new(upstreams, client) is the full constructor. upstreams is anything
implementing Upstreams — &'static str, String, Vec<_>, [_; N], or a
custom impl. Proxy<_, _> implements Handler, so mount it with .goal(...).
use salvo::prelude::*;
use salvo::proxy::{HyperClient, Proxy};
let proxy = Proxy::new(vec!["http://localhost:3000"], HyperClient::default());
let router = Router::new().push(Router::with_path("{**rest}").goal(proxy));
Shortcut: Proxy::use_hyper_client(upstreams) is equivalent to
Proxy::new(upstreams, HyperClient::default()).
By default, Proxy uses standard_host_header_getter, so the forwarded Host
header includes a non-default upstream port, e.g. backend:3000. If an upstream
requires the old bare-host behavior, configure default_host_header_getter
explicitly.
use salvo::proxy::{Proxy, ReqwestClient};
let proxy = Proxy::new(vec!["http://localhost:3000"], ReqwestClient::default());
client_ip_forwarding(true) (or Proxy::with_client_ip_forwarding(...)) prepends
the caller's address to X-Forwarded-For. Off by default.
let proxy = Proxy::new(vec!["http://backend:3000"], HyperClient::default())
.client_ip_forwarding(true);
Passing multiple upstreams uses a built-in round-robin rotation via the
Vec<_>/slice Upstreams impl:
let proxy = Proxy::new(
vec!["http://backend1:3000", "http://backend2:3000", "http://backend3:3000"],
HyperClient::default(),
);
For custom selection (health-aware, weighted, sticky), implement the Upstreams
trait on your own type.
use salvo::prelude::*;
use salvo::proxy::{HyperClient, Proxy};
#[tokio::main]
async fn main() {
let users = Proxy::new(vec!["http://user-service:3001"], HyperClient::default());
let orders = Proxy::new(vec!["http://order-service:3002"], HyperClient::default());
let products = Proxy::new(vec!["http://product-service:3003"], HyperClient::default());
let router = Router::with_path("api/v1")
.hoop(auth_middleware)
.push(Router::with_path("users/{**rest}").goal(users))
.push(Router::with_path("orders/{**rest}").goal(orders))
.push(Router::with_path("products/{**rest}").goal(products));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
.hoop(...) middleware runs before .goal(proxy), so call ctrl.skip_rest() to
block the proxy when auth fails.
#[handler]
async fn auth_middleware(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
match req.header::<String>("Authorization") {
Some(t) if validate_token(&t) => ctrl.call_next(req, depot, res).await,
_ => {
res.status_code(StatusCode::UNAUTHORIZED);
res.render("Unauthorized");
ctrl.skip_rest();
}
}
}
let router = Router::with_path("api/{**rest}")
.hoop(auth_middleware)
.goal(Proxy::new(vec!["http://backend:3000"], HyperClient::default()));
client_ip_forwarding(true) handles X-Forwarded-For. For other headers
(X-Forwarded-Proto, etc.), mutate the request in a hoop:
#[handler]
async fn add_proxy_headers(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
req.headers_mut().insert("X-Forwarded-Proto", "https".parse().unwrap());
ctrl.call_next(req, depot, res).await;
}
use salvo::proxy::default_host_header_getter;
let proxy = Proxy::new(vec!["http://backend:3000"], HyperClient::default())
.url_path_getter(|req, _depot| {
// Strip "/api" prefix before forwarding
req.uri().path().strip_prefix("/api").map(String::from)
})
.host_header_getter(default_host_header_getter);
url_query_getter is also available for query rewriting.
Security defaults:
strict_path_normalization(true) is enabled by default and rejects ambiguous encoded path traversal before proxying.strip_authorization_header(false) is the default. Set .strip_authorization_header(true) when inbound credentials must not reach the upstream.use salvo::rate_limiter::{BasicQuota, FixedGuard, MokaStore, RateLimiter, RemoteIpIssuer};
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::with_path("{**rest}").hoop(limiter).goal(proxy);
WebSocket upgrades are handled transparently by Proxy — no extra config needed:
let ws_proxy = Proxy::new(vec!["http://ws-backend:9000"], HyperClient::default());
let router = Router::with_path("ws/{**rest}").goal(ws_proxy);
use salvo::compression::Compression;
use salvo::cors::Cors;
use salvo::prelude::*;
use salvo::proxy::{HyperClient, Proxy};
#[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 status = res.status_code().unwrap_or(StatusCode::OK);
println!("{} {} -> {} ({:?})", method, path, status, start.elapsed());
}
#[tokio::main]
async fn main() {
let users = Proxy::new(vec!["http://users:3001"], HyperClient::default())
.client_ip_forwarding(true);
let orders = Proxy::new(vec!["http://orders:3002"], HyperClient::default())
.client_ip_forwarding(true);
let router = Router::new()
.hoop(logging)
.hoop(Cors::permissive().into_handler())
.hoop(Compression::new())
.push(Router::with_path("users/{**rest}").goal(users))
.push(Router::with_path("orders/{**rest}").goal(orders));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}