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:
- Call
manage
on theRocket
instance corresponding to your application with the initial value of the state. - Add a
&State<T>
type to any request handler, whereT
is the type of the value passed intomanage
.
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
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:
1 2 3 4 5 6 7
use AtomicUsize;
build.manage;
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:
1 2 3
.manage
.manage;
build
Retrieving State
State that is being managed by Rocket can be retrieved via the &State
type: a request guard 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:
1 2 3 4 5 6 7
use State;
You can retrieve more than one &State
type in a single route as well:
1 2
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, 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 and learn more about the manage
method and State
type 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
use State;
use ;
use IntoOutcome;
use Status;
;
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
use ;
/// A global atomic counter for generating IDs.
static ID_COUNTER: AtomicUsize = new;
/// A type that represents a request's ID.
;
/// Returns the current request's ID, assigning one only as necessary.
Note that, without request-local state, it would not be possible to:
- Associate a piece of data, here an ID, directly with a request.
- 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.
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 mechanisms.
Connecting your Rocket application to a database using rocket_db_pools
happens in three simple steps:
-
Choose your database(s) from the supported database driver list. Add
rocket_db_pools
as a dependency inCargo.toml
with respective database driver feature(s) enabled:1 2 3
[] = "0.1.0" = ["sqlx_sqlite"]
-
Choose a name for your database, here
sqlite_logs
. Configure at least a URL for the database underdatabases.$name
(here, inRocket.toml
), where$name
is your choice of database name:1 2
[] = "/path/to/database.sqlite"
-
Derive
Database
for a unitType
(Logs
here) which wraps the selected driver'sPool
type from the supported database driver list. Decorated the struct with#[database("$name")]
with the$name
from2.
. Attach$Type::init()
to your application'sRocket
to initialize the database pool and useConnection<$Type>
as a request guard to retrieve an active database connection:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
extern crate rocket; use ; use ; ; async
For complete usage details, see rocket_db_pools
.
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
:
1 2 3 4 5 6 7 8
[]
= "0.7"
= false
= ["macros", "migrate"]
[]
= "0.1.0"
= ["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.
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. The sqlx
example uses rocket_db_pools
while the diesel
and rusqlite
examples use rocket_sync_db_pools
.