mirror of https://github.com/rwf2/Rocket.git
329 lines
12 KiB
Markdown
329 lines
12 KiB
Markdown
+++
|
|
summary = "managing application state and connecting to databases"
|
|
+++
|
|
|
|
# State
|
|
|
|
Many web applications have a need to maintain state. This can be as simple as
|
|
maintaining a counter for the number of visits or as complex as needing to
|
|
access job queues and multiple databases. Rocket provides the tools to enable
|
|
these kinds of interactions in a safe and simple manner.
|
|
|
|
## Managed State
|
|
|
|
The enabling feature for maintaining state is _managed state_. Managed state, as
|
|
the name implies, is state that Rocket manages for your application. The state
|
|
is managed on a per-type basis: Rocket will manage at most one value of a given
|
|
type.
|
|
|
|
The process for using managed state is simple:
|
|
|
|
1. Call `manage` on the `Rocket` instance corresponding to your application
|
|
with the initial value of the state.
|
|
2. Add a `&State<T>` type to any request handler, where `T` is the type of the
|
|
value passed into `manage`.
|
|
|
|
! note: All managed state must be thread-safe.
|
|
|
|
Because Rocket automatically parallelizes your application, handlers can
|
|
concurrently access managed state. As a result, managed state must be
|
|
thread-safe. Thanks to Rust, this condition is checked at compile-time by
|
|
ensuring that the type of values you store in managed state implement `Send` +
|
|
`Sync`.
|
|
|
|
|
|
### Adding State
|
|
|
|
To instruct Rocket to manage state for your application, call the
|
|
[`manage`](@api/master/rocket/struct.Rocket.html#method.manage) method
|
|
on an instance of `Rocket`. For example, to ask Rocket to manage a `HitCount`
|
|
structure with an internal `AtomicUsize` with an initial value of `0`, we can
|
|
write the following:
|
|
|
|
```rust
|
|
use std::sync::atomic::AtomicUsize;
|
|
|
|
struct HitCount {
|
|
count: AtomicUsize
|
|
}
|
|
|
|
rocket::build().manage(HitCount { count: AtomicUsize::new(0) });
|
|
```
|
|
|
|
The `manage` method can be called any number of times as long as each call
|
|
refers to a value of a different type. For instance, to have Rocket manage both
|
|
a `HitCount` value and a `Config` value, we can write:
|
|
|
|
```rust
|
|
# use std::sync::atomic::AtomicUsize;
|
|
# struct HitCount { count: AtomicUsize }
|
|
# type Config = &'static str;
|
|
# let user_input = "input";
|
|
|
|
rocket::build()
|
|
.manage(HitCount { count: AtomicUsize::new(0) })
|
|
.manage(Config::from(user_input));
|
|
```
|
|
|
|
### Retrieving State
|
|
|
|
State that is being managed by Rocket can be retrieved via the
|
|
[`&State`](@api/master/rocket/struct.State.html) type: a [request
|
|
guard](../requests/#request-guards) for managed state. To use the request guard,
|
|
add a `&State<T>` type to any request handler, where `T` is the type of the
|
|
managed state. For example, we can retrieve and respond with the current
|
|
`HitCount` in a `count` route as follows:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
# fn main() {}
|
|
|
|
# use std::sync::atomic::{AtomicUsize, Ordering};
|
|
# struct HitCount { count: AtomicUsize }
|
|
|
|
use rocket::State;
|
|
|
|
#[get("/count")]
|
|
fn count(hit_count: &State<HitCount>) -> String {
|
|
let current_count = hit_count.count.load(Ordering::Relaxed);
|
|
format!("Number of visits: {}", current_count)
|
|
}
|
|
```
|
|
|
|
You can retrieve more than one `&State` type in a single route as well:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
# fn main() {}
|
|
|
|
# struct HitCount;
|
|
# struct Config;
|
|
# use rocket::State;
|
|
|
|
#[get("/state")]
|
|
fn state(hit_count: &State<HitCount>, config: &State<Config>) { /* .. */ }
|
|
```
|
|
|
|
! warning
|
|
|
|
If you request a `&State<T>` for a `T` that is not `managed`, Rocket will
|
|
refuse to start your application. This prevents what would have been an
|
|
unmanaged state runtime error. Unmanaged state is detected at runtime through
|
|
[_sentinels_](@api/master/rocket/trait.Sentinel.html), so there are limitations. If a
|
|
limitation is hit, Rocket still won't call the offending route. Instead,
|
|
Rocket will log an error message and return a **500** error to the client.
|
|
|
|
You can find a complete example using the `HitCount` structure in the [state
|
|
example on GitHub](@git/master/examples/state) and learn more about the [`manage`
|
|
method](@api/master/rocket/struct.Rocket.html#method.manage) and [`State`
|
|
type](@api/master/rocket/struct.State.html) in the API docs.
|
|
|
|
### Within Guards
|
|
|
|
Because `State` is itself a request guard, managed state can be retrieved from
|
|
another request guard's implementation using either [`Request::guard()`] or
|
|
[`Rocket::state()`]. In the following code example, the `Item` request guard
|
|
retrieves `MyConfig` from managed state using both methods:
|
|
|
|
```rust
|
|
use rocket::State;
|
|
use rocket::request::{self, Request, FromRequest};
|
|
use rocket::outcome::IntoOutcome;
|
|
use rocket::http::Status;
|
|
|
|
# struct MyConfig { user_val: String };
|
|
struct Item<'r>(&'r str);
|
|
|
|
#[rocket::async_trait]
|
|
impl<'r> FromRequest<'r> for Item<'r> {
|
|
type Error = ();
|
|
|
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, ()> {
|
|
// Using `State` as a request guard. Use `inner()` to get an `'r`.
|
|
let outcome = request.guard::<&State<MyConfig>>().await
|
|
.map(|my_config| Item(&my_config.user_val));
|
|
|
|
// Or alternatively, using `Rocket::state()`:
|
|
let outcome = request.rocket().state::<MyConfig>()
|
|
.map(|my_config| Item(&my_config.user_val))
|
|
.or_forward(Status::InternalServerError);
|
|
|
|
outcome
|
|
}
|
|
}
|
|
```
|
|
|
|
[`Request::guard()`]: @api/master/rocket/struct.Request.html#method.guard
|
|
[`Rocket::state()`]: @api/master/rocket/struct.Rocket.html#method.state
|
|
|
|
## Request-Local State
|
|
|
|
While managed state is *global* and available application-wide, request-local
|
|
state is *local* to a given request, carried along with the request, and dropped
|
|
once the request is completed. Request-local state can be used whenever a
|
|
`Request` is available, such as in a fairing, a request guard, or a responder.
|
|
|
|
Request-local state is *cached*: if data of a given type has already been
|
|
stored, it will be reused. This is especially useful for request guards that
|
|
might be invoked multiple times during routing and processing of a single
|
|
request, such as those that deal with authentication.
|
|
|
|
As an example, consider the following request guard implementation for
|
|
`RequestId` that uses request-local state to generate and expose a unique
|
|
integer ID per request:
|
|
|
|
```rust
|
|
# #[macro_use] extern crate rocket;
|
|
# fn main() {}
|
|
# use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
use rocket::request::{self, Request, FromRequest};
|
|
|
|
/// A global atomic counter for generating IDs.
|
|
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
/// A type that represents a request's ID.
|
|
struct RequestId(pub usize);
|
|
|
|
/// Returns the current request's ID, assigning one only as necessary.
|
|
#[rocket::async_trait]
|
|
impl<'r> FromRequest<'r> for &'r RequestId {
|
|
type Error = ();
|
|
|
|
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
|
// The closure passed to `local_cache` will be executed at most once per
|
|
// request: the first time the `RequestId` guard is used. If it is
|
|
// requested again, `local_cache` will return the same value.
|
|
request::Outcome::Success(request.local_cache(|| {
|
|
RequestId(ID_COUNTER.fetch_add(1, Ordering::Relaxed))
|
|
}))
|
|
}
|
|
}
|
|
|
|
#[get("/")]
|
|
fn id(id: &RequestId) -> String {
|
|
format!("This is request #{}.", id.0)
|
|
}
|
|
```
|
|
|
|
Note that, without request-local state, it would not be possible to:
|
|
|
|
1. Associate a piece of data, here an ID, directly with a request.
|
|
2. Ensure that a value is generated at most once per request.
|
|
|
|
For more examples, see the [`FromRequest` request-local state] documentation,
|
|
which uses request-local state to cache expensive authentication and
|
|
authorization computations, and the [`Fairing`] documentation, which uses
|
|
request-local state to implement request timing.
|
|
|
|
[`FromRequest` request-local state]: @api/master/rocket/request/trait.FromRequest.html#request-local-state
|
|
[`Fairing`]: @api/master/rocket/fairing/trait.Fairing.html#request-local-state
|
|
|
|
## Databases
|
|
|
|
Rocket includes built-in, ORM-agnostic support for databases via
|
|
[`rocket_db_pools`]. The library simplifies accessing one or more databases via
|
|
connection pools: data structures that maintain active database connections for
|
|
use in the application. Database configuration occurs via Rocket's regular
|
|
[configuration](../configuration/) mechanisms.
|
|
|
|
Connecting your Rocket application to a database using `rocket_db_pools` happens
|
|
in three simple steps:
|
|
|
|
1. Choose your database(s) from the [supported database driver list]. Add
|
|
`rocket_db_pools` as a dependency in `Cargo.toml` with respective database
|
|
driver feature(s) enabled:
|
|
|
|
```toml
|
|
[dependencies.rocket_db_pools]
|
|
version = "0.1.0"
|
|
features = ["sqlx_sqlite"]
|
|
```
|
|
|
|
2. Choose a name for your database, here `sqlite_logs`. [Configure] _at least_
|
|
a URL for the database under `databases.$name` (here, in `Rocket.toml`),
|
|
where `$name` is your choice of database name:
|
|
|
|
```toml
|
|
[default.databases.sqlite_logs]
|
|
url = "/path/to/database.sqlite"
|
|
```
|
|
|
|
3. [Derive `Database`] for a unit `Type` (`Logs` here) which wraps the selected
|
|
driver's `Pool` type from the [supported database driver list]. Decorated the
|
|
struct with `#[database("$name")]` with the `$name` from `2.`. Attach
|
|
`$Type::init()` to your application's `Rocket` to initialize the database
|
|
pool and use [`Connection<$Type>`] as a request guard to retrieve an active
|
|
database connection:
|
|
|
|
```rust
|
|
#[macro_use] extern crate rocket;
|
|
|
|
use rocket_db_pools::{Database, Connection};
|
|
use rocket_db_pools::sqlx::{self, Row};
|
|
|
|
#[derive(Database)]
|
|
#[database("sqlite_logs")]
|
|
struct Logs(sqlx::SqlitePool);
|
|
|
|
#[get("/<id>")]
|
|
async fn read(mut db: Connection<Logs>, id: i64) -> Option<String> {
|
|
sqlx::query("SELECT content FROM logs WHERE id = ?").bind(id)
|
|
.fetch_one(&mut **db).await
|
|
.and_then(|r| Ok(r.try_get(0)?))
|
|
.ok()
|
|
}
|
|
|
|
#[launch]
|
|
fn rocket() -> _ {
|
|
rocket::build().attach(Logs::init()).mount("/", routes![read])
|
|
}
|
|
```
|
|
|
|
For complete usage details, see [`rocket_db_pools`].
|
|
|
|
[`rocket_db_pools`]: @api/master/rocket_db_pools/index.html
|
|
[supported database driver list]: @api/master/rocket_db_pools/index.html#supported-drivers
|
|
[database driver features]: @api/master/rocket_db_pools/index.html#supported-drivers
|
|
[`Pool`]: @api/master/rocket_db_pools/index.html#supported-drivers
|
|
[Configure]: @api/master/rocket_db_pools/index.html#configuration
|
|
[Derive `Database`]: @api/master/rocket_db_pools/derive.Database.html
|
|
[`Connection<$Type>`]: @api/master/rocket_db_pools/struct.Connection.html
|
|
|
|
### Driver Features
|
|
|
|
Only the minimal features for each driver crate are enabled by
|
|
`rocket_db_pools`. To use additional driver functionality exposed via its
|
|
crate's features, you'll need to depend on the crate directly with those
|
|
features enabled in `Cargo.toml`:
|
|
|
|
```toml
|
|
[dependencies.sqlx]
|
|
version = "0.7"
|
|
default-features = false
|
|
features = ["macros", "migrate"]
|
|
|
|
[dependencies.rocket_db_pools]
|
|
version = "0.1.0"
|
|
features = ["sqlx_sqlite"]
|
|
```
|
|
|
|
### Synchronous ORMs
|
|
|
|
While [`rocket_db_pools`] provides support for `async` ORMs and should thus be
|
|
the preferred solution, Rocket also provides support for synchronous, blocking
|
|
ORMs like [Diesel] via the [`rocket_sync_db_pools`] library, which you may wish
|
|
to explore. Usage is similar, but not identical, to `rocket_db_pools`. See the
|
|
crate docs for complete usage details.
|
|
|
|
[`rocket_sync_db_pools`]: @api/master/rocket_sync_db_pools/index.html
|
|
[diesel]: https://diesel.rs/
|
|
|
|
### Examples
|
|
|
|
For examples of CRUD-like "blog" JSON APIs backed by a SQLite database driven by
|
|
each of `sqlx`, `diesel`, and `rusqlite`, with migrations run automatically for
|
|
the former two drivers, see the [databases example](@git/master/examples/databases). The
|
|
`sqlx` example uses `rocket_db_pools` while the `diesel` and `rusqlite` examples
|
|
use `rocket_sync_db_pools`.
|