async_session/
memory_store.rs

1use crate::{async_trait, log, Result, Session, SessionStore};
2use async_lock::RwLock;
3use std::{collections::HashMap, sync::Arc};
4
5/// # in-memory session store
6/// Because there is no external
7/// persistance, this session store is ephemeral and will be cleared
8/// on server restart.
9///
10/// # ***DO NOT USE THIS IN A PRODUCTION DEPLOYMENT.***
11#[derive(Debug, Clone)]
12pub struct MemoryStore {
13    inner: Arc<RwLock<HashMap<String, Session>>>,
14}
15
16#[async_trait]
17impl SessionStore for MemoryStore {
18    async fn load_session(&self, cookie_value: String) -> Result<Option<Session>> {
19        let id = Session::id_from_cookie_value(&cookie_value)?;
20        log::trace!("loading session by id `{}`", id);
21        Ok(self
22            .inner
23            .read()
24            .await
25            .get(&id)
26            .cloned()
27            .and_then(Session::validate))
28    }
29
30    async fn store_session(&self, session: Session) -> Result<Option<String>> {
31        log::trace!("storing session by id `{}`", session.id());
32        self.inner
33            .write()
34            .await
35            .insert(session.id().to_string(), session.clone());
36
37        session.reset_data_changed();
38        Ok(session.into_cookie_value())
39    }
40
41    async fn destroy_session(&self, session: Session) -> Result {
42        log::trace!("destroying session by id `{}`", session.id());
43        self.inner.write().await.remove(session.id());
44        Ok(())
45    }
46
47    async fn clear_store(&self) -> Result {
48        log::trace!("clearing memory store");
49        self.inner.write().await.clear();
50        Ok(())
51    }
52}
53
54impl MemoryStore {
55    /// Create a new instance of MemoryStore
56    pub fn new() -> Self {
57        Self {
58            inner: Arc::new(RwLock::new(HashMap::new())),
59        }
60    }
61
62    /// Performs session cleanup. This should be run on an
63    /// intermittent basis if this store is run for long enough that
64    /// memory accumulation is a concern
65    pub async fn cleanup(&self) -> Result {
66        log::trace!("cleaning up memory store...");
67        let ids_to_delete: Vec<_> = self
68            .inner
69            .read()
70            .await
71            .values()
72            .filter_map(|session| {
73                if session.is_expired() {
74                    Some(session.id().to_owned())
75                } else {
76                    None
77                }
78            })
79            .collect();
80
81        log::trace!("found {} expired sessions", ids_to_delete.len());
82        for id in ids_to_delete {
83            self.inner.write().await.remove(&id);
84        }
85        Ok(())
86    }
87
88    /// returns the number of elements in the memory store
89    /// # Example
90    /// ```rust
91    /// # use async_session::{MemoryStore, Session, SessionStore};
92    /// # fn main() -> async_session::Result { async_std::task::block_on(async {
93    /// let mut store = MemoryStore::new();
94    /// assert_eq!(store.count().await, 0);
95    /// store.store_session(Session::new()).await?;
96    /// assert_eq!(store.count().await, 1);
97    /// # Ok(()) }) }
98    /// ```
99    pub async fn count(&self) -> usize {
100        let data = self.inner.read().await;
101        data.len()
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use async_std::task;
109    use std::time::Duration;
110    #[async_std::test]
111    async fn creating_a_new_session_with_no_expiry() -> Result {
112        let store = MemoryStore::new();
113        let mut session = Session::new();
114        session.insert("key", "Hello")?;
115        let cloned = session.clone();
116        let cookie_value = store.store_session(session).await?.unwrap();
117        let loaded_session = store.load_session(cookie_value).await?.unwrap();
118        assert_eq!(cloned.id(), loaded_session.id());
119        assert_eq!("Hello", &loaded_session.get::<String>("key").unwrap());
120        assert!(!loaded_session.is_expired());
121        assert!(loaded_session.validate().is_some());
122        Ok(())
123    }
124
125    #[async_std::test]
126    async fn updating_a_session() -> Result {
127        let store = MemoryStore::new();
128        let mut session = Session::new();
129
130        session.insert("key", "value")?;
131        let cookie_value = store.store_session(session).await?.unwrap();
132
133        let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
134        session.insert("key", "other value")?;
135
136        assert_eq!(store.store_session(session).await?, None);
137        let session = store.load_session(cookie_value).await?.unwrap();
138        assert_eq!(&session.get::<String>("key").unwrap(), "other value");
139
140        Ok(())
141    }
142
143    #[async_std::test]
144    async fn updating_a_session_extending_expiry() -> Result {
145        let store = MemoryStore::new();
146        let mut session = Session::new();
147        session.expire_in(Duration::from_secs(1));
148        let original_expires = session.expiry().unwrap().clone();
149        let cookie_value = store.store_session(session).await?.unwrap();
150
151        let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
152
153        assert_eq!(session.expiry().unwrap(), &original_expires);
154        session.expire_in(Duration::from_secs(3));
155        let new_expires = session.expiry().unwrap().clone();
156        assert_eq!(None, store.store_session(session).await?);
157
158        let session = store.load_session(cookie_value.clone()).await?.unwrap();
159        assert_eq!(session.expiry().unwrap(), &new_expires);
160
161        task::sleep(Duration::from_secs(3)).await;
162        assert_eq!(None, store.load_session(cookie_value).await?);
163
164        Ok(())
165    }
166
167    #[async_std::test]
168    async fn creating_a_new_session_with_expiry() -> Result {
169        let store = MemoryStore::new();
170        let mut session = Session::new();
171        session.expire_in(Duration::from_secs(3));
172        session.insert("key", "value")?;
173        let cloned = session.clone();
174
175        let cookie_value = store.store_session(session).await?.unwrap();
176
177        let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap();
178        assert_eq!(cloned.id(), loaded_session.id());
179        assert_eq!("value", &*loaded_session.get::<String>("key").unwrap());
180
181        assert!(!loaded_session.is_expired());
182
183        task::sleep(Duration::from_secs(3)).await;
184        assert_eq!(None, store.load_session(cookie_value).await?);
185
186        Ok(())
187    }
188
189    #[async_std::test]
190    async fn destroying_a_single_session() -> Result {
191        let store = MemoryStore::new();
192        for _ in 0..3i8 {
193            store.store_session(Session::new()).await?;
194        }
195
196        let cookie = store.store_session(Session::new()).await?.unwrap();
197        assert_eq!(4, store.count().await);
198        let session = store.load_session(cookie.clone()).await?.unwrap();
199        store.destroy_session(session.clone()).await?;
200        assert_eq!(None, store.load_session(cookie).await?);
201        assert_eq!(3, store.count().await);
202
203        // attempting to destroy the session again is not an error
204        assert!(store.destroy_session(session).await.is_ok());
205        Ok(())
206    }
207
208    #[async_std::test]
209    async fn clearing_the_whole_store() -> Result {
210        let store = MemoryStore::new();
211        for _ in 0..3i8 {
212            store.store_session(Session::new()).await?;
213        }
214
215        assert_eq!(3, store.count().await);
216        store.clear_store().await.unwrap();
217        assert_eq!(0, store.count().await);
218
219        Ok(())
220    }
221}