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

Closed
ChrisBuchholz opened this Issue Dec 25, 2016 · 18 comments

Comments

6 participants
@ChrisBuchholz

ChrisBuchholz commented Dec 25, 2016

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?

@SergioBenitez

This comment has been minimized.

Owner

SergioBenitez commented Dec 26, 2016

Request Guards should fit the bill nicely. Did you have a chance to read through that section of the guide?

@hyena

This comment has been minimized.

hyena commented Dec 26, 2016

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 move || closures. For rocket, Request Guards seem like a good match for this, but even after reading that section of the guide it isn't immediately clear how to use them to return some shared piece of state data that is set up in main() rather than derived from the client's Request.

@hyena

This comment has been minimized.

hyena commented Dec 26, 2016

I suppose one way to do this would be with some mutable data set up inside a lazy_static!, possibly protected with a mutex, and an associated type whose from_request() implementation just returned a reference to the lazy static data?

@SergioBenitez

This comment has been minimized.

Owner

SergioBenitez commented Dec 26, 2016

@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. Template, for example, does a bit of this for you. These libraries will live in the contrib crate. The next addition is likely to be something that makes managing database connections as easy as returning a Template.

@hyena

This comment has been minimized.

hyena commented Dec 26, 2016

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.

@SergioBenitez

This comment has been minimized.

Owner

SergioBenitez commented Dec 26, 2016

Rocket will definitely stick with this approach. It is part of what makes Rocket, Rocket. Something like TypeMap will never appear in any official Rocket repositories. I don't believe dynamic typing should appear in a language like Rust, especially when you have code generation. That's not to say that there won't be some other mechanism that lets you do some things more easily or that is better suited to some tasks than request guards, however.

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.

@ChrisBuchholz

This comment has been minimized.

ChrisBuchholz commented Dec 26, 2016

If I "just" were to use a lazy_static! for controlling the db connection and connection pool, why would I use a RequestGuard instead of simply use super::DB?

@SergioBenitez

This comment has been minimized.

Owner

SergioBenitez commented Dec 26, 2016

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 Result, so you need to do something in the case of Err. It's likely that you always want to do the same thing, and let's say that it's to return a 503 Service Unavailable if there is no connection available. A request guard lets you do this for every route that needs a connection, in one go, and it makes it so that the type is what is controlling the behavior. The type directly encapsulates what it means to have a connection or be unable to get one. Not only does this result in less code, but it also results in richer types.

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. :)

@ChrisBuchholz

This comment has been minimized.

ChrisBuchholz commented Dec 26, 2016

@SergioBenitez: of course it does!

@PAStheLoD

This comment has been minimized.

PAStheLoD commented Dec 27, 2016

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!

@hyena

This comment has been minimized.

hyena commented Dec 28, 2016

How would that look in a larger system

It's an interesting question. I got a working solution to my problem with lazy_static! and request_guards: https://github.com/hyena/book-of-stars/blob/master/src/main.rs#L92 but even from there it was clear that this would become unwieldly in a situation with too much initialization...

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 FromRequest.

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. lazy_static! and Once can help but I personally find pushing too much logic into them to be awkward.

Perhaps what I'd really like to do is do the setup in main() and do some registration as part the .launch() call chain. The types and objects could still appear as parameters on the route handlers.

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 request object (e.g. https://github.com/Weasyl/weasyl/blob/master/weasyl/middleware.py#L164 + https://github.com/Weasyl/weasyl/blob/master/weasyl/wsgi.py#L40). But typing is less elegant here compared to a rocket's function parameters on the route handler.

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.

@RandomInsano

This comment has been minimized.

RandomInsano commented Dec 28, 2016

+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.

@ChrisBuchholz

This comment has been minimized.

ChrisBuchholz commented Dec 28, 2016

Indeed, using lazy_static! and RequestGuards seems a tad too much, especially if you have a lot of stuff to make available.

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 lazy_static! and making it available as a RequestGuards could surely be abstracted away in to a higher level API which would make it much easier to do.

@RandomInsano

This comment has been minimized.

RandomInsano commented Dec 30, 2016

Thanks for the example @ChrisBuchholz! I also found this is how the Template extension was implemented.

@hyena

This comment has been minimized.

hyena commented Dec 31, 2016

The ceremonial practice of settings up the lazy_static! and making it available as a RequestGuards could surely be abstracted away in to a higher level API which would make it much easier to do.

We could build such an abstraction today. But I think there are two other requirements a solution should have:

  1. The ability to set up objects in main() before igniting the rocket. Putting everything in lazy_static! is a little ugly.

  2. Deciding how these will be made available to individual handlers.

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.

@greglearns

This comment has been minimized.

Contributor

greglearns commented Jan 29, 2017

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?

@SergioBenitez

This comment has been minimized.

Owner

SergioBenitez commented Jan 30, 2017

@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.

@SergioBenitez

This comment has been minimized.

Owner

SergioBenitez commented Feb 3, 2017

Managed state has now fully landed in master! Managed State is a feature composed of three items:

  1. A new manage method on a Rocket instance.
  2. A new State type that acts as a request guard to retrieve managed state.
  3. An unmanaged_state lint pass that warns when State is used for unmanaged state.

Here's an example on how to use these to have Rocket manage a start-up initialized configuration, from the State documentation:

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 manage as many times as you'd like as long as each call passes a value of a different type. If you use a State<T> for a T that doesn't have a corresponding manage call, the guard will return a 500 error to the client.

What I am most excited about is the unmanaged_state lint, which protects you, in most cases, from using a State guard for a type T that isn't managed. When you do this, Rocket emits a warning that looks like the following:

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 manage call from the state example, which shows how to use managed state to implement a hit counter.

I'll be writing a longer form article about managed state for the 0.2 release, which if all goes well, will be released tomorrow. As a result of managed state landing in earnest, I am closing this issue. I've opened #167 to track supporting connection pools in Rocket's contrib.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment