diff --git a/Cargo.toml b/Cargo.toml index 96e8146..e984ec8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "rocket_session" -version = "0.2.2" +version = "0.3.0" authors = ["Ondřej Hruška "] -edition = "2018" +edition = "2021" license = "MIT" description = "Rocket.rs plug-in for cookie-based sessions holding arbitrary data" repository = "https://git.ondrovo.com/packages/rocket_session" @@ -17,5 +17,5 @@ categories = [ [dependencies] rand = "0.8" -rocket = "0.4" -parking_lot = "0.11" +rocket = "0.5.0-rc.2" +parking_lot = "0.12" diff --git a/README.md b/README.md index 2c05c13..83212b5 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ The implementation is generic to support any type as session data: a custom stru 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 @@ -31,7 +33,7 @@ the session list does not waste memory. ## Examples -(More examples are in the examples folder) +More examples are in the "examples" folder - run with `cargo run --example=NAME` ### Basic Example @@ -39,34 +41,32 @@ This simple example uses u64 as the session variable; note that it can be a stru it just needs to implement `Send + Sync + Default`. ```rust -#![feature(proc_macro_hygiene, decl_macro)] -#[macro_use] extern crate rocket; - -use std::time::Duration; +#[macro_use] +extern crate rocket; -// It's convenient to define a type alias: -pub type Session<'a> = rocket_session::Session<'a, u64>; +type Session<'a> = rocket_session::Session<'a, u64>; -fn main() { - rocket::ignite() +#[launch] +fn rocket() -> _ { + rocket::build() .attach(Session::fairing()) .mount("/", routes![index]) - .launch(); } #[get("/")] fn index(session: Session) -> String { let count = session.tap(|n| { - // Change the stored value (it is &mut) + // Change the stored value (it is &mut) *n += 1; - // Return something to the caller. - // This can be any type, 'tap' is generic. + // Return something to the caller. + // This can be any type, 'tap' is generic. *n }); format!("{} visits", count) } + ``` ## Extending Session by a Trait @@ -76,8 +76,8 @@ The `.tap()` method is powerful, but sometimes you may wish for something more c 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, since every method contains its own `.tap()`. -It may be safer to simply call the `.dot_*()` methods manually in one shared closure. +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. ```rust use serde_json::Value; diff --git a/examples/dog_list/main.rs b/examples/dog_list/main.rs index da9e1f5..d02dc62 100644 --- a/examples/dog_list/main.rs +++ b/examples/dog_list/main.rs @@ -1,21 +1,20 @@ -#![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate rocket; -use rocket::response::content::Html; +use rocket::response::content::RawHtml; use rocket::response::Redirect; type Session<'a> = rocket_session::Session<'a, Vec>; -fn main() { - rocket::ignite() +#[launch] +fn rocket() -> _ { + rocket::build() .attach(Session::fairing()) .mount("/", routes![index, add, remove]) - .launch(); } #[get("/")] -fn index(session: Session) -> Html { +fn index(session: Session) -> RawHtml { let mut page = String::new(); page.push_str( r#" @@ -38,7 +37,7 @@ fn index(session: Session) -> Html { } }); page.push_str(""); - Html(page) + RawHtml(page) } #[post("/add", data = "")] diff --git a/examples/minimal/main.rs b/examples/minimal/main.rs new file mode 100644 index 0000000..05efd1b --- /dev/null +++ b/examples/minimal/main.rs @@ -0,0 +1,28 @@ +#[macro_use] +extern crate rocket; + +use std::time::Duration; + +type Session<'a> = rocket_session::Session<'a, u64>; + +#[launch] +fn rocket() -> _ { + // This session expires in 15 seconds as a demonstration of session configuration + rocket::build() + .attach(Session::fairing().with_lifetime(Duration::from_secs(15))) + .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) +} diff --git a/examples/visit_counter/main.rs b/examples/visit_counter/main.rs index e65d053..6502a1b 100644 --- a/examples/visit_counter/main.rs +++ b/examples/visit_counter/main.rs @@ -2,11 +2,10 @@ //! //! The expiry time is set to 10 seconds to illustrate how a session is cleared if inactive. -#![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate rocket; -use rocket::response::content::Html; +use rocket::response::content::RawHtml; use std::time::Duration; #[derive(Default, Clone)] @@ -18,8 +17,9 @@ struct SessionData { // It's convenient to define a type alias: type Session<'a> = rocket_session::Session<'a, SessionData>; -fn main() { - rocket::ignite() +#[launch] +fn rocket() -> _ { + rocket::build() .attach( Session::fairing() // 10 seconds of inactivity until session expires @@ -30,11 +30,10 @@ fn main() { .with_cookie_len(20), ) .mount("/", routes![index, about]) - .launch(); } #[get("/")] -fn index(session: Session) -> Html { +fn index(session: Session) -> RawHtml { // Here we build the entire response inside the 'tap' closure. // While inside, the session is locked to parallel changes, e.g. @@ -42,7 +41,7 @@ fn index(session: Session) -> Html { session.tap(|sess| { sess.visits1 += 1; - Html(format!( + RawHtml(format!( r##"

Home

@@ -55,14 +54,14 @@ fn index(session: Session) -> Html { } #[get("/about")] -fn about(session: Session) -> Html { +fn about(session: Session) -> RawHtml { // Here we return a value from the tap function and use it below let count = session.tap(|sess| { sess.visits2 += 1; sess.visits2 }); - Html(format!( + RawHtml(format!( r##"

About

diff --git a/src/lib.rs b/src/lib.rs index 44058da..60c1f3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,3 @@ -use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; -use rand::{Rng, rngs::OsRng}; - -use rocket::{ - fairing::{self, Fairing, Info}, - http::{Cookie, Status}, - request::FromRequest, - Outcome, Request, Response, Rocket, State, -}; - use std::borrow::Cow; use std::collections::HashMap; use std::fmt::{self, Display, Formatter}; @@ -15,6 +5,16 @@ use std::marker::PhantomData; use std::ops::Add; use std::time::{Duration, Instant}; +use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; +use rand::{rngs::OsRng, Rng}; +use rocket::{ + fairing::{self, Fairing, Info}, + http::{Cookie, Status}, + outcome::Outcome, + request::FromRequest, + Build, Request, Response, Rocket, State, +}; + /// Session store (shared state) #[derive(Debug)] pub struct SessionStore @@ -114,29 +114,29 @@ where D: 'static + Sync + Send + Default, { /// The shared state reference - store: State<'a, SessionStore>, + store: &'a State>, /// Session ID id: &'a SessionID, } -impl<'a, 'r, D> FromRequest<'a, 'r> for Session<'a, D> +#[rocket::async_trait] +impl<'r, D> FromRequest<'r> for Session<'r, D> where D: 'static + Sync + Send + Default, { type Error = (); - fn from_request(request: &'a Request<'r>) -> Outcome { - let store: State> = request.guard().unwrap(); + async fn from_request(request: &'r Request<'_>) -> Outcome { + let store = request.guard::<&State>>().await.unwrap(); Outcome::Success(Session { id: request.local_cache(|| { let store_ug = store.inner.upgradable_read(); // Resolve session ID - let id = if let Some(cookie) = request.cookies().get(&store.config.cookie_name) { - Some(SessionID(cookie.value().to_string())) - } else { - None - }; + let id = request + .cookies() + .get(&store.config.cookie_name) + .map(|cookie| SessionID(cookie.value().to_string())); let expires = Instant::now().add(store.config.lifespan); @@ -295,6 +295,7 @@ where } } +#[rocket::async_trait] impl Fairing for SessionFairing where D: 'static + Sync + Send + Default, @@ -302,11 +303,11 @@ where fn info(&self) -> Info { Info { name: "Session", - kind: fairing::Kind::Attach | fairing::Kind::Response, + kind: fairing::Kind::Ignite | fairing::Kind::Response, } } - fn on_attach(&self, rocket: Rocket) -> Result { + async fn on_ignite(&self, rocket: Rocket) -> Result, Rocket> { // install the store singleton Ok(rocket.manage(SessionStore:: { inner: Default::default(), @@ -314,7 +315,7 @@ where })) } - fn on_response<'r>(&self, request: &'r Request, response: &mut Response) { + async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response) { // send the session cookie, if session started let session = request.local_cache(|| SessionID("".to_string()));