implement cookie based polymorphic session
This commit is contained in:
+17
-2
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user