Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45bfd1e41c | ||
|
|
82f9519f95 | ||
|
|
f30b1f5be1 | ||
|
|
b0a2d8c910 | ||
|
|
bc2180d1e4 |
@@ -1,3 +1,12 @@
|
|||||||
|
# [0.4.0]
|
||||||
|
- Update to rocket `0.5.1` and rand `0.9`
|
||||||
|
|
||||||
|
# [0.3.0]
|
||||||
|
|
||||||
|
- Update dependencies
|
||||||
|
- Added new example
|
||||||
|
- Port to rocket `0.5.0-rc.2`
|
||||||
|
|
||||||
# [0.2.2]
|
# [0.2.2]
|
||||||
|
|
||||||
- Update dependencies
|
- Update dependencies
|
||||||
|
|||||||
+5
-5
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rocket_session"
|
name = "rocket_session"
|
||||||
version = "0.2.2"
|
version = "0.4.0"
|
||||||
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Rocket.rs plug-in for cookie-based sessions holding arbitrary data"
|
description = "Rocket.rs plug-in for cookie-based sessions holding arbitrary data"
|
||||||
repository = "https://git.ondrovo.com/packages/rocket_session"
|
repository = "https://git.ondrovo.com/packages/rocket_session"
|
||||||
@@ -16,6 +16,6 @@ categories = [
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.8"
|
rand = "0.9"
|
||||||
rocket = "0.4"
|
rocket = "0.5"
|
||||||
parking_lot = "0.11"
|
parking_lot = "0.12"
|
||||||
|
|||||||
@@ -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
|
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.
|
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
|
## Usage
|
||||||
|
|
||||||
To use session in a route, first make sure you have the fairing attached by calling
|
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
|
## Examples
|
||||||
|
|
||||||
(More examples are in the examples folder)
|
More examples are in the "examples" folder - run with `cargo run --example=NAME`
|
||||||
|
|
||||||
### Basic Example
|
### 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`.
|
it just needs to implement `Send + Sync + Default`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#![feature(proc_macro_hygiene, decl_macro)]
|
#[macro_use]
|
||||||
#[macro_use] extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
use std::time::Duration;
|
type Session<'a> = rocket_session::Session<'a, u64>;
|
||||||
|
|
||||||
// It's convenient to define a type alias:
|
#[launch]
|
||||||
pub type Session<'a> = rocket_session::Session<'a, u64>;
|
fn rocket() -> _ {
|
||||||
|
rocket::build()
|
||||||
fn main() {
|
|
||||||
rocket::ignite()
|
|
||||||
.attach(Session::fairing())
|
.attach(Session::fairing())
|
||||||
.mount("/", routes![index])
|
.mount("/", routes![index])
|
||||||
.launch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index(session: Session) -> String {
|
fn index(session: Session) -> String {
|
||||||
let count = session.tap(|n| {
|
let count = session.tap(|n| {
|
||||||
// Change the stored value (it is &mut)
|
// Change the stored value (it is &mut)
|
||||||
*n += 1;
|
*n += 1;
|
||||||
|
|
||||||
// Return something to the caller.
|
// Return something to the caller.
|
||||||
// This can be any type, 'tap' is generic.
|
// This can be any type, 'tap' is generic.
|
||||||
*n
|
*n
|
||||||
});
|
});
|
||||||
|
|
||||||
format!("{} visits", count)
|
format!("{} visits", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Extending Session by a Trait
|
## 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
|
Here is an example of using a custom trait and the `json_dotpath` crate to implement
|
||||||
a polymorphic store based on serde serialization.
|
a polymorphic store based on serde serialization.
|
||||||
|
|
||||||
Note that this approach is prone to data races, since every method contains its own `.tap()`.
|
Note that this approach is prone to data races if you're accessing the session object multiple times per request,
|
||||||
It may be safer to simply call the `.dot_*()` methods manually in one shared closure.
|
since every method contains its own `.tap()`. It may be safer to simply call the `.dot_*()` methods manually in one shared closure.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
#![feature(proc_macro_hygiene, decl_macro)]
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
use rocket::response::content::Html;
|
use rocket::response::content::RawHtml;
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
|
|
||||||
type Session<'a> = rocket_session::Session<'a, Vec<String>>;
|
type Session<'a> = rocket_session::Session<'a, Vec<String>>;
|
||||||
|
|
||||||
fn main() {
|
#[launch]
|
||||||
rocket::ignite()
|
fn rocket() -> _ {
|
||||||
|
rocket::build()
|
||||||
.attach(Session::fairing())
|
.attach(Session::fairing())
|
||||||
.mount("/", routes![index, add, remove])
|
.mount("/", routes![index, add, remove])
|
||||||
.launch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index(session: Session) -> Html<String> {
|
fn index(session: Session) -> RawHtml<String> {
|
||||||
let mut page = String::new();
|
let mut page = String::new();
|
||||||
page.push_str(
|
page.push_str(
|
||||||
r#"
|
r#"
|
||||||
@@ -38,7 +37,7 @@ fn index(session: Session) -> Html<String> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
page.push_str("</ul>");
|
page.push_str("</ul>");
|
||||||
Html(page)
|
RawHtml(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/add", data = "<dog>")]
|
#[post("/add", data = "<dog>")]
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -2,11 +2,10 @@
|
|||||||
//!
|
//!
|
||||||
//! The expiry time is set to 10 seconds to illustrate how a session is cleared if inactive.
|
//! 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]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
|
||||||
use rocket::response::content::Html;
|
use rocket::response::content::RawHtml;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@@ -18,8 +17,9 @@ struct SessionData {
|
|||||||
// It's convenient to define a type alias:
|
// It's convenient to define a type alias:
|
||||||
type Session<'a> = rocket_session::Session<'a, SessionData>;
|
type Session<'a> = rocket_session::Session<'a, SessionData>;
|
||||||
|
|
||||||
fn main() {
|
#[launch]
|
||||||
rocket::ignite()
|
fn rocket() -> _ {
|
||||||
|
rocket::build()
|
||||||
.attach(
|
.attach(
|
||||||
Session::fairing()
|
Session::fairing()
|
||||||
// 10 seconds of inactivity until session expires
|
// 10 seconds of inactivity until session expires
|
||||||
@@ -30,11 +30,10 @@ fn main() {
|
|||||||
.with_cookie_len(20),
|
.with_cookie_len(20),
|
||||||
)
|
)
|
||||||
.mount("/", routes![index, about])
|
.mount("/", routes![index, about])
|
||||||
.launch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index(session: Session) -> Html<String> {
|
fn index(session: Session) -> RawHtml<String> {
|
||||||
// Here we build the entire response inside the 'tap' closure.
|
// Here we build the entire response inside the 'tap' closure.
|
||||||
|
|
||||||
// While inside, the session is locked to parallel changes, e.g.
|
// While inside, the session is locked to parallel changes, e.g.
|
||||||
@@ -42,7 +41,7 @@ fn index(session: Session) -> Html<String> {
|
|||||||
session.tap(|sess| {
|
session.tap(|sess| {
|
||||||
sess.visits1 += 1;
|
sess.visits1 += 1;
|
||||||
|
|
||||||
Html(format!(
|
RawHtml(format!(
|
||||||
r##"
|
r##"
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
@@ -55,14 +54,14 @@ fn index(session: Session) -> Html<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/about")]
|
#[get("/about")]
|
||||||
fn about(session: Session) -> Html<String> {
|
fn about(session: Session) -> RawHtml<String> {
|
||||||
// Here we return a value from the tap function and use it below
|
// Here we return a value from the tap function and use it below
|
||||||
let count = session.tap(|sess| {
|
let count = session.tap(|sess| {
|
||||||
sess.visits2 += 1;
|
sess.visits2 += 1;
|
||||||
sess.visits2
|
sess.visits2
|
||||||
});
|
});
|
||||||
|
|
||||||
Html(format!(
|
RawHtml(format!(
|
||||||
r##"
|
r##"
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<h1>About</h1>
|
<h1>About</h1>
|
||||||
|
|||||||
+29
-25
@@ -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::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
@@ -15,6 +5,16 @@ use std::marker::PhantomData;
|
|||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
|
||||||
|
use rand::{rngs::OsRng, Rng, TryRngCore};
|
||||||
|
use rocket::{
|
||||||
|
fairing::{self, Fairing, Info},
|
||||||
|
http::{Cookie, Status},
|
||||||
|
outcome::Outcome,
|
||||||
|
request::FromRequest,
|
||||||
|
Build, Request, Response, Rocket, State,
|
||||||
|
};
|
||||||
|
|
||||||
/// Session store (shared state)
|
/// Session store (shared state)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SessionStore<D>
|
pub struct SessionStore<D>
|
||||||
@@ -114,29 +114,31 @@ where
|
|||||||
D: 'static + Sync + Send + Default,
|
D: 'static + Sync + Send + Default,
|
||||||
{
|
{
|
||||||
/// The shared state reference
|
/// The shared state reference
|
||||||
store: State<'a, SessionStore<D>>,
|
store: &'a State<SessionStore<D>>,
|
||||||
/// Session ID
|
/// Session ID
|
||||||
id: &'a SessionID,
|
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
|
where
|
||||||
D: 'static + Sync + Send + Default,
|
D: 'static + Sync + Send + Default,
|
||||||
{
|
{
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> Outcome<Self, (Status, Self::Error), ()> {
|
async fn from_request(
|
||||||
let store: State<SessionStore<D>> = request.guard().unwrap();
|
request: &'r Request<'_>,
|
||||||
|
) -> Outcome<Self, (Status, Self::Error), Status> {
|
||||||
|
let store = request.guard::<&State<SessionStore<D>>>().await.unwrap();
|
||||||
Outcome::Success(Session {
|
Outcome::Success(Session {
|
||||||
id: request.local_cache(|| {
|
id: request.local_cache(|| {
|
||||||
let store_ug = store.inner.upgradable_read();
|
let store_ug = store.inner.upgradable_read();
|
||||||
|
|
||||||
// Resolve session ID
|
// Resolve session ID
|
||||||
let id = if let Some(cookie) = request.cookies().get(&store.config.cookie_name) {
|
let id = request
|
||||||
Some(SessionID(cookie.value().to_string()))
|
.cookies()
|
||||||
} else {
|
.get(&store.config.cookie_name)
|
||||||
None
|
.map(|cookie| SessionID(cookie.value().to_string()));
|
||||||
};
|
|
||||||
|
|
||||||
let expires = Instant::now().add(store.config.lifespan);
|
let expires = Instant::now().add(store.config.lifespan);
|
||||||
|
|
||||||
@@ -177,7 +179,8 @@ where
|
|||||||
// Find a new unique ID - we are still safely inside the write guard
|
// Find a new unique ID - we are still safely inside the write guard
|
||||||
let new_id = SessionID(loop {
|
let new_id = SessionID(loop {
|
||||||
let token: String = OsRng
|
let token: String = OsRng
|
||||||
.sample_iter(&rand::distributions::Alphanumeric)
|
.unwrap_err()
|
||||||
|
.sample_iter(&rand::distr::Alphanumeric)
|
||||||
.take(store.config.cookie_len)
|
.take(store.config.cookie_len)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect();
|
.collect();
|
||||||
@@ -295,6 +298,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
impl<D> Fairing for SessionFairing<D>
|
impl<D> Fairing for SessionFairing<D>
|
||||||
where
|
where
|
||||||
D: 'static + Sync + Send + Default,
|
D: 'static + Sync + Send + Default,
|
||||||
@@ -302,11 +306,11 @@ where
|
|||||||
fn info(&self) -> Info {
|
fn info(&self) -> Info {
|
||||||
Info {
|
Info {
|
||||||
name: "Session",
|
name: "Session",
|
||||||
kind: fairing::Kind::Attach | fairing::Kind::Response,
|
kind: fairing::Kind::Ignite | fairing::Kind::Response,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
|
async fn on_ignite(&self, rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>> {
|
||||||
// install the store singleton
|
// install the store singleton
|
||||||
Ok(rocket.manage(SessionStore::<D> {
|
Ok(rocket.manage(SessionStore::<D> {
|
||||||
inner: Default::default(),
|
inner: Default::default(),
|
||||||
@@ -314,15 +318,15 @@ 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
|
// send the session cookie, if session started
|
||||||
let session = request.local_cache(|| SessionID("".to_string()));
|
let session = request.local_cache(|| SessionID("".to_string()));
|
||||||
|
|
||||||
if !session.0.is_empty() {
|
if !session.0.is_empty() {
|
||||||
response.adjoin_header(
|
response.adjoin_header(
|
||||||
Cookie::build(self.config.cookie_name.clone(), session.to_string())
|
Cookie::build((self.config.cookie_name.clone(), session.to_string()))
|
||||||
.path("/")
|
.path("/")
|
||||||
.finish(),
|
.build(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user