implement cookie based polymorphic session

This commit is contained in:
2019-12-31 01:37:13 +01:00
parent 52676cf227
commit d24af144fa
5 changed files with 262 additions and 2 deletions
+17 -2
View File
@@ -13,6 +13,7 @@ use rocket_contrib::serve::StaticFiles;
use rocket_contrib::templates::Template;
mod store;
mod session;
use crate::store::form::{render_card_fields, render_empty_fields, RenderedCard, RenderedField, MapFromForm, collect_card_form};
use crate::store::Store;
@@ -20,8 +21,9 @@ use parking_lot::RwLock;
use rocket::request::Form;
use rocket::response::Redirect;
use rocket::State;
use rocket::{State, Request};
use std::env;
use crate::session::{SessionID, SessionStore, Session};
#[derive(Serialize, Debug)]
pub struct ListContext<'a> {
@@ -29,6 +31,7 @@ pub struct ListContext<'a> {
pub cards: Vec<RenderedCard<'a>>,
pub page: usize,
pub pages: usize,
pub count : usize,
}
const PER_PAGE: usize = 20; // TODO configurable
@@ -48,9 +51,19 @@ fn find_page_with_card(store: &Store, card_id: usize) -> Option<usize> {
}
#[get("/?<page>")]
fn route_index(store: State<RwLock<Store>>, page: Option<usize>) -> Template {
fn route_index(
store: State<RwLock<Store>>,
session : Session,
page: Option<usize>
) -> Template {
let rg = store.read();
let mut count : usize = session.get_or_default("foo.bar.count");
count += 1;
session.set("foo.bar.count", count);
println!("{:?}", session);
let mut page = page.unwrap_or_default();
let n_pages = (rg.data.cards.len() as f64 / PER_PAGE as f64).ceil() as usize;
@@ -62,6 +75,7 @@ fn route_index(store: State<RwLock<Store>>, page: Option<usize>) -> Template {
fields: render_empty_fields(&rg),
pages: n_pages,
page,
count,
cards: rg
.data
.cards
@@ -174,6 +188,7 @@ fn main() {
rocket::ignite()
.attach(Template::fairing())
.attach(Session::fairing())
.manage(RwLock::new(store))
.mount("/", StaticFiles::from(cwd.join("templates/static/")))
.mount(
+168
View File
@@ -0,0 +1,168 @@
use rocket::request::FromRequest;
use rocket::{Outcome, Request, State, http::{Status, Cookies, Cookie}, Response, Data, Rocket};
use std::ops::{Deref, DerefMut};
use std::collections::HashMap;
use parking_lot::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
use serde_json::{Value, Map};
use rocket::fairing::{self, Fairing, Info};
use rand::Rng;
use std::borrow::Cow;
use serde::{Deserialize, Serialize};
use serde::de::DeserializeOwned;
use json_dotpath::DotPaths;
use rocket::response::ResponseBuilder;
const SESSION_ID : &'static str = "SESSID";
type SessionsMap = HashMap<String, SessionInstance>;
#[derive(Debug)]
struct SessionInstance {
data: serde_json::Map<String, Value>,
// TODO expiration
}
#[derive(Default, Debug)]
pub struct SessionStore {
inner: RwLock<SessionsMap>,
}
#[derive(PartialEq, Hash, Clone, Debug)]
pub struct SessionID(String);
impl<'a, 'r> FromRequest<'a, 'r> for &'a SessionID {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<Self, (Status, Self::Error), ()> {
Outcome::Success(request.local_cache(|| {
println!("get id");
if let Some(cookie) = request.cookies().get(SESSION_ID) {
println!("from cookie");
SessionID(cookie.value().to_string()) // FIXME avoid cloning
} else {
println!("new id");
SessionID(rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(16)
.collect())
}
}))
}
}
#[derive(Debug)]
pub struct Session<'a> {
store: State<'a, SessionStore>,
id : &'a SessionID,
}
impl<'a, 'r> FromRequest<'a, 'r> for Session<'a> {
type Error = ();
fn from_request(request: &'a Request<'r>) -> Outcome<Self, (Status, Self::Error), ()> {
Outcome::Success(Session {
id: request.local_cache(|| {
if let Some(cookie) = request.cookies().get(SESSION_ID) {
SessionID(cookie.value().to_string())
} else {
SessionID(rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(16)
.collect())
}
}),
store: request.guard().unwrap()
})
}
}
impl<'a> Session<'a> {
pub fn fairing() -> impl Fairing {
SessionFairing
}
pub fn get<T : DeserializeOwned>(&self, path : &str) -> Option<T> {
let rg = self.store.inner.read();
if let Some(ses) = rg.get(&self.id.0) {
ses.data.dot_get(path)
} else {
None
}
}
pub fn get_or<T : DeserializeOwned>(&self, path : &str, def : T) -> T {
self.get(path).unwrap_or(def)
}
pub fn get_or_else<T : DeserializeOwned, F : FnOnce() -> T>(&self, path : &str, def : F) -> T {
self.get(path).unwrap_or_else(def)
}
pub fn get_or_default<T : DeserializeOwned + Default>(&self, path : &str) -> T {
self.get(path).unwrap_or_default()
}
pub fn take<T : DeserializeOwned>(&self, path : &str) -> Option<T> {
let mut wg = self.store.inner.write();
if let Some(ses) = wg.get_mut(&self.id.0) {
ses.data.dot_take(path)
} else {
None
}
}
pub fn replace<O: DeserializeOwned, N: Serialize>(&self, path : &str, new : N) -> Option<O> {
let mut wg = self.store.inner.write();
if let Some(ses) = wg.get_mut(&self.id.0) {
ses.data.dot_replace(path, new)
} else {
None
}
}
pub fn set<T : Serialize>(&self, path : &str, value : T) {
let mut wg = self.store.inner.write();
if let Some(ses) = wg.get_mut(&self.id.0) {
ses.data.dot_set(path, value);
} else {
let mut map = Map::new();
map.dot_set(path, value);
wg.insert(self.id.0.clone(), SessionInstance {
data : map,
});
}
}
pub fn remove(&self, path : &str) {
let mut wg = self.store.inner.write();
if let Some(ses) = wg.get_mut(&self.id.0) {
ses.data.dot_remove(path);
}
}
}
/// Fairing struct
struct SessionFairing;
impl Fairing for SessionFairing {
fn info(&self) -> Info {
Info {
name: "Session Fairing",
kind: fairing::Kind::Attach | fairing::Kind::Response
}
}
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
Ok(rocket.manage(SessionStore::default()))
}
fn on_response<'r>(&self, request: &'r Request, response: &mut Response) {
let session = request.local_cache(|| {
SessionID("".to_string())
});
if !session.0.is_empty() {
response.adjoin_header(Cookie::build(SESSION_ID, session.0.clone()).finish());
}
}
}