Trouble with lifetimes in actix-web middleware

Hi all,

I'm trying to write an asynchronous middleware for actix-web. The code as I have it so far is below.

use actix_web::{HttpRequest, HttpResponse, HttpMessage, Result, Error};
use actix_web::middleware::{Middleware, Started};
use actix_web::http::Method;
use futures::{Future, future, future::Either};
use uuid::Uuid;
use std::collections::HashMap;

const CONTENT_TYPE_FORM_DATA: &'static str = "application/x-www-form-urlencoded";
const FORM_VALUE_AUTHN_ID: &'static str = "authnId";

pub struct AuthenticationMiddleware {
    pub unauthenticated_routes: Vec<String>
}

impl AuthenticationMiddleware {
    fn get_authn_id<S: 'static>(&self, req: &HttpRequest<S>) -> impl Future<Item=Option<Uuid>, Error=Error> {
        if req.method() == Method::POST && req.content_type() == CONTENT_TYPE_FORM_DATA {
            let fut = req.urlencoded::<HashMap<String, String>>()
                .from_err()
                .and_then(move |form_values| {
                    if form_values.contains_key(FORM_VALUE_AUTHN_ID) {
                        if let Some(value) = form_values.get(FORM_VALUE_AUTHN_ID) {
                            match Uuid::parse_str(value) {
                                Ok(authn_id) => Either::A(future::ok(Some(authn_id))),
                                _ => Either::B(self.get_authn_id_from_cookie(req))
                            }
                        }
                        else {
                            Either::B(self.get_authn_id_from_cookie(req))
                        }
                    }
                    else {
                        Either::B(self.get_authn_id_from_cookie(req))
                    }
                });

            Either::A(fut)
        }
        else {
            Either::B(self.get_authn_id_from_cookie(req))
        }
    }

    fn get_authn_id_from_cookie<S>(&self, req: &HttpRequest<S>) -> impl Future<Item=Option<Uuid>, Error=Error> {
        future::ok(None)
    }
}

impl<S: 'static> Middleware<S> for AuthenticationMiddleware {
    fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
        let request_path = req.path();

        if self.unauthenticated_routes.iter().any(|path| request_path.starts_with(path)) {
            // The path does not require authentication.  Proceed to next middleware.
            Ok(Started::Done)
        }
        else {
            let fut = self.get_authn_id(req)
                .from_err()
                .and_then(|authn_id| {
                    match authn_id {
                        Some(authn_id) => future::ok(None),
                        None => future::ok(Some(HttpResponse::Ok().finish()))
                    }
                });  
            
            Ok(Started::Future(Box::new(fut)))
        }
    }
}

When I compile, I'm getting the following errors:

error: cannot infer an appropriate lifetime
  --> /Users/jveatch/repos/wovelocity/GridIron/GridIron/server/src/web/middleware/actix/mod.rs:20:27
   |
16 |       fn get_authn_id<S: 'static>(&self, req: &HttpRequest<S>) -> impl Future<Item=Option<Uuid>, Error=Error> {
   |                                                                   ------------------------------------------- this return type evaluates to the `'static` lifetime...
...
20 |                   .and_then(move |form_values| {
   |  ___________________________^
21 | |                     if form_values.contains_key(FORM_VALUE_AUTHN_ID) {
22 | |                         if let Some(value) = form_values.get(FORM_VALUE_AUTHN_ID) {
23 | |                             match Uuid::parse_str(value) {
...  |
34 | |                     }
35 | |                 });
   | |_________________^ ...but this borrow...
   |
note: ...can't outlive the anonymous lifetime #1 defined on the method body at 16:5
  --> /Users/jveatch/repos/wovelocity/GridIron/GridIron/server/src/web/middleware/actix/mod.rs:16:5
   |
16 | /     fn get_authn_id<S: 'static>(&self, req: &HttpRequest<S>) -> impl Future<Item=Option<Uuid>, Error=Error> {
17 | |         if req.method() == Method::POST && req.content_type() == CONTENT_TYPE_FORM_DATA {
18 | |             let fut = req.urlencoded::<HashMap<String, String>>()
19 | |                 .from_err()
...  |
41 | |         }
42 | |     }
   | |_____^
help: you can add a constraint to the return type to make it last less than `'static` and match the anonymous lifetime #1 defined on the method body at 16:5
   |
16 |     fn get_authn_id<S: 'static>(&self, req: &HttpRequest<S>) -> impl Future<Item=Option<Uuid>, Error=Error> + '_ {
   |                                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0621]: explicit lifetime required in the type of `req`
  --> /Users/jveatch/repos/wovelocity/GridIron/GridIron/server/src/web/middleware/actix/mod.rs:16:65
   |
16 |     fn get_authn_id<S: 'static>(&self, req: &HttpRequest<S>) -> impl Future<Item=Option<Uuid>, Error=Error> {
   |                                             ---------------     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime `'static` required
   |                                             |
   |                                             help: add explicit lifetime `'static` to the type of `req`: `&'static actix_web::HttpRequest<S>`

I'm still trying to figure out lifetimes. I can't figure out what this error means or how to correct it. Any help would be appreciated.

When Actix tells you something about 'static, it's not really about lifetimes of references. It means it wants owned types, which in practice means: put all stuff in Arc and clone it for every request, and every closure in the chain.

3 Likes

It’s because you’re using a method (get_authn_id_from_cookie to be specific) inside the closure, which causes the closure to capture a reference to self. That in turn means your closure is not 'static, but that’s required in the signature of the return type.

I can’t tell if this method is just like what you wrote in your real code or you stripped it down for the example. As written, it doesn’t need to be a method but can be an associated function instead.

1 Like

I haven't really implemented get_authn_id_from_cookie yet. I just stuck that one line of code in there to get it to compile. It will be looking for a specific cookie in the request.

Thanks for the help, @vitalyd. I changed those two methods to associated functions and that part of the error went away. However, now I'm getting this error from get_authn_id:

error[E0621]: explicit lifetime required in the type of `req`
  --> /Users/jveatch/repos/wovelocity/GridIron/GridIron/server/src/web/middleware/actix/mod.rs:16:49
   |
16 |     fn get_authn_id<S>(req: &HttpRequest<S>) -> impl Future<Item=Option<Uuid>, Error=Error> {
   |                             ---------------     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime `'static` required
   |                             |
   |                             help: add explicit lifetime `'static` to the type of `req`: `&'static actix_web::HttpRequest<S>`

If I add the static lifetime to the request parameter, I get an error in the send function below where I'm calling get_authn_id:

error[E0312]: lifetime of reference outlives lifetime of borrowed content...
  --> /Users/jveatch/repos/wovelocity/GridIron/GridIron/server/src/web/middleware/actix/mod.rs:58:62
   |
58 |             let fut = AuthenticationMiddleware::get_authn_id(req)
   |                                                              ^^^
   |
   = note: ...the reference is valid for the static lifetime...
note: ...but the borrowed content is only valid for the anonymous lifetime #2 defined on the method body at 50:5
  --> /Users/jveatch/repos/wovelocity/GridIron/GridIron/server/src/web/middleware/actix/mod.rs:50:5
   |
50 | /     fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
51 | |         let request_path = req.path();
52 | |
53 | |         if self.unauthenticated_routes.iter().any(|path| request_path.starts_with(path)) {
...  |
74 | |         }
75 | |     }
   | |_____^

It’s the same underlying issue about capturing references in the closure, except now it’s capturing &HttpRequest :slight_smile:

You need to get whatever state you need out of the request outside the closure, and then move that state into the closure. This state needs to be either owned (no references) or it needs to hold 'static types.

But why are you using a future continuation to get some info from the cookies? That seems like it’ll not involve any async action and so you should just get that state/info on the spot, without deferring that to another future. If you have async action that follows this part, then it should be put into a closure.

It’s useful to keep in mind that the futures combinators expressed via closures don’t actually execute the closures in that lexical scope - they only capture the “instructions” and the state needed to execute them later (when the previous future resolves).

Keep in mind that these closures are not executed in the function where you define them. They will run somewhere else, sometime later, so everything they touch has to be long lived and independent of any references from function's arguments, local variables, etc.

In practice the actix handlers have to prepare all the data they can outside of closures, then move the data (owned, not references) inside the closures.

Thanks @kornel and @vitalyd for the tremendous help. I got it working based on your feedback.