rustrust-warp

How do I clone a variable before moving it into warp's .then() filter?


I have the following code snippet:

async fn server(config: crate::Config) {
    println!("Building server");
    let key = hmac::Key::new(hmac::HMAC_SHA256, config.docusign.hmac_key.as_bytes());
    let webhook = warp::path("webhook")
        .and(warp::post())
        .and(warp::body::content_length_limit(4194304))
        .and(warp::header::headers_cloned())
        .and(warp::body::bytes())
        .then(|headers: HeaderMap, bytes: Bytes| async move {
            match verify_msg(&key, &headers, &bytes) {
                Ok(_) => {
                    println!("Message is Valid!");
                    process_msg(bytes).await.into_response()
                }
                Err(string) => {
                    println!("{string}");
                    warp::reply::with_status(warp::reply(), http::StatusCode::UNAUTHORIZED)
                        .into_response()
                }
            }
        });

    warp::serve(webhook)
        .tls()
        .cert_path("cert/cert.pem")
        .key_path("cert/key.pem")
        .run(([0, 0, 0, 0], 443))
        .await;

    println!("Shutting down Server");
}

This gives me an error:

expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
this closure implements `FnOnce`, not `Fn`rustc(E0525)
server.rs(20, 4): the requirement to implement `Fn` derives from here
server.rs(20, 9): this closure implements `FnOnce`, not `Fn`
server.rs(21, 22): closure is `FnOnce` because it moves the variable `key` out of its environment

This makes sense, I am using the key variable and thus moving it out of the environment. What I can't figure out is how can I get this async closure to work without moving the key? I've tried cloning it like this: match verify_msg(&key.clone(), &headers, &bytes) but it still doesn't work. I guess that makes sense, since the variable is still referenced inside the closure. So, how do I clone the key before it gets moved?

I was able to get it working with .map() and a regular (non-async) closure, but the process_msg() function is async, so I don't think that would work.

Edit: The answer from @t56k got me on the right track, but didn't quite work. Going in the direction of putting async blocks inside of a closure and following the compiler's recommendations eventually got me this:

async fn server(config: crate::Config) {
    println!("Building server");
    let key = hmac::Key::new(hmac::HMAC_SHA256, config.docusign.hmac_key.as_bytes());
    let webhook = warp::path("webhook")
        .and(warp::post())
        .and(warp::body::content_length_limit(4194304))
        .and(warp::header::headers_cloned())
        .and(warp::body::bytes())
        .then(move |headers: HeaderMap, bytes: Bytes| {
            let key = key.clone();
            async move {
                match verify_msg(&key, &headers, &bytes) {
                    Ok(_) => {
                        println!("Message is Valid!");
                        process_msg(bytes).await.into_response()
                    }
                    Err(string) => {
                        println!("{string}");
                        warp::reply::with_status(warp::reply(), http::StatusCode::UNAUTHORIZED)
                            .into_response()
                    }
                }
            }
        });

    warp::serve(webhook)
        .tls()
        .cert_path("cert/cert.pem")
        .key_path("cert/key.pem")
        .run(([0, 0, 0, 0], 443))
        .await;

    println!("Shutting down Server");
}

which works perfectly for some reason even though I'm using the move keyword. I guess i'm only allowed to move key if it isn't inside of an async block? In any case, my problem is solved, but if anyone could explain why this works I would gladly accept it.


Solution

  • This is untested for your use case, but you can .clone() things before the move to allow them access.

    .and_then(|headers: HeaderMap, bytes: Bytes| async {
        let key = key.clone();
    
        move {
            match verify_msg(key, &headers, &bytes) {
                // ...
            }
        }
    });