4.0 KiB
Sessions for Rocket.rs
Adding cookie-based sessions to a rocket application is extremely simple with this crate.
Sessions are used to share data between related requests, such as user authentication, shopping basket, form values that failed validation for re-filling, etc.
Configuration
The implementation is generic to support any type as session data: a custom struct, String
,
HashMap
, or perhaps serde_json::Value
. You're free to choose.
The session lifetime, cookie name, and other parameters can be configured by calling chained methods on the fairing. When a session expires, the data associated with it is dropped.
Example: Session::fairing().with_lifetime(Duration::from_secs(15))
Usage
To use session in a route, first make sure you have the fairing attached by calling
rocket.attach(Session::fairing())
at start-up, and then add something like session : Session
to the parameter list of your route(s). Everything else--session init, expiration, cookie
management--is done for you behind the scenes.
Session data is accessed in a closure run in the session context, using the session.tap()
method. This closure runs inside a per-session mutex, avoiding simultaneous mutation
from different requests. Try to avoid lengthy operations inside the closure,
as it effectively blocks any other request to session-enabled routes by the client.
Every request to a session-enabled route extends the session's lifetime to the full configured time (defaults to 1 hour). Automatic clean-up removes expired sessions to make sure the session list does not waste memory.
Examples
More examples are in the "examples" folder - run with cargo run --example=NAME
Basic Example
This simple example uses u64 as the session variable; note that it can be a struct, map, or anything else,
it just needs to implement Send + Sync + Default
.
#[macro_use]
extern crate rocket;
type Session<'a> = rocket_session::Session<'a, u64>;
#[launch]
fn rocket() -> _ {
rocket::build()
.attach(Session::fairing())
.mount("/", routes![index])
}
#[get("/")]
fn index(session: Session) -> String {
let count = session.tap(|n| {
// Change the stored value (it is &mut)
*n += 1;
// Return something to the caller.
// This can be any type, 'tap' is generic.
*n
});
format!("{} visits", count)
}
Extending Session by a Trait
The .tap()
method is powerful, but sometimes you may wish for something more convenient.
Here is an example of using a custom trait and the json_dotpath
crate to implement
a polymorphic store based on serde serialization.
Note that this approach is prone to data races if you're accessing the session object multiple times per request,
since every method contains its own .tap()
. It may be safer to simply call the .dot_*()
methods manually in one shared closure.
use serde_json::Value;
use serde::de::DeserializeOwned;
use serde::Serialize;
use json_dotpath::DotPaths;
pub type Session<'a> = rocket_session::Session<'a, serde_json::Map<String, Value>>;
pub trait SessionAccess {
fn get<T: DeserializeOwned>(&self, path: &str) -> Option<T>;
fn take<T: DeserializeOwned>(&self, path: &str) -> Option<T>;
fn replace<O: DeserializeOwned, N: Serialize>(&self, path: &str, new: N) -> Option<O>;
fn set<T: Serialize>(&self, path: &str, value: T);
fn remove(&self, path: &str) -> bool;
}
impl<'a> SessionAccess for Session<'a> {
fn get<T: DeserializeOwned>(&self, path: &str) -> Option<T> {
self.tap(|data| data.dot_get(path))
}
fn take<T: DeserializeOwned>(&self, path: &str) -> Option<T> {
self.tap(|data| data.dot_take(path))
}
fn replace<O: DeserializeOwned, N: Serialize>(&self, path: &str, new: N) -> Option<O> {
self.tap(|data| data.dot_replace(path, new))
}
fn set<T: Serialize>(&self, path: &str, value: T) {
self.tap(|data| data.dot_set(path, value));
}
fn remove(&self, path: &str) -> bool {
self.tap(|data| data.dot_remove(path))
}
}