Rocket: How to correctly use lifetimes for request-local cache?

Created on 16 May 2019  路  2Comments  路  Source: SergioBenitez/Rocket

Hi, I tried to use the Rocket 0.4.1 to implement a request guard to store some &str from a request.

My implementation for example,

extern crate rocket;

use rocket::Outcome;
use rocket::request::{Outcome as RequestOutcome, Request, FromRequest};

pub struct UserAgent<'a> {
    pub value: Option<&'a str>
}

impl<'a, 'r> FromRequest<'a, 'r> for UserAgent<'a> {
    type Error = ();

    fn from_request(request: &'a Request<'r>) -> RequestOutcome<Self, Self::Error> {
        let value = request.headers().get("user-agent").next();

        Outcome::Success(UserAgent {
            value
        })
    }
}

And I also want it to be cached as a request-local state, so I implement the code below,

impl<'a, 'r> FromRequest<'a, 'r> for &'a UserAgent<'a> {
    type Error = ();

    fn from_request(request: &'a Request<'r>) -> RequestOutcome<Self, Self::Error> {
        Outcome::Success(request.local_cache(|| {
            let value = request.headers().get("user-agent").next();

            UserAgent {
                value
            }
        }))
    }
}

However, after compiling the code, Cargo says,

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/main.rs:27:33
   |
27 |             let value = request.headers().get("user-agent").next();
   |                                 ^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime 'r as defined on the impl at 22:10...
  --> src/main.rs:22:10
   |
22 | impl<'a, 'r> FromRequest<'a, 'r> for &'a UserAgent<'a> {
   |          ^^
note: ...so that the type `rocket::Request<'r>` is not borrowed for too long
  --> src/main.rs:27:25
   |
27 |             let value = request.headers().get("user-agent").next();
   |                         ^^^^^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the type `UserAgent<'_>` will meet its required lifetime bounds
  --> src/main.rs:26:34
   |
26 |         Outcome::Success(request.local_cache(|| {
   |        

My guess is that the local_cache method returns &T where T: Send + Sync + 'static so that &'a UserAgent<'a> can't be returned because 'a doesn't outlive 'static.

Does that mean I cannot use Rocket's lifetimes in my cacheable request guard?

question

Most helpful comment

The following code will only allocate a String for the user agent once, and reuse it each time the user agent is requested. Another allocation-free option would be to call request.headers.get in from_request without caching. Which approach is more costly will depend on how many times the involved routes usually invoke the guard.

use rocket::Outcome;
use rocket::request::{self, FromRequest, Request};

struct UserAgent(Option<String>);

impl<'a, 'r> FromRequest<'a, 'r> for &'a UserAgent {
    type Error = ();

    fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
        Outcome::Success(request.local_cache(|| {
            eprintln!("Getting user agent!");
            let value = request.headers().get("User-Agent").next().map(|x| x.to_string());

            UserAgent(value)
        }))
    }
}

#[get("/")]
fn hello(a: &UserAgent, b: &UserAgent, c: &UserAgent) -> String {
    format!("{:?} = {:?} = {:?}", a.0, b.0, c.0)
}

I think that's the best we can do right now as long as TypeId only works with 'static, so I will close this for now. We can re-open if a more compelling use case is found.

All 2 comments

Yes, the request-local cache can only contain 'static data. I believe support for data constrained to 'r would require rust-lang/rust#41875, but I'm not confident it's the only thing needed.

The following code will only allocate a String for the user agent once, and reuse it each time the user agent is requested. Another allocation-free option would be to call request.headers.get in from_request without caching. Which approach is more costly will depend on how many times the involved routes usually invoke the guard.

use rocket::Outcome;
use rocket::request::{self, FromRequest, Request};

struct UserAgent(Option<String>);

impl<'a, 'r> FromRequest<'a, 'r> for &'a UserAgent {
    type Error = ();

    fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
        Outcome::Success(request.local_cache(|| {
            eprintln!("Getting user agent!");
            let value = request.headers().get("User-Agent").next().map(|x| x.to_string());

            UserAgent(value)
        }))
    }
}

#[get("/")]
fn hello(a: &UserAgent, b: &UserAgent, c: &UserAgent) -> String {
    format!("{:?} = {:?} = {:?}", a.0, b.0, c.0)
}

I think that's the best we can do right now as long as TypeId only works with 'static, so I will close this for now. We can re-open if a more compelling use case is found.

Was this page helpful?
0 / 5 - 0 ratings