From 54bbc98343e4f0be31f60b2496dc8f81243c09bd Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sat, 25 Nov 2017 09:52:32 -0800 Subject: [PATCH] cookie session prototype --- src/middlewares/mod.rs | 3 + src/middlewares/session.rs | 228 +++++++++++++++++++++++++++++++++++++ src/pipeline.rs | 11 +- src/task.rs | 11 +- 4 files changed, 241 insertions(+), 12 deletions(-) create mode 100644 src/middlewares/session.rs diff --git a/src/middlewares/mod.rs b/src/middlewares/mod.rs index 3cb37e3b9..619e35702 100644 --- a/src/middlewares/mod.rs +++ b/src/middlewares/mod.rs @@ -6,7 +6,10 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; mod logger; +mod session; pub use self::logger::Logger; +pub use self::session::{RequestSession, Session, SessionImpl, + SessionBackend, SessionStorage, CookieSessionBackend}; /// Middleware start result pub enum Started { diff --git a/src/middlewares/session.rs b/src/middlewares/session.rs new file mode 100644 index 000000000..89153878b --- /dev/null +++ b/src/middlewares/session.rs @@ -0,0 +1,228 @@ +#![allow(dead_code, unused_imports, unused_variables)] + +use std::any::Any; +use std::rc::Rc; +use std::sync::Arc; +use serde_json; +use serde::{Serialize, Deserialize}; +use http::header::{self, HeaderValue}; +use cookie::{CookieJar, Cookie, Key}; +use futures::Future; +use futures::future::{FutureResult, ok as FutOk, err as FutErr}; + +use error::{Result, Error, ErrorResponse}; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use middlewares::{Middleware, Started, Response}; + +/// The helper trait to obtain your session data from a request. +pub trait RequestSession { + fn session(&mut self) -> Session; +} + +impl RequestSession for HttpRequest { + + fn session(&mut self) -> Session { + if let Some(s_impl) = self.extensions().get_mut::>() { + if let Some(s) = Arc::get_mut(s_impl) { + return Session(s.0.as_mut()) + } + } + //Session(&mut DUMMY) + unreachable!() + } +} + +/// The high-level interface you use to modify session data. +/// +/// Session object could be obtained with +/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session) +/// method. `RequestSession` trait is implemented for `HttpRequest`. +pub struct Session<'a>(&'a mut SessionImpl); + +impl<'a> Session<'a> { + + /// Get a `value` from the session. + pub fn get>(&'a self, key: &str) -> Result> { + if let Some(s) = self.0.get(key) { + Ok(Some(serde_json::from_str(s)?)) + } else { + Ok(None) + } + } + + /// Set a `value` from the session. + pub fn set(&'a mut self, key: &str, value: T) -> Result<()> { + self.0.set(key, serde_json::to_string(&value)?); + Ok(()) + } + + /// Remove value from the session. + pub fn remove(&'a mut self, key: &str) { + self.0.remove(key) + } + + /// Clear the session. + pub fn clear(&'a mut self) { + self.0.clear() + } +} + +struct SessionImplBox(Box); + +#[doc(hidden)] +unsafe impl Send for SessionImplBox {} +#[doc(hidden)] +unsafe impl Sync for SessionImplBox {} + +/// Session storage middleware +pub struct SessionStorage(T); + +impl SessionStorage { + /// Create session storage + pub fn new(backend: T) -> SessionStorage { + SessionStorage(backend) + } +} + +impl Middleware for SessionStorage { + + fn start(&self, mut req: HttpRequest) -> Started { + let fut = self.0.from_request(&mut req) + .then(|res| { + match res { + Ok(sess) => { + req.extensions().insert(Arc::new(SessionImplBox(Box::new(sess)))); + let resp: Option = None; + FutOk((req, resp)) + }, + Err(err) => FutErr(err) + } + }); + Started::Future(Box::new(fut)) + } + + fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response { + if let Some(s_box) = req.extensions().remove::>() { + s_box.0.write(resp) + } else { + Response::Response(resp) + } + } +} + +/// A simple key-value storage interface that is internally used by `Session`. +#[doc(hidden)] +pub trait SessionImpl: 'static { + + fn get(&self, key: &str) -> Option<&str>; + + fn set(&mut self, key: &str, value: String); + + fn remove(&mut self, key: &str); + + fn clear(&mut self); + + /// Write session to storage backend. + fn write(&self, resp: HttpResponse) -> Response; +} + +/// Session's storage backend trait definition. +#[doc(hidden)] +pub trait SessionBackend: Sized + 'static { + type Session: SessionImpl; + type ReadFuture: Future; + + /// Parse the session from request and load data from a storage backend. + fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture; +} + +/// Dummy session impl, does not do anything +struct DummySessionImpl; + +static DUMMY: DummySessionImpl = DummySessionImpl; + +impl SessionImpl for DummySessionImpl { + + fn get(&self, key: &str) -> Option<&str> { + None + } + fn set(&mut self, key: &str, value: String) {} + fn remove(&mut self, key: &str) {} + fn clear(&mut self) {} + fn write(&self, resp: HttpResponse) -> Response { + Response::Response(resp) + } +} + +/// Session that uses signed cookies as session storage +pub struct CookieSession { + jar: CookieJar, + key: Rc, +} + +impl SessionImpl for CookieSession { + + fn get(&self, key: &str) -> Option<&str> { + unimplemented!() + } + + fn set(&mut self, key: &str, value: String) { + unimplemented!() + } + + fn remove(&mut self, key: &str) { + unimplemented!() + } + + fn clear(&mut self) { + let cookies: Vec<_> = self.jar.iter().map(|c| c.clone()).collect(); + for cookie in cookies { + self.jar.remove(cookie); + } + } + + fn write(&self, mut resp: HttpResponse) -> Response { + for cookie in self.jar.delta() { + match HeaderValue::from_str(&cookie.to_string()) { + Err(err) => return Response::Response(err.error_response()), + Ok(val) => resp.headers.append(header::SET_COOKIE, val), + }; + } + Response::Response(resp) + } +} + +/// Use signed cookies as session storage. +/// +/// You need to pass a random value to the constructor of `CookieSessionBackend`. +/// This is private key for cookie session, When this value is changed, all session data is lost. +/// +/// Note that whatever you write into your session is visible by the user (but not modifiable). +/// +/// Constructor panics if key length is less than 32 bytes. +pub struct CookieSessionBackend { + key: Rc, +} + +impl CookieSessionBackend { + + /// Construct new `CookieSessionBackend` instance. + /// + /// Panics if key length is less than 32 bytes. + pub fn new(key: &[u8]) -> Self { + CookieSessionBackend { + key: Rc::new(Key::from_master(key)), + } + } +} + +impl SessionBackend for CookieSessionBackend { + + type Session = CookieSession; + type ReadFuture = FutureResult; + + fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture { + unimplemented!() + } +} diff --git a/src/pipeline.rs b/src/pipeline.rs index a243b5238..9345459b6 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -55,14 +55,17 @@ impl Pipeline { st.disconnected(), PipelineState::Handle(ref mut st) => st.task.disconnected(), + PipelineState::Task(ref mut st) => + st.0.disconnected(), + PipelineState::Error(ref mut st) => + st.0.disconnected(), _ =>(), } } pub(crate) fn poll_io(&mut self, io: &mut T) -> Poll { loop { - let state = mem::replace(&mut self.0, PipelineState::None); - match state { + match mem::replace(&mut self.0, PipelineState::None) { PipelineState::Task(mut st) => { let req:&mut HttpRequest = unsafe{mem::transmute(&mut st.1)}; let res = st.0.poll_io(io, req); @@ -110,8 +113,7 @@ impl Pipeline { pub(crate) fn poll(&mut self) -> Poll<(), Error> { loop { - let state = mem::replace(&mut self.0, PipelineState::None); - match state { + match mem::replace(&mut self.0, PipelineState::None) { PipelineState::Handle(mut st) => { let res = st.poll(); match res { @@ -140,7 +142,6 @@ impl Pipeline { return res } _ => { - self.0 = state; return Ok(Async::Ready(())) } } diff --git a/src/task.rs b/src/task.rs index 045582fda..6e12460be 100644 --- a/src/task.rs +++ b/src/task.rs @@ -182,9 +182,8 @@ impl Task { // poll stream if self.state == TaskRunningState::Running { match self.poll()? { - Async::Ready(_) => { - self.state = TaskRunningState::Done; - }, + Async::Ready(_) => + self.state = TaskRunningState::Done, Async::NotReady => (), } } @@ -260,10 +259,8 @@ impl Task { // flush io match io.poll_complete() { - Ok(Async::Ready(())) => self.state.resume(), - Ok(Async::NotReady) => { - return Ok(Async::NotReady) - } + Ok(Async::Ready(_)) => self.state.resume(), + Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => { debug!("Error sending data: {}", err); return Err(err.into())