async_redis_session_v2/
lib.rs1#![forbid(unsafe_code, future_incompatible)]
19#![deny(
20 missing_debug_implementations,
21 nonstandard_style,
22 missing_docs,
23 unreachable_pub,
24 missing_copy_implementations,
25 unused_qualifications
26)]
27
28use async_session::{async_trait, serde_json, Result, Session, SessionStore};
29use redis::{aio::Connection, AsyncCommands, Client, IntoConnectionInfo, RedisResult};
30
31#[derive(Clone, Debug)]
33pub struct RedisSessionStore {
34 client: Client,
35 prefix: Option<String>,
36}
37
38impl RedisSessionStore {
39 pub fn from_client(client: Client) -> Self {
46 Self {
47 client,
48 prefix: None,
49 }
50 }
51
52 pub fn new(connection_info: impl IntoConnectionInfo) -> RedisResult<Self> {
59 Ok(Self::from_client(Client::open(connection_info)?))
60 }
61
62 pub fn with_prefix(mut self, prefix: impl AsRef<str>) -> Self {
76 self.prefix = Some(prefix.as_ref().to_owned());
77 self
78 }
79
80 async fn ids(&self) -> Result<Vec<String>> {
81 Ok(self.connection().await?.keys(self.prefix_key("*")).await?)
82 }
83
84 pub async fn count(&self) -> Result<usize> {
86 if self.prefix.is_none() {
87 let mut connection = self.connection().await?;
88 Ok(redis::cmd("DBSIZE").query_async(&mut connection).await?)
89 } else {
90 Ok(self.ids().await?.len())
91 }
92 }
93
94 #[cfg(test)]
95 async fn ttl_for_session(&self, session: &Session) -> Result<usize> {
96 Ok(self
97 .connection()
98 .await?
99 .ttl(self.prefix_key(session.id()))
100 .await?)
101 }
102
103 fn prefix_key(&self, key: impl AsRef<str>) -> String {
104 if let Some(ref prefix) = self.prefix {
105 format!("{}{}", prefix, key.as_ref())
106 } else {
107 key.as_ref().into()
108 }
109 }
110
111 async fn connection(&self) -> RedisResult<Connection> {
112 self.client.get_async_std_connection().await
113 }
114}
115
116#[async_trait]
117impl SessionStore for RedisSessionStore {
118 async fn load_session(&self, cookie_value: String) -> Result<Option<Session>> {
119 let id = Session::id_from_cookie_value(&cookie_value)?;
120 let mut connection = self.connection().await?;
121 let record: Option<String> = connection.get(self.prefix_key(id)).await?;
122 match record {
123 Some(value) => Ok(serde_json::from_str(&value)?),
124 None => Ok(None),
125 }
126 }
127
128 async fn store_session(&self, session: Session) -> Result<Option<String>> {
129 let id = self.prefix_key(session.id());
130 let string = serde_json::to_string(&session)?;
131
132 let mut connection = self.connection().await?;
133
134 match session.expires_in() {
135 None => connection.set(id, string).await?,
136
137 Some(expiry) => connection.set_ex(id, string, expiry.as_secs()).await?,
138 };
139
140 Ok(session.into_cookie_value())
141 }
142
143 async fn destroy_session(&self, session: Session) -> Result {
144 let mut connection = self.connection().await?;
145 let key = self.prefix_key(session.id().to_string());
146 connection.del(key).await?;
147 Ok(())
148 }
149
150 async fn clear_store(&self) -> Result {
151 let mut connection = self.connection().await?;
152
153 if self.prefix.is_none() {
154 let _: () = redis::cmd("FLUSHDB").query_async(&mut connection).await?;
155 } else {
156 let ids = self.ids().await?;
157 if !ids.is_empty() {
158 connection.del(ids).await?;
159 }
160 }
161 Ok(())
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use async_std::task;
169 use std::time::Duration;
170
171 async fn test_store() -> RedisSessionStore {
172 let store = RedisSessionStore::new("redis://127.0.0.1").unwrap();
173 store.clear_store().await.unwrap();
174 store
175 }
176
177 #[async_std::test]
178 async fn creating_a_new_session_with_no_expiry() -> Result {
179 let store = test_store().await;
180 let mut session = Session::new();
181 session.insert("key", "value")?;
182 let cloned = session.clone();
183 let cookie_value = store.store_session(session).await?.unwrap();
184
185 let loaded_session = store.load_session(cookie_value).await?.unwrap();
186 assert_eq!(cloned.id(), loaded_session.id());
187 assert_eq!("value", &loaded_session.get::<String>("key").unwrap());
188
189 assert!(!loaded_session.is_expired());
190 Ok(())
191 }
192
193 #[async_std::test]
194 async fn updating_a_session() -> Result {
195 let store = test_store().await;
196 let mut session = Session::new();
197
198 session.insert("key", "value")?;
199 let cookie_value = store.store_session(session).await?.unwrap();
200
201 let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
202 session.insert("key", "other value")?;
203 assert_eq!(None, store.store_session(session).await?);
204
205 let session = store.load_session(cookie_value.clone()).await?.unwrap();
206 assert_eq!(&session.get::<String>("key").unwrap(), "other value");
207
208 assert_eq!(1, store.count().await.unwrap());
209 Ok(())
210 }
211
212 #[async_std::test]
213 async fn updating_a_session_extending_expiry() -> Result {
214 let store = test_store().await;
215 let mut session = Session::new();
216 session.expire_in(Duration::from_secs(5));
217 let original_expires = session.expiry().unwrap().clone();
218 let cookie_value = store.store_session(session).await?.unwrap();
219
220 let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
221 let ttl = store.ttl_for_session(&session).await?;
222 assert!(ttl > 3 && ttl < 5);
223
224 assert_eq!(session.expiry().unwrap(), &original_expires);
225 session.expire_in(Duration::from_secs(10));
226 let new_expires = session.expiry().unwrap().clone();
227 store.store_session(session).await?;
228
229 let session = store.load_session(cookie_value.clone()).await?.unwrap();
230 let ttl = store.ttl_for_session(&session).await?;
231 assert!(ttl > 8 && ttl < 10);
232 assert_eq!(session.expiry().unwrap(), &new_expires);
233
234 assert_eq!(1, store.count().await.unwrap());
235
236 task::sleep(Duration::from_secs(10)).await;
237 assert_eq!(0, store.count().await.unwrap());
238
239 Ok(())
240 }
241
242 #[async_std::test]
243 async fn creating_a_new_session_with_expiry() -> Result {
244 let store = test_store().await;
245 let mut session = Session::new();
246 session.expire_in(Duration::from_secs(3));
247 session.insert("key", "value")?;
248 let cloned = session.clone();
249
250 let cookie_value = store.store_session(session).await?.unwrap();
251
252 assert!(store.ttl_for_session(&cloned).await? > 1);
253
254 let loaded_session = store.load_session(cookie_value.clone()).await?.unwrap();
255 assert_eq!(cloned.id(), loaded_session.id());
256 assert_eq!("value", &loaded_session.get::<String>("key").unwrap());
257
258 assert!(!loaded_session.is_expired());
259
260 task::sleep(Duration::from_secs(2)).await;
261 assert_eq!(None, store.load_session(cookie_value).await?);
262
263 Ok(())
264 }
265
266 #[async_std::test]
267 async fn destroying_a_single_session() -> Result {
268 let store = test_store().await;
269 for _ in 0..3i8 {
270 store.store_session(Session::new()).await?;
271 }
272
273 let cookie = store.store_session(Session::new()).await?.unwrap();
274 assert_eq!(4, store.count().await?);
275 let session = store.load_session(cookie.clone()).await?.unwrap();
276 store.destroy_session(session.clone()).await.unwrap();
277 assert_eq!(None, store.load_session(cookie).await?);
278 assert_eq!(3, store.count().await?);
279
280 assert!(store.destroy_session(session).await.is_ok());
282 Ok(())
283 }
284
285 #[async_std::test]
286 async fn clearing_the_whole_store() -> Result {
287 let store = test_store().await;
288 for _ in 0..3i8 {
289 store.store_session(Session::new()).await?;
290 }
291
292 assert_eq!(3, store.count().await?);
293 store.clear_store().await.unwrap();
294 assert_eq!(0, store.count().await?);
295
296 Ok(())
297 }
298
299 #[async_std::test]
300 async fn prefixes() -> Result {
301 test_store().await; let store = RedisSessionStore::new("redis://127.0.0.1")?.with_prefix("sessions/");
304 store.clear_store().await?;
305
306 for _ in 0..3i8 {
307 store.store_session(Session::new()).await?;
308 }
309
310 let mut session = Session::new();
311
312 session.insert("key", "value")?;
313 let cookie_value = store.store_session(session).await?.unwrap();
314
315 let mut session = store.load_session(cookie_value.clone()).await?.unwrap();
316 session.insert("key", "other value")?;
317 assert_eq!(None, store.store_session(session).await?);
318
319 let session = store.load_session(cookie_value.clone()).await?.unwrap();
320 assert_eq!(&session.get::<String>("key").unwrap(), "other value");
321
322 assert_eq!(4, store.count().await.unwrap());
323
324 let other_store =
325 RedisSessionStore::new("redis://127.0.0.1")?.with_prefix("other-namespace/");
326
327 assert_eq!(0, other_store.count().await.unwrap());
328 for _ in 0..3i8 {
329 other_store.store_session(Session::new()).await?;
330 }
331
332 other_store.clear_store().await?;
333
334 assert_eq!(0, other_store.count().await?);
335 assert_eq!(4, store.count().await?);
336
337 Ok(())
338 }
339}