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?
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.
Most helpful comment
The following code will only allocate a
Stringfor the user agent once, and reuse it each time the user agent is requested. Another allocation-free option would be to callrequest.headers.getinfrom_requestwithout caching. Which approach is more costly will depend on how many times the involved routes usually invoke the guard.I think that's the best we can do right now as long as
TypeIdonly works with'static, so I will close this for now. We can re-open if a more compelling use case is found.