Handle HTTP requests with parameters using hyper and async functions

Dear Rustaceans,

What is the correct way to handle HTTP requests with parameters using hyper and async functions ?

Concretely, here is a code example:

  • In the serve_file function, I receive a filepath variable I got with clap argument parser
  • I clone and convert it to String
  • I give it to the handle_request function.

I have compiler errors with static variables and lifetimes.

/// Handle request
async fn handle_request(req: Request<Body>, filepath: String) -> Result<Response<Body>> {
    match (req.method(), req.uri().path()) {
        (&Method::GET, filepath) => send_file(filepath).await,
        _ => Ok(not_found()),
    }
}

/// Serve requests
#[tokio::main]
pub async fn serve_file(filepath: &str, ip_addr: &str, port: &str, count: &str) -> Result<()> {
    // Create address
    let addr = format!("{ip_addr}:{port}", ip_addr = ip_addr, port = port)
        .parse()
        .unwrap();

    // Create service
    let service = make_service_fn(move |_| async {
        let filepath = filepath.clone();
        let filepath = filepath.to_owned();
        Ok::<_, hyper::Error>(service_fn(|req| handle_request(req, filepath)))
    });

    // Create server bound on the provided address
    let server = Server::bind(&addr).serve(service);

    // Wait for the server to complete serving or exit with an error.
    if let Err(error) = server.await {
        eprintln!("Error: {}", error);
    }

    Ok(())
}
let server = Server::bind(&addr).serve(service);
                                 ^^^^^ lifetime `'static` required

Thank you very much for sharing your knowledge.

You need to clone it in the closure, not in the async block.

// Create owned version to give to closure.
let filepath = filepath.clone();
let service = make_service_fn(move |_| {
    // Make clone we can give away to async block.
    let filepath = filepath.clone();
    async move {
        // async block can only run once, and gives ownership to closure
        Ok::<_, hyper::Error>(service_fn(move |req| {
            // closure makes a clone for each request
            handle_request(req, filepath.clone())
        }))
    }
});

Basically you need a clone every time a closure can be called more than once, because those closures cannot give away ownership of the one instance they have. The outer closure is called per connection and the inner is called per request on that connection.

1 Like

Dear Alice,

It works perfectly, thank you so much for your help!
I now understand better how to handle ownership in closures.

1 Like