Tokio-rustls + hyper-staticfile lifetime issues


#1

So I’m working with both the tokio-rustls server example and the hyper-staticfile document server example however I’m running into lifetime issues that I’ve have not been able to resolve. I’m wondering if someone may give me some guidance on how to get around this problem.

The following is my src:

src/main.rs:

extern crate futures;
extern crate hyper;
extern crate rustls;
extern crate tokio_core;
extern crate tokio_rustls;
extern crate hyper_rustls;
#[macro_use] extern crate lazy_static;
extern crate hyper_staticfile;

use tokio_core::net::TcpListener;
use rustls::internal::pemfile;
use std::env;
use tokio_rustls::ServerConfigExt;
use futures::{Future, Stream, future};
use hyper::{Error, Uri};
use hyper::server::{Http, Request, Response, Service};
use hyper_staticfile::Static;
use std::path::Path;
use tokio_core::reactor::{Core, Handle};

fn load_certs(filename: &str) -> Vec<rustls::Certificate> {
    let certfile = std::fs::File::open(filename).expect("cannot open certificate file");
    let mut reader = std::io::BufReader::new(certfile);
    pemfile::certs(&mut reader).unwrap()
}

fn load_private_key(filename: &str) -> rustls::PrivateKey {
    let keyfile = std::fs::File::open(filename).expect("cannot open private key file");
    let mut reader = std::io::BufReader::new(keyfile);
    let keys = pemfile::rsa_private_keys(&mut reader).unwrap();
    assert!(keys.len() == 1);
    keys[0].clone()
}

// example: DOMAIN=https://mediadepot-qa1.its.txstate.edu:8443
lazy_static! {
    static ref DOMAIN: Uri = {
        match env::var("DOMAIN") {
           Ok(domain) => domain.parse::<Uri>().unwrap(),
           Err(_) => panic!("No DOMAIN defined."),
        }
    };
}

type ResponseFuture = Box<Future<Item=Response, Error=Error>>;

struct MainService {
    static_: Static,
}

impl MainService {
    fn new(handle: &Handle) -> MainService {
        MainService {
            static_: Static::new(handle, Path::new("target/doc/")),
        }
    }
}

impl Service for MainService {
    type Request = Request;
    type Response = Response;
    type Error = Error;
    type Future = ResponseFuture;

    fn call(&self, req: Request) -> Self::Future {
        if req.path() == "/" {
            let res = Response::new()
                .with_status(hyper::StatusCode::MovedPermanently)
                .with_header(hyper::header::Location::new("/hyper_staticfile/"));
            Box::new(future::ok(res))
        } else {
            self.static_.call(req)
        }
    }
}

fn main() {
    let address = match std::env::var("ADDRESS") {
        Ok(a) => a.to_owned(),
        Err(_)  => "127.0.0.1:8443".to_owned(),
    };
    let addr = address.parse().unwrap();
    let certs = load_certs("private/local.cert.pem");
    let certs_key = load_private_key("private/local.key.pem");
    let mut config = rustls::ServerConfig::new();
    config.set_single_cert(certs, certs_key);
    let arc_config = std::sync::Arc::new(config);
    let mut core = Core::new().unwrap();
    let handle = core.handle();
    let socket = TcpListener::bind(&addr, &handle).unwrap();
    let http = Http::new();
    let done = socket.incoming()
        .for_each(|(sock, remote_addr)| {
            println!("Info: {:?}", remote_addr);
            let done = arc_config.accept_async(sock)
                .map(|stream| {
                    let s = MainService::new(&handle);
                    http.bind_connection(&handle, stream, remote_addr, s);
                })
                .map_err(move |err| println!("Error: {:?} - {}", err, remote_addr));
            handle.spawn(done);
            Ok(())
        });
    println!("Starting to serve on https://{} ...", addr);
    core.run(done).unwrap();
}

Which gives the following errors:

error[E0373]: closure may outlive the current function, but it borrows `**handle`, which is owned by the current function
   --> src/main.rs:141:22
    |
141 |                 .map(|stream| {
    |                      ^^^^^^^^ may outlive borrowed value `**handle`
142 |                     let s = MainService::new(&handle);
    |                                               ------ `**handle` is borrowed here
    |
help: to force the closure to take ownership of `**handle` (and any other referenced variables), use the `move` keyword
    |
141 |                 .map(move |stream| {
    |                      ^^^^^^^^^^^^^

error[E0373]: closure may outlive the current function, but it borrows `**http`, which is owned by the current function
   --> src/main.rs:141:22
    |
141 |                 .map(|stream| {
    |                      ^^^^^^^^ may outlive borrowed value `**http`
...
144 |                     http.bind_connection(&handle, stream, remote_addr, s);
    |                     ---- `**http` is borrowed here
    |
help: to force the closure to take ownership of `**http` (and any other referenced variables), use the `move` keyword
    |
141 |                 .map(move |stream| {
    |                      ^^^^^^^^^^^^^

error[E0373]: closure may outlive the current function, but it borrows `remote_addr`, which is owned by the current function
   --> src/main.rs:141:22
    |
141 |                 .map(|stream| {
    |                      ^^^^^^^^ may outlive borrowed value `remote_addr`
...
144 |                     http.bind_connection(&handle, stream, remote_addr, s);
    |                                                           ----------- `remote_addr` is borrowed here
    |
help: to force the closure to take ownership of `remote_addr` (and any other referenced variables), use the `move` keyword
    |
141 |                 .map(move |stream| {
    |                      ^^^^^^^^^^^^^

error: aborting due to 3 previous errors

error: Could not compile `statictest`.

I tried the suggestion from the compiler of utilizing the move but then ran into issues with the handle and http variables. I do not understand how the closure can outlive the handle variable which was generated outside the closure.


#2

You’ll likely want something like this:

// put Http into an Rc so we can move a clone into the inner closure
let http = Rc::new(Http::new());
let done = socket.incoming()
        .for_each(|(sock, remote_addr)| {
            println!("Info: {:?}", remote_addr);
            // clone the Handle so we can move it into the inner closure
            let h = handle.clone();
            let http_clone = http.clone();
            let done = arc_config.accept_async(sock)
                .map(move |stream| {
                    let s = MainService::new(&h);
                    http_clone.bind_connection(&h, stream, remote_addr, s);
                })
                .map_err(move |err| println!("Error: {:?} - {}", err, remote_addr));
            handle.spawn(done);
            Ok(())
        });

As an aside, I think hyper wants you to define a NewService and use that with Http::bind rather than Http::bind_connection.

The inner closure captures a handle reference. But Handle::spawn requires that the passed in future is 'static - that’s no longer the case because the desugared closure is probably something like:

struct __InnerClosure<'a> {
    handle: &'a Handle
}

#3

That compiled! I appreciate the guidance as I spent most of the day hitting a wall with this one. I originally tried cloning the handle, but made the mistake of thinking that shadowing the variable would work. I did not realize that the handle.spawn would reflect the handle that got moved and not reference the shadowed variable. That was a big help.

My Cargo.lock file shows that I’m using futures 0.1.17. Are you saying that later versions of the futures crate will not have this problem as the closures will not be 'static?

From what I can tell the hyper-staticfile crate looks like it is using the bind_connection method so that it can have access to the core handle and submit the file in chunks.

Thank you!


#4

No, this is independent of tokio version. https://docs.rs/tokio-core/0.1.10/tokio_core/reactor/struct.Handle.html#method.spawn is the method you’re calling; note how F is required to be 'static. That means any future you pass there must not have any references (besides ’ static itself). If, while constructing that future, you use a closure that captures something from the environment by reference (such as your inner closure), you won’t be able to pass it there. When you clone the Handle and move it into the closure, the closure ends up owning that handle and your closure is 'static. Does that make sense?

I didn’t look at the hyper-staticfile docs. But, NewService should still allow your service to get access to the handle. The NewService impl can have a handle itself and then when it’s asked to construct a new instance of the service, it can clone the handle and give it to the service instance.