Building proxy in rust

I am trying to build a proxy in rust with simple caching. How to pass same cache object in the handle_request function. So that it can have the same cache object across all the requests. Below is the given sample code

#[derive(Clone)]
struct CachedResponse {
    response: String,
    timestamp: SystemTime,
}

#[derive(Clone)]
struct Cache {
    entries: HashMap<String, CachedResponse>
}

async fn handle_request(req: Request<Body>, mut cache: Cache) -> Result<Response<Body>, hyper::Error> {
    /*Here I need cache object that should be shared across all request*/
    Ok(response)
}

#[tokio::main]
async fn main() {

    let mut cache_map: HashMap<String, CachedResponse> = HashMap::new();
    let mut cache = Cache {
        entries: cache_map
    };

    let addr = ([0, 0, 0, 0], 3000).into();
    let make_svc = make_service_fn(move |_| {
        let state = &cache;
        async {
            Ok::<_, Infallible>(service_fn(move |req| handle_request(req, state.clone())))
        }
    });
    let server = Server::bind(&addr).serve(make_svc);

    // Start the server
    println!("Listening on http://{}", addr);
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

Compilation Error:

error: captured variable cannot escape `FnMut` closure body
   --> src/main.rs:153:9
    |
146 |       let mut cache = Cache {
    |           --------- variable defined here
...
151 |       let make_svc = make_service_fn(move |_| {
    |                                             - inferred to be a `FnMut` closure
152 |           let state = &cache;
    |                        ----- variable captured here
153 | /         async move {
154 | |             Ok::<_, Infallible>(service_fn(move |req| handle_request(req, state.clone())))
155 | |         }
    | |_________^ returns an `async` block that contains a reference to a captured variable, which then escapes the closure body
    |
    = note: `FnMut` closures only have access to their captured variables while they are executing...
    = note: ...therefore, they cannot allow references to captured variables to escape


Note: I am newibe to rust and trying to understand the basics and the above code is not for production. It is just for learning purposes.

Put it in an Arc and clone that.

1 Like

Additionally to Arc, if you need mutable, thread-safe access to a shared variable, you need to wrap it in some guard, like a Mutex or RwLock. You can find more information on this in the book: Shared-State Concurrency - The Rust Programming Language.

Here an example of how you could share your cache between threads and protect it from race conditions using the Mutex synchronization primitive:

use hyper::{
    body::Body,
    service::{make_service_fn, service_fn},
    Request, Response, Server,
};

use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::{Arc, Mutex};

#[derive(Clone)]
struct Cache {
    entries: Arc<Mutex<HashMap<String, String>>>,
}

async fn handle_request(_req: Request<Body>, cache: Cache) -> Result<Response<Body>, hyper::Error> {
    /*Here I need cache object that should be shared across all request*/
    println!("{:?}", cache.entries.lock().unwrap());
    todo!()
}

#[tokio::main]
async fn main() {
    let cache = Cache {
        entries: Arc::new(Mutex::new(HashMap::new())),
    };

    let addr = ([0, 0, 0, 0], 3000).into();
    let make_svc = make_service_fn(move |_| {
        let cache = cache.clone();
        async { Ok::<_, Infallible>(service_fn(move |req| handle_request(req, cache.clone()))) }
    });
    let server = Server::bind(&addr).serve(make_svc);

    // Start the server
    println!("Listening on http://{}", addr);
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

Playground.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.