Here is a short guide on building your reserve proxy in Rust.

First of all, we need to create a Cargo. toml with the required dependencies:

[dependencies]
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
rustls = { version = "0.19", features = ["dangerous_configuration"] }
tokio-rustls = "0.22"
hyper-rustls = "0.22"
futures = "0.3"

This example will create a primary HTTP server (our origin server), a TLS reverse proxy server, and a primary client to interact with them.

Step 1: Create the origin server

Now let’s create the origin server. This server will listen to localhost on port 3000:

async fn origin_service(_req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    Ok(Response::new(Body::from("Hello from origin server!")))
}

async fn start_origin_server() {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    let make_svc = make_service_fn(|_conn| async { Ok::<_, hyper::Error>(service_fn(origin_service)) });

    let server = Server::bind(&addr).serve(make_svc);
    if let Err(e) = server.await {
        eprintln!("origin server error: {}", e);
    }
}

Step 2: Create a reverse proxy server

Now, we will create a reverse proxy server. This server will listen to localhost on port 8080 and forward the requests to our origin server:

async fn proxy_service(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
    let client = Client::new();
    let uri = format!("http://localhost:3000{}", req.uri()).parse().unwrap();

    let forwarded_req = Request::builder()
        .method(req.method())
        .uri(uri)
        .version(req.version())
        .headers(req.headers().clone())
        .body(req.into_body())
        .unwrap();

    client.request(forwarded_req).await
}

async fn start_proxy_server() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // load the certificates
    let certs = load_certs("path_to_your_cert.pem")?;
    let key = load_private_key("path_to_your_key.pem")?;

    let mut config = ServerConfig::new(NoClientAuth::new());
    config.set_single_cert(certs, key)?;

    let acceptor = TlsAcceptor::from(Arc::new(config));
    let addr = SocketAddr::from(([127, 0, 0, 1], 8080));

    let make_svc = make_service_fn(|_conn| async { Ok::<_, hyper::Error>(service_fn(proxy_service)) });

    let server = Server::bind(&addr)
        .tls(acceptor)
        .serve(make_svc);

    if let Err(e) = server.await {
        eprintln!("proxy server error: {}", e);
    }
    Ok(())
}

For simplicity, we are using HTTP to connect to the origin server. You might want to use HTTPS for this connection in a real-world scenario.

Steps 3 and 4.

Forward a client request to the origin server (via reverse proxy) and Copy the origin server response to the client (via reverse proxy). The proxy_service function does this. This function will forward the incoming request to the origin server and then return the response from the origin server to the client.

Finally, start the servers: We will start both the origin and the proxy server in the main function:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    tokio::spawn(start_origin_server());
    start_proxy_server().await?;

    Ok(())
}

Ensure you replace “path_to_your_cert.pem” and “path_to_your_key.pem” with the actual paths to your server’s TLS certificate and private key files. The server certificate file should be in PEM format and may contain one or more certificates. The private key should also be a PEM file.

This simplified example excludes functions like logging, error handling, configuration, rate limiting, handling large requests or responses, etc.