web_server_abstraction/
session.rs1use crate::types::{Cookie, Request, Response};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7use std::time::{Duration, SystemTime};
8use uuid::Uuid;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Session {
13 pub id: String,
14 pub data: HashMap<String, String>,
15 pub created_at: SystemTime,
16 pub expires_at: SystemTime,
17}
18
19impl Session {
20 pub fn new(duration: Duration) -> Self {
22 let now = SystemTime::now();
23 Self {
24 id: Uuid::new_v4().to_string(),
25 data: HashMap::new(),
26 created_at: now,
27 expires_at: now + duration,
28 }
29 }
30
31 pub fn is_expired(&self) -> bool {
33 SystemTime::now() > self.expires_at
34 }
35
36 pub fn extend(&mut self, duration: Duration) {
38 self.expires_at = SystemTime::now() + duration;
39 }
40
41 pub fn get(&self, key: &str) -> Option<&String> {
43 self.data.get(key)
44 }
45
46 pub fn set(&mut self, key: String, value: String) {
48 self.data.insert(key, value);
49 }
50
51 pub fn remove(&mut self, key: &str) -> Option<String> {
53 self.data.remove(key)
54 }
55
56 pub fn clear(&mut self) {
58 self.data.clear();
59 }
60}
61
62pub trait SessionStore: Send + Sync {
64 fn get(&self, session_id: &str) -> Option<Session>;
66
67 fn set(&self, session: Session);
69
70 fn remove(&self, session_id: &str);
72
73 fn cleanup_expired(&self);
75}
76
77#[derive(Debug)]
79pub struct MemorySessionStore {
80 sessions: Arc<RwLock<HashMap<String, Session>>>,
81}
82
83impl MemorySessionStore {
84 pub fn new() -> Self {
85 Self {
86 sessions: Arc::new(RwLock::new(HashMap::new())),
87 }
88 }
89}
90
91impl Default for MemorySessionStore {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97impl SessionStore for MemorySessionStore {
98 fn get(&self, session_id: &str) -> Option<Session> {
99 let sessions = self.sessions.read().unwrap();
100 sessions.get(session_id).cloned()
101 }
102
103 fn set(&self, session: Session) {
104 let mut sessions = self.sessions.write().unwrap();
105 sessions.insert(session.id.clone(), session);
106 }
107
108 fn remove(&self, session_id: &str) {
109 let mut sessions = self.sessions.write().unwrap();
110 sessions.remove(session_id);
111 }
112
113 fn cleanup_expired(&self) {
114 let mut sessions = self.sessions.write().unwrap();
115 let now = SystemTime::now();
116 sessions.retain(|_, session| session.expires_at > now);
117 }
118}
119
120pub struct SessionManager {
122 store: Box<dyn SessionStore>,
123 cookie_name: String,
124 session_duration: Duration,
125 secure: bool,
126 http_only: bool,
127}
128
129impl SessionManager {
130 pub fn new(store: Box<dyn SessionStore>) -> Self {
132 Self {
133 store,
134 cookie_name: "session_id".to_string(),
135 session_duration: Duration::from_secs(24 * 60 * 60), secure: false,
137 http_only: true,
138 }
139 }
140
141 pub fn memory() -> Self {
143 Self::new(Box::new(MemorySessionStore::new()))
144 }
145
146 pub fn cookie_name(mut self, name: String) -> Self {
148 self.cookie_name = name;
149 self
150 }
151
152 pub fn duration(mut self, duration: Duration) -> Self {
154 self.session_duration = duration;
155 self
156 }
157
158 pub fn secure(mut self, secure: bool) -> Self {
160 self.secure = secure;
161 self
162 }
163
164 pub fn http_only(mut self, http_only: bool) -> Self {
166 self.http_only = http_only;
167 self
168 }
169
170 pub fn get_session(&self, request: &Request) -> Option<Session> {
172 let cookie = request.cookie(&self.cookie_name)?;
173 let session = self.store.get(&cookie.value)?;
174
175 if session.is_expired() {
176 self.store.remove(&session.id);
177 return None;
178 }
179
180 Some(session)
181 }
182
183 pub fn create_session(&self) -> Session {
185 Session::new(self.session_duration)
186 }
187
188 pub fn save_session(&self, mut session: Session, response: Response) -> Response {
190 if session
192 .expires_at
193 .duration_since(SystemTime::now())
194 .unwrap_or(Duration::ZERO)
195 < self.session_duration / 4
196 {
197 session.extend(self.session_duration);
198 }
199
200 let cookie = Cookie::new(&self.cookie_name, &session.id)
201 .path("/")
202 .http_only(self.http_only)
203 .secure(self.secure)
204 .max_age(self.session_duration);
205
206 self.store.set(session);
207 response.cookie(cookie)
208 }
209
210 pub fn destroy_session(&self, request: &Request, mut response: Response) -> Response {
212 if let Some(cookie) = request.cookie(&self.cookie_name) {
213 self.store.remove(&cookie.value);
214
215 let expired_cookie = Cookie::new(&self.cookie_name, "")
217 .path("/")
218 .max_age(Duration::ZERO);
219
220 response = response.cookie(expired_cookie);
221 }
222 response
223 }
224
225 pub fn cleanup_expired(&self) {
227 self.store.cleanup_expired();
228 }
229}
230
231pub trait SessionExt {
233 fn session(&self, manager: &SessionManager) -> Session;
235}
236
237impl SessionExt for Request {
238 fn session(&self, manager: &SessionManager) -> Session {
239 manager
240 .get_session(self)
241 .unwrap_or_else(|| manager.create_session())
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_session_creation() {
251 let session = Session::new(Duration::from_secs(3600));
252 assert!(!session.is_expired());
253 assert_eq!(session.data.len(), 0);
254 }
255
256 #[test]
257 fn test_session_data() {
258 let mut session = Session::new(Duration::from_secs(3600));
259
260 session.set("user_id".to_string(), "123".to_string());
261 assert_eq!(session.get("user_id"), Some(&"123".to_string()));
262
263 session.remove("user_id");
264 assert_eq!(session.get("user_id"), None);
265 }
266
267 #[test]
268 fn test_memory_store() {
269 let store = MemorySessionStore::new();
270 let session = Session::new(Duration::from_secs(3600));
271 let session_id = session.id.clone();
272
273 store.set(session);
274 assert!(store.get(&session_id).is_some());
275
276 store.remove(&session_id);
277 assert!(store.get(&session_id).is_none());
278 }
279
280 #[test]
281 fn test_session_manager() {
282 let manager = SessionManager::memory()
283 .cookie_name("test_session".to_string())
284 .duration(Duration::from_secs(1800));
285
286 let session = manager.create_session();
287 assert!(!session.is_expired());
288 }
289}