Search code examples
rusthyper

Struggling to call an async method from within a hyper request handler


I am struggling to build a web service using Hyper. The problem I face is best illustrated with with example code ...

use std::convert::Infallible;
use std::sync::Arc;
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};

struct ContrivedServicesManager {
    some_state: Vec<String>
}

impl ContrivedServicesManager {
    pub /* async */ fn handle_request(&self, _req: Request<Body>) -> Response<Body> {
        hyper::Response::new(Body::from(self.some_state.join(", ")))
    }

    fn new(args: Vec<String>) -> Self {
        Self {
            some_state: args
        }
    }
}

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

    // Grab all the command line arges following argv[0]
    let args: Vec<String> = std::env::args()
        .enumerate()
        .filter(|&(i,_)| i > 0)
        .map(|(_, e)| e)
        .collect();

    // The inner layer of request handling machinery
    let services_manager = Arc::new(ContrivedServicesManager::new(args));

    // Build the outer layer of request handling machinery
    let service_maker = make_service_fn(move |_| {
        let services_manager = services_manager.clone();

        return async move {
            Ok::<_, Infallible>(service_fn(move |req| {
                let response = services_manager.handle_request(req);
                async move { Ok::<_, Infallible>(
                    response
                )}
            }))
        };
    });

    // Configure & start the web server
    let addr = ([127, 0, 0, 1], 3000).into();
    let server = Server::bind(&addr).serve(service_maker);
    println!("Listening on http://{}", addr);
    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }

}

This compiles & works. However, if ContrivedServicesManager::handle_request is changed to be async it fails to compile and the compiler outputs 237 lines of complaint, which seem to centre on service_maker being the wrong type.

I would be so glad if someone with stronger Rust-Fu would tell me how to overcome this.


Solution

  • Just some minor adjustments were necessary.

    Primarily, the service_manager has to be cloned again in the innermost closure because it might now live forever, referenced in the returned future.

    Minor nit: use Arc::clone() instead of .clone() for Arc objects to avoid ambiguities.

    use hyper::service::{make_service_fn, service_fn};
    use hyper::{Body, Request, Response, Server};
    use std::convert::Infallible;
    use std::sync::Arc;
    
    struct ContrivedServicesManager {
        some_state: Vec<String>,
    }
    
    impl ContrivedServicesManager {
        pub async fn handle_request(&self, _req: Request<Body>) -> Response<Body> {
            hyper::Response::new(Body::from(self.some_state.join(", ")))
        }
    
        fn new(args: Vec<String>) -> Self {
            Self { some_state: args }
        }
    }
    
    #[tokio::main]
    async fn main() {
        // Grab all the command line arges following argv[0]
        let args: Vec<String> = std::env::args()
            .enumerate()
            .filter(|&(i, _)| i > 0)
            .map(|(_, e)| e)
            .collect();
    
        // The inner layer of request handling machinery
        let services_manager = Arc::new(ContrivedServicesManager::new(args));
    
        // Build the outer layer of request handling machinery
        let service_maker = make_service_fn(move |_| {
            let services_manager = Arc::clone(&services_manager);
    
            async move {
                Ok::<_, Infallible>(service_fn(move |req| {
                    // Need to clone again, because it will get captured by `handle_request` and live indefinitely
                    // until the future gets polled
                    let services_manager = Arc::clone(&services_manager);
                    async move {
                        // Has to be inside of the `async move` because it's an async function
                        let response = services_manager.handle_request(req).await;
                        Ok::<_, Infallible>(response)
                    }
                }))
            }
        });
    
        // Configure & start the web server
        let addr = ([127, 0, 0, 1], 3000).into();
        let server = Server::bind(&addr).serve(service_maker);
        println!("Listening on http://{}", addr);
        if let Err(e) = server.await {
            eprintln!("server error: {}", e);
        }
    }