Reverse Proxy

A reverse proxy is a server architecture that receives requests from clients and forwards them to one or more backend servers. Unlike a forward proxy (which acts on behalf of clients), a reverse proxy operates on behalf of the server side.

Key advantages of reverse proxies:

  • Load Balancing: Distributes requests across multiple servers
  • Enhanced Security: Hides real server information
  • Content Caching: Improves performance
  • Path Rewriting and Forwarding: Routes requests flexibly

The Salvo framework provides middleware for reverse proxy functionality.

Path forwarding and normalization

When you mount a proxy with a wildcard route such as Router::with_path("api/{**rest}"), the default proxy path is the captured tail. For example, /api/users is forwarded as /users when the upstream is http://backend. If the backend also expects the /api prefix, include that prefix in the upstream URL:

let router = Router::with_path("api/{**rest}").goal(
    Proxy::use_hyper_client("http://backend/api")
);

Proxy normalizes literal . and .. path segments before forwarding. If the proxy is used as an access-control boundary and the backend may perform another URL decode pass, enable strict path normalization:

let router = Router::with_path("api/{**rest}").goal(
    Proxy::use_hyper_client("http://backend/api")
        .strict_path_normalization(true)
);

Strict path normalization rejects ambiguous percent-encoded path characters such as %2e, %2f, %5c, and encoded percent signs that can become those sequences after another decode pass. The backend should still validate and normalize its own routes and file paths; strict mode helps avoid proxy/backend path interpretation mismatches.

Example Code

main.rs
Cargo.toml
use salvo::prelude::*;

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt().init();

    // In this example, if the requested URL begins with <http://127.0.0.1:8698/>, the proxy goes to
    // <https://www.rust-lang.org>; if the requested URL begins with <http://localhost:8698/>, the proxy
    // goes to <https://crates.io>.
    let router = Router::new()
        .push(
            Router::new()
                .host("127.0.0.1")
                .path("{**rest}")
                .goal(Proxy::use_hyper_client("https://docs.rs")),
        )
        .push(
            Router::new()
                .host("localhost")
                .path("{**rest}")
                .goal(Proxy::use_hyper_client("https://crates.io")),
        );

    let acceptor = TcpListener::new("0.0.0.0:8698").bind().await;
    Server::new(acceptor).serve(router).await;
}