Hyper, tokio - Pass variable to service handler

This prolem is related to How to pass variables to hyper service handler? but I am not able to figure it out between my async functions.

I am running a tool which takes a path as a command line argument and starts a webserver which is launching a service that will scrape some file from the given path.
I am having trouble passing the path to the service handler as it seems to be running out of scope because its living in another thread.

This is my main function, I have added type information as comments

#[tokio::main]
async fn main() -> Result<()> {
    let mut args: Vec<String> = env::args().collect();
    args.remove(0);

    // (PathBuf, log::Level)
    let (path, log_level) = handle_args(args)?;
    simple_logger::init_with_level(log_level)?;

    let addr = ([127, 0, 0, 1], 9898).into();
    info!("Listening on http://{}", addr);

    let make_svc = make_service_fn(|_| async {
        // async fn serve_req(path: &Path) -> std::result::Result<Response<Body>, hyper::Error>
        Ok::<_, hyper::Error>(service_fn(|_req| async { serve_req(path.as_path()).await }))
    });

    // Then bind and serve...
    let server = Server::bind(&addr).serve(make_svc);

    if let Err(err) = server.await {
        error!("server error: {}", err);
    }

    Ok(())
}

The compiler is giving me the following error:

error[E0597]: `path` does not live long enough
  --> src/main.rs:43:67
   |
42 |       let make_svc = make_service_fn(|_| async {
   |  ____________________________________---_-
   | |                                    |
   | |                                    value captured here
43 | |         Ok::<_, hyper::Error>(service_fn(|_req| async { serve_req(path.as_path()).await }))
   | |                                                                   ^^^^ borrowed value does not live long enough
44 | |     });
   | |_____- returning this value requires that `path` is borrowed for `'static`
...
54 |   }
   |   - `path` dropped here while still borrowed

I think I understand the problem but I have not been able to clone/move/copy the path into the service handler in a way that it does not fall out of scope.

How would I approach this?

I can't test it, but I believe it's something like this:

let make_svc = make_service_fn(move |_| { // first move it into the closure
    // closure can be called multiple times, so for each call, we must
    // clone it and move that clone into the async block
    let path = path.clone();
    async move {
        // async block is only executed once, so just pass it on to the closure
        Ok::<_, hyper::Error>(service_fn(move |_req| {
            // but this closure may also be called multiple times, so make
            // a clone for each call, and move the clone into the async block
            let path = path.clone();
            async move { serve_req(path.as_path()).await }
        }))
    }
});
1 Like

Works like a charm, also thank you for the comments explaining.
I kinda tried the same but without adding one the one extra closure.
Why is that one needed?

Which one of them?

Before the change there were two async closures, now there are two move closures and two async move closures. I am realizing I formulated the question kinda weird. I want to know in general why the move has to occur this often

Every async block and closure in the snippet is part of the return value of the surrounding scope, so they all need ownership to avoid borrowing from the path owned by the surrounding scope.

As for the clones, they're needed in the closures that can be called multiple times, because giving ownership away without cloning can only happen once. The outer clone is called for each connection, and the inner clone is called for each request (keep-alive allows multiple http requests in a single connection).

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.