clia_ntex_session/
lib.rs

1//! User sessions.
2//!
3//! Actix provides a general solution for session management. Session
4//! middlewares could provide different implementations which could
5//! be accessed via general session api.
6//!
7//! By default, only cookie session backend is implemented. Other
8//! backend implementations can be added.
9//!
10//! In general, you insert a *session* middleware and initialize it
11//! , such as a `CookieSessionBackend`. To access session data,
12//! [*Session*](struct.Session.html) extractor must be used. Session
13//! extractor allows us to get or set session data.
14//!
15//! ```rust,no_run
16//! use ntex::web::{self, App, HttpResponse, Error};
17//! use ntex_session::{Session, CookieSession};
18//!
19//! fn index(session: Session) -> Result<&'static str, Error> {
20//!     // access session data
21//!     if let Some(count) = session.get::<i32>("counter")? {
22//!         println!("SESSION value: {}", count);
23//!         session.set("counter", count+1)?;
24//!     } else {
25//!         session.set("counter", 1)?;
26//!     }
27//!
28//!     Ok("Welcome!")
29//! }
30//!
31//! #[ntex::main]
32//! async fn main() -> std::io::Result<()> {
33//!     web::server(
34//!         || App::new().wrap(
35//!               CookieSession::signed(&[0; 32]) // <- create cookie based session middleware
36//!                     .secure(false)
37//!              )
38//!             .service(web::resource("/").to(|| async { HttpResponse::Ok() })))
39//!         .bind("127.0.0.1:59880")?
40//!         .run()
41//!         .await
42//! }
43//! ```
44use std::cell::RefCell;
45use std::collections::HashMap;
46use std::convert::Infallible;
47use std::rc::Rc;
48
49use futures::future::{ok, Ready};
50use ntex::http::{Payload, RequestHead};
51use ntex::util::Extensions;
52use ntex::web::{Error, FromRequest, HttpRequest, WebRequest, WebResponse};
53use serde::de::DeserializeOwned;
54use serde::Serialize;
55
56#[cfg(feature = "cookie-session")]
57mod cookie;
58#[cfg(feature = "cookie-session")]
59pub use crate::cookie::CookieSession;
60
61/// The high-level interface you use to modify session data.
62///
63/// Session object could be obtained with
64/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session)
65/// method. `RequestSession` trait is implemented for `HttpRequest`.
66///
67/// ```rust
68/// use ntex_session::Session;
69/// use ntex::web::*;
70///
71/// fn index(session: Session) -> Result<&'static str, Error> {
72///     // access session data
73///     if let Some(count) = session.get::<i32>("counter")? {
74///         session.set("counter", count + 1)?;
75///     } else {
76///         session.set("counter", 1)?;
77///     }
78///
79///     Ok("Welcome!")
80/// }
81/// # fn main() {}
82/// ```
83pub struct Session(Rc<RefCell<SessionInner>>);
84
85/// Helper trait that allows to get session
86pub trait UserSession {
87    fn get_session(&self) -> Session;
88}
89
90impl UserSession for HttpRequest {
91    fn get_session(&self) -> Session {
92        Session::get_session(&mut self.extensions_mut())
93    }
94}
95
96impl<Err> UserSession for WebRequest<Err> {
97    fn get_session(&self) -> Session {
98        Session::get_session(&mut self.extensions_mut())
99    }
100}
101
102impl UserSession for RequestHead {
103    fn get_session(&self) -> Session {
104        Session::get_session(&mut self.extensions_mut())
105    }
106}
107
108#[derive(PartialEq, Clone, Debug)]
109pub enum SessionStatus {
110    Changed,
111    Purged,
112    Renewed,
113    Unchanged,
114}
115
116/// #[default] macro can be used but will depend on specific rust version
117impl Default for SessionStatus {
118    fn default() -> SessionStatus {
119        SessionStatus::Unchanged
120    }
121}
122
123#[derive(Default)]
124struct SessionInner {
125    state: HashMap<String, String>,
126    pub status: SessionStatus,
127}
128
129impl Session {
130    /// Get a `value` from the session.
131    pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, Error> {
132        if let Some(s) = self.0.borrow().state.get(key) {
133            Ok(Some(serde_json::from_str(s)?))
134        } else {
135            Ok(None)
136        }
137    }
138
139    /// Set a `value` from the session.
140    pub fn set<T: Serialize>(&self, key: &str, value: T) -> Result<(), Error> {
141        let mut inner = self.0.borrow_mut();
142        if inner.status != SessionStatus::Purged {
143            inner.status = SessionStatus::Changed;
144            inner.state.insert(key.to_owned(), serde_json::to_string(&value)?);
145        }
146        Ok(())
147    }
148
149    /// Remove value from the session.
150    pub fn remove(&self, key: &str) {
151        let mut inner = self.0.borrow_mut();
152        if inner.status != SessionStatus::Purged {
153            inner.status = SessionStatus::Changed;
154            inner.state.remove(key);
155        }
156    }
157
158    /// Clear the session.
159    pub fn clear(&self) {
160        let mut inner = self.0.borrow_mut();
161        if inner.status != SessionStatus::Purged {
162            inner.status = SessionStatus::Changed;
163            inner.state.clear()
164        }
165    }
166
167    /// Removes session, both client and server side.
168    pub fn purge(&self) {
169        let mut inner = self.0.borrow_mut();
170        inner.status = SessionStatus::Purged;
171        inner.state.clear();
172    }
173
174    /// Renews the session key, assigning existing session state to new key.
175    pub fn renew(&self) {
176        let mut inner = self.0.borrow_mut();
177        if inner.status != SessionStatus::Purged {
178            inner.status = SessionStatus::Renewed;
179        }
180    }
181
182    pub fn set_session<Err>(
183        data: impl Iterator<Item = (String, String)>,
184        req: &WebRequest<Err>,
185    ) {
186        let session = Session::get_session(&mut req.extensions_mut());
187        let mut inner = session.0.borrow_mut();
188        inner.state.extend(data);
189    }
190
191    pub fn get_changes(
192        res: &mut WebResponse,
193    ) -> (SessionStatus, Option<impl Iterator<Item = (String, String)>>) {
194        if let Some(s_impl) = res.request().extensions().get::<Rc<RefCell<SessionInner>>>() {
195            let state = std::mem::take(&mut s_impl.borrow_mut().state);
196            (s_impl.borrow().status.clone(), Some(state.into_iter()))
197        } else {
198            (SessionStatus::Unchanged, None)
199        }
200    }
201
202    fn get_session(extensions: &mut Extensions) -> Session {
203        if let Some(s_impl) = extensions.get::<Rc<RefCell<SessionInner>>>() {
204            return Session(Rc::clone(s_impl));
205        }
206        let inner = Rc::new(RefCell::new(SessionInner::default()));
207        extensions.insert(inner.clone());
208        Session(inner)
209    }
210}
211
212/// Extractor implementation for Session type.
213///
214/// ```rust
215/// use ntex_session::Session;
216///
217/// fn index(session: Session) -> Result<&'static str, ntex::web::Error> {
218///     // access session data
219///     if let Some(count) = session.get::<i32>("counter")? {
220///         session.set("counter", count + 1)?;
221///     } else {
222///         session.set("counter", 1)?;
223///     }
224///
225///     Ok("Welcome!")
226/// }
227/// # fn main() {}
228/// ```
229impl<Err> FromRequest<Err> for Session {
230    type Error = Infallible;
231    type Future = Ready<Result<Session, Infallible>>;
232
233    #[inline]
234    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
235        ok(Session::get_session(&mut req.extensions_mut()))
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use ntex::web::{test, HttpResponse};
242
243    use super::*;
244
245    #[test]
246    fn session() {
247        let req = test::TestRequest::default().to_srv_request();
248
249        Session::set_session(
250            vec![("key".to_string(), "\"value\"".to_string())].into_iter(),
251            &req,
252        );
253        let session = Session::get_session(&mut req.extensions_mut());
254        let res = session.get::<String>("key").unwrap();
255        assert_eq!(res, Some("value".to_string()));
256
257        session.set("key2", "value2".to_string()).unwrap();
258        session.remove("key");
259
260        let mut res = req.into_response(HttpResponse::Ok().finish());
261        let (_status, state) = Session::get_changes(&mut res);
262        let changes: Vec<_> = state.unwrap().collect();
263        assert_eq!(changes, [("key2".to_string(), "\"value2\"".to_string())]);
264    }
265
266    #[test]
267    fn get_session() {
268        let req = test::TestRequest::default().to_srv_request();
269
270        Session::set_session(
271            vec![("key".to_string(), "\"value\"".to_string())].into_iter(),
272            &req,
273        );
274
275        let session = req.get_session();
276        let res = session.get::<String>("key").unwrap();
277        assert_eq!(res, Some("value".to_string()));
278    }
279
280    #[test]
281    fn get_session_from_request_head() {
282        let mut req = test::TestRequest::default().to_srv_request();
283
284        Session::set_session(
285            vec![("key".to_string(), "\"value\"".to_string())].into_iter(),
286            &req,
287        );
288
289        let session = req.head_mut().get_session();
290        let res = session.get::<String>("key").unwrap();
291        assert_eq!(res, Some("value".to_string()));
292    }
293
294    #[test]
295    fn purge_session() {
296        let req = test::TestRequest::default().to_srv_request();
297        let session = Session::get_session(&mut req.extensions_mut());
298        assert_eq!(session.0.borrow().status, SessionStatus::Unchanged);
299        session.purge();
300        assert_eq!(session.0.borrow().status, SessionStatus::Purged);
301    }
302
303    #[test]
304    fn renew_session() {
305        let req = test::TestRequest::default().to_srv_request();
306        let session = Session::get_session(&mut req.extensions_mut());
307        assert_eq!(session.0.borrow().status, SessionStatus::Unchanged);
308        session.renew();
309        assert_eq!(session.0.borrow().status, SessionStatus::Renewed);
310    }
311}