反向代理

反向代理是一种服务器架构,它接收来自客户端的请求并将其转发到后端的一个或多个服务器。与正向代理(代表客户端)不同,反向代理代表服务器端工作。

反向代理的主要优势:

  • 负载均衡:分散请求到多个服务器
  • 安全性提升:隐藏真实服务器信息
  • 内容缓存:提高性能
  • 路径重写与转发:灵活地路由请求

Salvo 框架提供反向代理功能的中间件.

路径转发与规范化

当你使用 Router::with_path("api/{**rest}") 这类通配路由挂载代理时,默认转发路径是被捕获到的剩余路径。例如 upstream 配置为 http://backend 时,客户端请求 /api/users 会转发为 http://backend/users。如果后端服务本身也期望收到 /api 前缀,应该把前缀写进 upstream:

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

Proxy 在转发前会规范化字面量 ... 路径段。如果你把代理作为访问控制边界,并且后端可能再次 URL decode 路径,可以启用严格路径规范化:

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

严格模式会拒绝容易产生二次解释差异的 percent-encoded 路径字符,例如 %2e%2f%5c,以及再次 decode 后可能变成这些序列的 encoded percent。后端仍然应该自己验证并规范化路由和文件路径;严格模式的作用是减少代理和后端对同一路径理解不一致的风险。

示例代码

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;
}