Giter Site home page Giter Site logo

Comments (18)

SergioBenitez avatar SergioBenitez commented on June 14, 2024 12

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

from rocket.

ChrisBuchholz avatar ChrisBuchholz commented on June 14, 2024 3

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.

from rocket.

SergioBenitez avatar SergioBenitez commented on June 14, 2024 2

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.

from rocket.

ChrisBuchholz avatar ChrisBuchholz commented on June 14, 2024 1

@SergioBenitez: of course it does!

from rocket.

RandomInsano avatar RandomInsano commented on June 14, 2024 1

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

from rocket.

SergioBenitez avatar SergioBenitez commented on June 14, 2024

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

from rocket.

hyena avatar hyena commented on June 14, 2024

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.

from rocket.

hyena avatar hyena commented on June 14, 2024

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?

from rocket.

SergioBenitez avatar SergioBenitez commented on June 14, 2024

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

from rocket.

hyena avatar hyena commented on June 14, 2024

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.

from rocket.

SergioBenitez avatar SergioBenitez commented on June 14, 2024

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.

from rocket.

ChrisBuchholz avatar ChrisBuchholz commented on June 14, 2024

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?

from rocket.

PAStheLoD avatar PAStheLoD commented on June 14, 2024

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!

from rocket.

hyena avatar hyena commented on June 14, 2024

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.

from rocket.

RandomInsano avatar RandomInsano commented on June 14, 2024

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

from rocket.

hyena avatar hyena commented on June 14, 2024

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.

from rocket.

greglearns avatar greglearns commented on June 14, 2024

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?

from rocket.

SergioBenitez avatar SergioBenitez commented on June 14, 2024

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

from rocket.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.