-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Object available to every route ad-hoc #53
Comments
Request Guards should fit the bill nicely. Did you have a chance to read through that section of the guide? |
Just encountered this too. It's a pretty common issue in these frameworks, I think! In Iron, I got around it by implementing my handlers as |
I suppose one way to do this would be with some mutable data set up inside a |
@hyena Yep! Ideally, with time, the most common versions of something like this will be encapsulated by libraries, so you don't have to deal with this yourself. |
Thanks for sanity checking that! :) I'll use that approach for now. Looking ahead, do you think Rocket should stick with this approach or something else? Iron does a weird thing where Requests have a http://ironframework.io/doc/iron/typemap/struct.TypeMap.html that can have one object per type put into it, usually set up via middleware. But that doesn't seem like a clear match here (and the one object per type approach feels a little hacky too). EDIT: The contrib/template approach makes sense. Something flexible that can be set up and associated with routes. |
Rocket will definitely stick with this approach. It is part of what makes Rocket, Rocket. Something like Request guards aren't meant to be a hack. They are designed exactly for use cases like this. You have some global state; it needs to be global. Unfortunately, there's no way around it. But request guards let you reason about that global state at a local type-level, which I think is awesome. |
If I "just" were to use a |
For the same reasons you'd use a request guard otherwise: so that a handler's signature directly declares its requirements, and as a mechanism for centralizing policy. The former is straightforward, but the latter may seem irrelevant here. Let me explain. Say you were to use r2d2 to get a connection via pool::get. The method returns a On a more practical note, Rocket is likely to ship with a library for handling database connections in the near future, and it'll almost certainly use request guards for the reasons I mention above. Doing it this way as well, while there's no official library, will let you migrate to the official solution in short order. :) |
@SergioBenitez: of course it does! |
How would that look in a larger system (that uses and depends on a lot of external services, let's say a relational DB for customer data, emitting events to a firehose "global" event stream such as Kafka, logging metrics to something statsd-like, sending a transactional email, maybe even grabbing some other semi-internal services, one for User Authentication, and a different one for Request Authorization)? Sure, encoding everything into types at the function level gives enormously powerful semantics, but maybe a bit too unwieldy syntax. Anyway, maybe there's simply no great solution for handling these orthogonal concerns (at least I know of none), so far every library/crate/framework encodes some parts of itself through the type system (understandable, as that's the most obvious possibility in Rust), but usually people don't want to structure their programs like matyoskha dolls. (Everything would have to go inside one big block where let's say the r2d2 pool is available.) Of course I could be misunderstanding the problem, so thank you in advance for any helpful reply! |
It's an interesting question. I got a working solution to my problem with The problem from my perspective isn't an unwieldy syntax. If there's no initialization required, request_guards work fine for passing typed parameters to handlers. We could even implement our own trait that was a simplified form of The awkward part right now in my opinion is the initialization: How to, for example, setup a threadpool or a connection to logging metrics, etc. Perhaps what I'd really like to do is do the setup in I'm still not so sure. Pyramid handles this with middleware and the ability to register a 'request property' that can reify a property on a The tl;dr is that the way request guards make global state available to route handlers seems like a good fit. But it feels awkward currently to setup global state and make it available to the request guards. |
+1 for the difficulty in initializing state. I was playing with image and Rocket yesterday to implement a hit counter for some practice and got stuck on 'how am I going to initialize my counts?' Maybe a good hitcounter example would be helpful to illustrate the 'right' way of sharing state between requests (I couldn't grok how the Cookie example guard worked under the hood). WTR @PAStheLoD's question I'd be fine keeping all kinds of state Rocket routes needed within a single Mutexed struct (db pool, logging system, etc) and just name it MasterState or something. If Rocket's living inside a larger app, I'd just need references to what Rocket needed in there. As an example from my weekend project, I need to store a memory cache of image tiles somewhere and initialize as well as access my counters. Being able to pass state to the request guards would be mighty handy, but in this example it would really just need a good way to be initialized. |
Indeed, using This is how I set up usage of a DB connection pool: lazy_static! {
pub static ref DB_POOL: r2d2::Pool<ConnectionManager<PgConnection>> = {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set.");
let config = r2d2::Config::default();
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool = r2d2::Pool::new(config, manager).expect("Failed to create pool.");
pool
};
}
pub struct DB(r2d2::PooledConnection<ConnectionManager<PgConnection>>);
impl DB {
pub fn conn(&self) -> &PgConnection {
&*self.0
}
}
impl<'a, 'r> FromRequest<'a, 'r> for DB {
type Error = r2d2::GetTimeout;
fn from_request(_: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
match DB_POOL.get() {
Ok(conn) => Success(DB(conn)),
Err(e) => Failure((Status::InternalServerError, e)),
}
}
} The ceremonial practice of settings up the |
Thanks for the example @ChrisBuchholz! I also found this is how the Template extension was implemented. |
We could build such an abstraction today. But I think there are two other requirements a solution should have:
In Pyramid (sorry to keep going back to that, but it's a useful comparison), a common approach to making things like connections available to handlers is to register them as named properties on the request object, setup by middleware before the handler sees them. Rocket's current approach is somewhat different if I understand it correctly: We write a FromRequest implementation for a type and all handlers that use one of those types in its arguments uses that implementation to create one. They can even have multiple ones on a single handler. Probably the best approach for now is to stick with Rocket's request guard style and see if we can wrap a new sort of trait around it, one that lets us initialize more easily at a non-static level. |
The functionality described on this page for supporting things like connection pools is different than the State that was merged into master last week, correct? |
@greglearns Yes and no. You can use managed state to hold the connection pool, but you'll still need to implement a request guard that fetches a connection instance from the pool. I'd like for this last step to be removed as well, even though it's a small one, since it's so common. Still, I believe we can close this issue once managed state lands. |
Managed state has now fully landed in master! Managed State is a feature composed of three items:
Here's an example on how to use these to have Rocket manage a start-up initialized configuration, from the use rocket::State;
// In a real application, this would likely be more complex.
struct MyConfig(String);
#[get("/")]
fn index(state: State<MyConfig>) -> String {
format!("The config value is: {}", state.0)
}
#[get("/raw")]
fn raw_config_value<'r>(state: State<'r, MyConfig>) -> &'r str {
// use `inner()` to get a lifetime longer than `deref` gives us
state.inner().0.as_str()
}
fn main() {
let config = MyConfig("user input".to_string());
rocket::ignite()
.mount("/", routes![index, raw_config_value])
.manage(config)
.launch()
} You can call What I am most excited about is the warning: 'HitCount' is not currently being managed by Rocket, #[warn(unmanaged_state)] on by default
--> src/main.rs:16:21
|
16 | fn index(hit_count: State<HitCount>) -> content::HTML<String> {
| ^^^^^^^^^^^^^^^
|
help: maybe add a call to `manage` here?
--> src/main.rs:29:5
|
29 | rocket::ignite()
| ^^^^^^^^^^^^^^^^
= note: this 'State' request guard will always fail
warning: 'HitCount' is not currently being managed by Rocket, #[warn(unmanaged_state)] on by default
--> src/main.rs:24:21
|
24 | fn count(hit_count: State<HitCount>) -> String {
| ^^^^^^^^^^^^^^^
|
help: maybe add a call to `manage` here?
--> src/main.rs:29:5
|
29 | rocket::ignite()
| ^^^^^^^^^^^^^^^^
= note: this 'State' request guard will always fail These warnings were captured by removing the I'll be writing a longer form article about managed state for the |
It's not obvious how to make an object or some data available to all routes (ad-hoc). I'm thinking something like a DB connection pool object that would get passed to every route as an argument.
How would one do that with rocket?
The text was updated successfully, but these errors were encountered: