A Heavy Hyper HTTP Handler: how to not clone big things

I am making a http server using hyper. A pattern I have used before is to create a handler struct that has various methods async fn some_endpoint(&self, req: hyper::Request<hyper::Body>) -> Result<hyper::Response<...>,...>, each of which receives requests along a given url path. That handler gets cloned on a new thread for each new request.

But now I want to add something "heavy", for example a HashMap that looks up a value from among thousands of entries. That is too big to clone, so I tried defining a handler with a reference:

#[derive(Clone)]
pub struct HandlerHTTP<'a>{
    // I used a reference because the handler gets cloned for each request-
    // I don't want to clone the whole dict!
	dict: &'a HashMap<String, i32>,
}

Here is where I get confused: I make a hashmap and it doesn't go out of scope until main() exits. But the compiler gripes that it goes out of scope while still having references, asking for a static lifetime instead. The code is below as well as on the playground.

How do I make a hyper http server with "heavy" things without cloning them?

The code:

use std::{env, vec::Vec, collections::HashMap};
use hyper::{Body, Method, Request, Response, Server, StatusCode,
    server::conn::AddrStream, service::{make_service_fn, service_fn}};


static INDEX: &[u8] = b"Hello world!";
static NOTFOUND: &[u8] = b"Not Found";

type GenericError = Box<dyn std::error::Error + Send + Sync>;


#[derive(Clone)]
pub struct HandlerHTTP<'a>{
    // I used a reference because the handler gets cloned for each request-
    // I don't want to clone the whole dict!
	dict: &'a HashMap<String, i32>,
}

impl<'a> HandlerHTTP<'a> {
    fn new(dict: &'a HashMap<String, i32>) -> Self {
        HandlerHTTP{dict: dict}
    }

    /*pub async fn handle_a_request(&self, req: Request<Body>) -> Result<Response<Body>, GenericError> {
        TODO - write these methods later
    } */
}


async fn request_router(req: Request<Body>, handler: HandlerHTTP<'_>, ip_address: String) -> Result<Response<Body>, GenericError> {
    match (req.method(), req.uri().path()) {
        (&Method::GET, "/") | (&Method::GET, "/index.html") => Ok(Response::new(INDEX.into())),
        //(&Method::GET, "/search") => handler.handle_a_request(req).await,
        _ => {
            // Return 404 not found response.
            Ok(Response::builder()
                .status(StatusCode::NOT_FOUND)
                .body(NOTFOUND.into())
                .unwrap())
        }
    }
}



#[tokio::main]
async fn main() -> Result<(), GenericError> {
    
    // Initialize stuff that needs unwrapped. If you're gonna fail, fail early
    let mut dict = HashMap::new();
    dict.insert("foo".to_string(), 5);
    dict.insert("bar".to_string(), 6);
    let handler = HandlerHTTP::new(&dict);
    let bind_to = "127.0.0.1::9088".parse().unwrap();

    let new_service = make_service_fn(move |conn: &AddrStream| {
        // Clone all the things that need to be used in addition to the request
        // This is what Go does - it makes a new copy with a go routine under the hood for each new request
        let handler = handler.clone();
        let remote_addr = conn.remote_addr();
        let ip_address = remote_addr.ip().to_string();
        async {
            Ok::<_, GenericError>(service_fn(move |req| {
                // Clone again to ensure everything you need outlives this closure.
                request_router(req, handler.to_owned(), ip_address.to_owned())
            }))
        }
    });
    let server = Server::bind(&bind_to).serve(new_service);
    println!("Listening on http://{}", &bind_to);
    server.await?;
    Ok(())
}

You can store the hash map a Arc<HashMap<String, i32>> or Rc<HashMap<String, i32>> depending on if you need the atomicity of the reference count.
In general, don't store references in structs unless you know that you can trivially satisfy the lifetime bounds (which basically means everything depends in a tree-like fashion).

2 Likes

That worked- thank you! I changed the struct to

#[derive(Clone)]
pub struct HandlerHTTP{
	dict: Arc<HashMap<String, i32>>,
}

and the main body to

#[tokio::main]
async fn main() -> Result<(), GenericError> {
    
    // Initialize stuff that needs unwrapped. If you're gonna fail, fail early
    let mut inner = HashMap::new();
    inner.insert("foo".to_string(), 5);
    inner.insert("bar".to_string(), 6);
    let dict = Arc::new(inner);
    let handler = HandlerHTTP::new(dict);
    let bind_to = "127.0.0.1::9088".parse().unwrap();
    // ...snip...

For posterity, here is a link to a new playground that does compile. I also found this page in the Tokio documentation that describes almost this same problem.

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.