mirror of
https://github.com/rwf2/Rocket.git
synced 2025-02-18 22:52:01 +00:00
The guide is now in docs/guide. All other site assets are being migrated to a separate repository. The guide markup has been upgraded to take advantages of improvements in the static site generator used to build the Rocket website.
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`.
|