1#![doc(html_favicon_url = "https://salvo.rs/favicon-32x32.png")]
62#![doc(html_logo_url = "https://salvo.rs/images/logo.svg")]
63#![cfg_attr(docsrs, feature(doc_cfg))]
64
65use std::fmt::{self, Formatter};
66use std::time::Duration;
67
68use cookie::{Cookie, Key, KeyError, SameSite};
69use salvo_core::http::uri::Scheme;
70use salvo_core::{Depot, Error, FlowCtrl, Handler, Request, Response, async_trait};
71use saysion::base64::Engine as _;
72use saysion::base64::engine::general_purpose;
73use saysion::hmac::{Hmac, Mac};
74use saysion::sha2::Sha256;
75pub use saysion::{CookieStore, MemoryStore, Session, SessionStore};
76
77pub const SESSION_KEY: &str = "::salvo::session";
79const BASE64_DIGEST_LEN: usize = 44;
80
81pub trait SessionDepotExt {
83 fn set_session(&mut self, session: Session) -> &mut Self;
85 fn take_session(&mut self) -> Option<Session>;
87 fn session(&self) -> Option<&Session>;
89 fn session_mut(&mut self) -> Option<&mut Session>;
91}
92
93impl SessionDepotExt for Depot {
94 #[inline]
95 fn set_session(&mut self, session: Session) -> &mut Self {
96 self.insert(SESSION_KEY, session);
97 self
98 }
99 #[inline]
100 fn take_session(&mut self) -> Option<Session> {
101 self.remove(SESSION_KEY).ok()
102 }
103 #[inline]
104 fn session(&self) -> Option<&Session> {
105 self.get(SESSION_KEY).ok()
106 }
107 #[inline]
108 fn session_mut(&mut self) -> Option<&mut Session> {
109 self.get_mut(SESSION_KEY).ok()
110 }
111}
112
113pub struct HandlerBuilder<S> {
115 store: S,
116 cookie_path: String,
117 cookie_name: String,
118 cookie_domain: Option<String>,
119 session_ttl: Option<Duration>,
120 save_unchanged: bool,
121 same_site_policy: SameSite,
122 key: Key,
123 fallback_keys: Vec<Key>,
124}
125impl<S> fmt::Debug for HandlerBuilder<S>
126where
127 S: SessionStore + fmt::Debug,
128{
129 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
130 f.debug_struct("HandlerBuilder")
131 .field("store", &self.store)
132 .field("cookie_path", &self.cookie_path)
133 .field("cookie_name", &self.cookie_name)
134 .field("cookie_domain", &self.cookie_domain)
135 .field("session_ttl", &self.session_ttl)
136 .field("same_site_policy", &self.same_site_policy)
137 .field("key", &"..")
138 .field("fallback_keys", &"..")
139 .field("save_unchanged", &self.save_unchanged)
140 .finish()
141 }
142}
143
144impl<S> HandlerBuilder<S>
145where
146 S: SessionStore,
147{
148 #[inline]
162 #[must_use]
163 pub fn new(store: S, secret: &[u8]) -> Self {
164 Self::try_new(store, secret).unwrap()
165 }
166
167 #[inline]
180 pub fn try_new(store: S, secret: &[u8]) -> Result<Self, KeyError> {
181 let key = Key::try_from(secret)?;
182 Ok(Self {
183 store,
184 save_unchanged: true,
185 cookie_path: "/".into(),
186 cookie_name: "salvo.session.id".into(),
187 cookie_domain: None,
188 same_site_policy: SameSite::Lax,
189 session_ttl: Some(Duration::from_secs(24 * 60 * 60)),
190 key,
191 fallback_keys: vec![],
192 })
193 }
194
195 #[inline]
199 #[must_use]
200 pub fn cookie_path(mut self, cookie_path: impl Into<String>) -> Self {
201 self.cookie_path = cookie_path.into();
202 self
203 }
204
205 #[inline]
211 #[must_use]
212 pub fn session_ttl(mut self, session_ttl: Option<Duration>) -> Self {
213 self.session_ttl = session_ttl;
214 self
215 }
216
217 #[inline]
223 #[must_use]
224 pub fn cookie_name(mut self, cookie_name: impl Into<String>) -> Self {
225 self.cookie_name = cookie_name.into();
226 self
227 }
228
229 #[inline]
239 #[must_use]
240 pub fn save_unchanged(mut self, value: bool) -> Self {
241 self.save_unchanged = value;
242 self
243 }
244
245 #[inline]
250 #[must_use]
251 pub fn same_site_policy(mut self, policy: SameSite) -> Self {
252 self.same_site_policy = policy;
253 self
254 }
255
256 #[inline]
258 #[must_use]
259 pub fn cookie_domain(mut self, cookie_domain: impl AsRef<str>) -> Self {
260 self.cookie_domain = Some(cookie_domain.as_ref().to_owned());
261 self
262 }
263 #[inline]
265 #[must_use]
266 pub fn fallback_keys(mut self, keys: Vec<impl Into<Key>>) -> Self {
267 self.fallback_keys = keys.into_iter().map(|s| s.into()).collect();
268 self
269 }
270
271 #[inline]
273 #[must_use]
274 pub fn add_fallback_key(mut self, key: impl Into<Key>) -> Self {
275 self.fallback_keys.push(key.into());
276 self
277 }
278
279 pub fn build(self) -> Result<SessionHandler<S>, Error> {
281 let Self {
282 store,
283 save_unchanged,
284 cookie_path,
285 cookie_name,
286 cookie_domain,
287 session_ttl,
288 same_site_policy,
289 key,
290 fallback_keys,
291 } = self;
292 let hmac = Hmac::<Sha256>::new_from_slice(key.signing())
293 .map_err(|_| Error::Other("invalid key length".into()))?;
294 let fallback_hmacs = fallback_keys
295 .iter()
296 .map(|key| Hmac::<Sha256>::new_from_slice(key.signing()))
297 .collect::<Result<Vec<_>, _>>()
298 .map_err(|_| Error::Other("invalid key length".into()))?;
299 Ok(SessionHandler {
300 store,
301 save_unchanged,
302 cookie_path,
303 cookie_name,
304 cookie_domain,
305 session_ttl,
306 same_site_policy,
307 hmac,
308 fallback_hmacs,
309 })
310 }
311}
312
313pub struct SessionHandler<S> {
315 store: S,
316 cookie_path: String,
317 cookie_name: String,
318 cookie_domain: Option<String>,
319 session_ttl: Option<Duration>,
320 save_unchanged: bool,
321 same_site_policy: SameSite,
322 hmac: Hmac<Sha256>,
323 fallback_hmacs: Vec<Hmac<Sha256>>,
324}
325impl<S> fmt::Debug for SessionHandler<S>
326where
327 S: SessionStore + fmt::Debug,
328{
329 #[inline]
330 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
331 f.debug_struct("SessionHandler")
332 .field("store", &self.store)
333 .field("cookie_path", &self.cookie_path)
334 .field("cookie_name", &self.cookie_name)
335 .field("cookie_domain", &self.cookie_domain)
336 .field("session_ttl", &self.session_ttl)
337 .field("same_site_policy", &self.same_site_policy)
338 .field("key", &"..")
339 .field("fallback_keys", &"..")
340 .field("save_unchanged", &self.save_unchanged)
341 .finish()
342 }
343}
344#[async_trait]
345impl<S> Handler for SessionHandler<S>
346where
347 S: SessionStore + Send + Sync + 'static,
348{
349 async fn handle(
350 &self,
351 req: &mut Request,
352 depot: &mut Depot,
353 res: &mut Response,
354 ctrl: &mut FlowCtrl,
355 ) {
356 let cookie = req.cookies().get(&self.cookie_name);
357 let cookie_value = cookie.and_then(|cookie| self.verify_signature(cookie.value()).ok());
358
359 let mut session = self.load_or_create(cookie_value).await;
360
361 if let Some(ttl) = self.session_ttl {
362 session.expire_in(ttl);
363 }
364
365 depot.set_session(session);
366
367 ctrl.call_next(req, depot, res).await;
368 if ctrl.is_ceased() {
369 return;
370 }
371
372 let session = depot.take_session().expect("session should exist in depot");
373 if session.is_destroyed() {
374 if let Err(e) = self.store.destroy_session(session).await {
375 tracing::error!(error = ?e, "unable to destroy session");
376 }
377 res.remove_cookie(&self.cookie_name);
378 } else if self.save_unchanged || session.data_changed() {
379 match self.store.store_session(session).await {
380 Ok(cookie_value) => {
381 if let Some(cookie_value) = cookie_value {
382 let secure_cookie = req.uri().scheme() == Some(&Scheme::HTTPS);
383 let cookie = self.build_cookie(secure_cookie, cookie_value);
384 res.add_cookie(cookie);
385 }
386 }
387 Err(e) => {
388 tracing::error!(error = ?e, "store session error");
389 }
390 }
391 }
392 }
393}
394
395impl<S> SessionHandler<S>
396where
397 S: SessionStore + Send + Sync + 'static,
398{
399 pub fn builder(store: S, secret: &[u8]) -> HandlerBuilder<S> {
401 HandlerBuilder::new(store, secret)
402 }
403 #[inline]
404 async fn load_or_create(&self, cookie_value: Option<String>) -> Session {
405 let session = match cookie_value {
406 Some(cookie_value) => self.store.load_session(cookie_value).await.ok().flatten(),
407 None => None,
408 };
409
410 session
411 .and_then(|session| session.validate())
412 .unwrap_or_default()
413 }
414 fn verify_signature(&self, cookie_value: &str) -> Result<String, Error> {
420 if cookie_value.len() < BASE64_DIGEST_LEN {
421 return Err(Error::Other(
422 "length of value is <= BASE64_DIGEST_LEN".into(),
423 ));
424 }
425
426 let (digest_str, value) = cookie_value.split_at(BASE64_DIGEST_LEN);
428 let digest = general_purpose::STANDARD
429 .decode(digest_str)
430 .map_err(|_| Error::Other("bad base64 digest".into()))?;
431
432 let mut hmac = self.hmac.clone();
434 hmac.update(value.as_bytes());
435 if hmac.verify_slice(&digest).is_ok() {
436 return Ok(value.to_owned());
437 }
438 for hmac in &self.fallback_hmacs {
439 let mut hmac = hmac.clone();
440 hmac.update(value.as_bytes());
441 if hmac.verify_slice(&digest).is_ok() {
442 return Ok(value.to_owned());
443 }
444 }
445 Err(Error::Other("value did not verify".into()))
446 }
447 fn build_cookie(&self, secure: bool, cookie_value: String) -> Cookie<'static> {
448 let mut cookie = Cookie::build((self.cookie_name.clone(), cookie_value))
449 .http_only(true)
450 .same_site(self.same_site_policy)
451 .secure(secure)
452 .path(self.cookie_path.clone())
453 .build();
454
455 if let Some(ttl) = self.session_ttl {
456 cookie.set_expires(Some((std::time::SystemTime::now() + ttl).into()));
457 }
458
459 if let Some(cookie_domain) = self.cookie_domain.clone() {
460 cookie.set_domain(cookie_domain)
461 }
462
463 self.sign_cookie(&mut cookie);
464
465 cookie
466 }
467 fn sign_cookie(&self, cookie: &mut Cookie<'_>) {
471 let mut mac = self.hmac.clone();
473 mac.update(cookie.value().as_bytes());
474
475 let mut new_value = general_purpose::STANDARD.encode(mac.finalize().into_bytes());
477 new_value.push_str(cookie.value());
478 cookie.set_value(new_value);
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use salvo_core::http::Method;
485 use salvo_core::http::header::*;
486 use salvo_core::prelude::*;
487 use salvo_core::test::{ResponseExt, TestClient};
488
489 use super::*;
490
491 #[test]
492 fn test_session_data() {
493 let builder = SessionHandler::builder(
494 saysion::CookieStore,
495 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
496 )
497 .cookie_domain("test.domain")
498 .cookie_name("test_cookie")
499 .cookie_path("/abc")
500 .same_site_policy(SameSite::Strict)
501 .session_ttl(Some(Duration::from_secs(30)));
502 assert!(format!("{builder:?}").contains("test_cookie"));
503
504 let handler = builder.build().unwrap();
505 assert!(format!("{handler:?}").contains("test_cookie"));
506 assert_eq!(handler.cookie_domain, Some("test.domain".into()));
507 assert_eq!(handler.cookie_name, "test_cookie");
508 assert_eq!(handler.cookie_path, "/abc");
509 assert_eq!(handler.same_site_policy, SameSite::Strict);
510 assert_eq!(handler.session_ttl, Some(Duration::from_secs(30)));
511 }
512
513 #[tokio::test]
514 async fn test_session_login() {
515 #[handler]
516 pub async fn login(req: &mut Request, depot: &mut Depot, res: &mut Response) {
517 if req.method() == Method::POST {
518 let mut session = Session::new();
519 session
520 .insert("username", req.form::<String>("username").await.unwrap())
521 .unwrap();
522 depot.set_session(session);
523 res.render(Redirect::other("/"));
524 } else {
525 res.render(Text::Html("login page"));
526 }
527 }
528
529 #[handler]
530 pub async fn logout(depot: &mut Depot, res: &mut Response) {
531 if let Some(session) = depot.session_mut() {
532 session.remove("username");
533 }
534 res.render(Redirect::other("/"));
535 }
536
537 #[handler]
538 pub async fn home(depot: &mut Depot, res: &mut Response) {
539 let mut content = r#"home"#.into();
540 if let Some(session) = depot.session_mut() {
541 if let Some(username) = session.get::<String>("username") {
542 content = username;
543 }
544 }
545 res.render(Text::Html(content));
546 }
547
548 let session_handler = SessionHandler::builder(
549 MemoryStore::new(),
550 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
551 )
552 .build()
553 .unwrap();
554 let router = Router::new()
555 .hoop(session_handler)
556 .get(home)
557 .push(Router::with_path("login").get(login).post(login))
558 .push(Router::with_path("logout").get(logout));
559 let service = Service::new(router);
560
561 let response = TestClient::post("http://127.0.0.1:8698/login")
562 .raw_form("username=salvo")
563 .send(&service)
564 .await;
565 assert_eq!(response.status_code, Some(StatusCode::SEE_OTHER));
566 let cookie = response.headers().get(SET_COOKIE).unwrap();
567
568 let mut response = TestClient::get("http://127.0.0.1:8698/")
569 .add_header(COOKIE, cookie, true)
570 .send(&service)
571 .await;
572 assert_eq!(response.take_string().await.unwrap(), "salvo");
573
574 let response = TestClient::get("http://127.0.0.1:8698/logout")
575 .send(&service)
576 .await;
577 assert_eq!(response.status_code, Some(StatusCode::SEE_OTHER));
578
579 let mut response = TestClient::get("http://127.0.0.1:8698/")
580 .send(&service)
581 .await;
582 assert_eq!(response.take_string().await.unwrap(), "home");
583 }
584
585 #[test]
587 fn test_handler_builder_new() {
588 let builder = HandlerBuilder::new(
589 MemoryStore::new(),
590 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
591 );
592 assert_eq!(builder.cookie_path, "/");
593 assert_eq!(builder.cookie_name, "salvo.session.id");
594 assert!(builder.cookie_domain.is_none());
595 assert!(builder.save_unchanged);
596 assert_eq!(builder.same_site_policy, SameSite::Lax);
597 assert_eq!(builder.session_ttl, Some(Duration::from_secs(24 * 60 * 60)));
598 }
599
600 #[test]
601 fn test_handler_builder_cookie_path() {
602 let builder = HandlerBuilder::new(
603 MemoryStore::new(),
604 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
605 )
606 .cookie_path("/custom");
607 assert_eq!(builder.cookie_path, "/custom");
608 }
609
610 #[test]
611 fn test_handler_builder_session_ttl() {
612 let builder = HandlerBuilder::new(
613 MemoryStore::new(),
614 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
615 )
616 .session_ttl(Some(Duration::from_secs(3600)));
617 assert_eq!(builder.session_ttl, Some(Duration::from_secs(3600)));
618 }
619
620 #[test]
621 fn test_handler_builder_session_ttl_none() {
622 let builder = HandlerBuilder::new(
623 MemoryStore::new(),
624 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
625 )
626 .session_ttl(None);
627 assert!(builder.session_ttl.is_none());
628 }
629
630 #[test]
631 fn test_handler_builder_cookie_name() {
632 let builder = HandlerBuilder::new(
633 MemoryStore::new(),
634 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
635 )
636 .cookie_name("my_session");
637 assert_eq!(builder.cookie_name, "my_session");
638 }
639
640 #[test]
641 fn test_handler_builder_save_unchanged() {
642 let builder = HandlerBuilder::new(
643 MemoryStore::new(),
644 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
645 )
646 .save_unchanged(false);
647 assert!(!builder.save_unchanged);
648 }
649
650 #[test]
651 fn test_handler_builder_same_site_policy() {
652 let builder = HandlerBuilder::new(
653 MemoryStore::new(),
654 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
655 )
656 .same_site_policy(SameSite::None);
657 assert_eq!(builder.same_site_policy, SameSite::None);
658 }
659
660 #[test]
661 fn test_handler_builder_cookie_domain() {
662 let builder = HandlerBuilder::new(
663 MemoryStore::new(),
664 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
665 )
666 .cookie_domain("example.com");
667 assert_eq!(builder.cookie_domain, Some("example.com".to_string()));
668 }
669
670 #[test]
671 fn test_handler_builder_fallback_keys() {
672 let builder = HandlerBuilder::new(
673 MemoryStore::new(),
674 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
675 )
676 .fallback_keys(vec![Key::from(
677 b"fallbackfallbackfallbackfallbackfallbackfallbackfallbackfallback" as &[u8],
678 )]);
679 assert_eq!(builder.fallback_keys.len(), 1);
680 }
681
682 #[test]
683 fn test_handler_builder_add_fallback_key() {
684 let builder = HandlerBuilder::new(
685 MemoryStore::new(),
686 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
687 )
688 .add_fallback_key(Key::from(
689 b"fallbackfallbackfallbackfallbackfallbackfallbackfallbackfallback" as &[u8],
690 ))
691 .add_fallback_key(Key::from(
692 b"anotherkeyanotherkeyanotherkeyanotherkeyanotherkeyanotherkeyanot" as &[u8],
693 ));
694 assert_eq!(builder.fallback_keys.len(), 2);
695 }
696
697 #[test]
698 fn test_handler_builder_build() {
699 let handler = HandlerBuilder::new(
700 MemoryStore::new(),
701 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
702 )
703 .build()
704 .unwrap();
705 assert_eq!(handler.cookie_path, "/");
706 assert_eq!(handler.cookie_name, "salvo.session.id");
707 }
708
709 #[test]
710 fn test_handler_builder_debug() {
711 let builder = HandlerBuilder::new(
712 MemoryStore::new(),
713 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
714 );
715 let debug_str = format!("{:?}", builder);
716 assert!(debug_str.contains("HandlerBuilder"));
717 assert!(debug_str.contains("cookie_path"));
718 assert!(debug_str.contains("cookie_name"));
719 }
720
721 #[test]
722 fn test_handler_builder_chain() {
723 let handler = HandlerBuilder::new(
724 MemoryStore::new(),
725 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
726 )
727 .cookie_path("/app")
728 .cookie_name("app_session")
729 .cookie_domain("app.example.com")
730 .session_ttl(Some(Duration::from_secs(7200)))
731 .save_unchanged(false)
732 .same_site_policy(SameSite::Strict)
733 .build()
734 .unwrap();
735
736 assert_eq!(handler.cookie_path, "/app");
737 assert_eq!(handler.cookie_name, "app_session");
738 assert_eq!(handler.cookie_domain, Some("app.example.com".to_string()));
739 assert_eq!(handler.session_ttl, Some(Duration::from_secs(7200)));
740 assert!(!handler.save_unchanged);
741 assert_eq!(handler.same_site_policy, SameSite::Strict);
742 }
743
744 #[test]
746 fn test_session_handler_builder() {
747 let handler = SessionHandler::builder(
748 MemoryStore::new(),
749 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
750 )
751 .build()
752 .unwrap();
753 assert_eq!(handler.cookie_name, "salvo.session.id");
754 }
755
756 #[test]
757 fn test_session_handler_debug() {
758 let handler = SessionHandler::builder(
759 MemoryStore::new(),
760 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
761 )
762 .build()
763 .unwrap();
764 let debug_str = format!("{:?}", handler);
765 assert!(debug_str.contains("SessionHandler"));
766 assert!(debug_str.contains("cookie_path"));
767 }
768
769 #[test]
771 fn test_depot_set_session() {
772 let mut depot = Depot::new();
773 let session = Session::new();
774 depot.set_session(session);
775 assert!(depot.session().is_some());
776 }
777
778 #[test]
779 fn test_depot_take_session() {
780 let mut depot = Depot::new();
781 let session = Session::new();
782 depot.set_session(session);
783 let taken = depot.take_session();
784 assert!(taken.is_some());
785 assert!(depot.session().is_none());
786 }
787
788 #[test]
789 fn test_depot_session() {
790 let mut depot = Depot::new();
791 assert!(depot.session().is_none());
792
793 depot.set_session(Session::new());
794 assert!(depot.session().is_some());
795 }
796
797 #[test]
798 fn test_depot_session_mut() {
799 let mut depot = Depot::new();
800 depot.set_session(Session::new());
801
802 if let Some(session) = depot.session_mut() {
803 session.insert("key", "value").unwrap();
804 }
805
806 if let Some(session) = depot.session() {
807 assert_eq!(session.get::<String>("key"), Some("value".to_string()));
808 }
809 }
810
811 #[tokio::test]
813 async fn test_session_destroy() {
814 #[handler]
815 pub async fn destroy_session(depot: &mut Depot, res: &mut Response) {
816 if let Some(session) = depot.session_mut() {
817 session.destroy();
818 }
819 res.render("destroyed");
820 }
821
822 let session_handler = SessionHandler::builder(
823 MemoryStore::new(),
824 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
825 )
826 .build()
827 .unwrap();
828
829 let router = Router::new()
830 .hoop(session_handler)
831 .push(Router::with_path("destroy").get(destroy_session));
832 let service = Service::new(router);
833
834 let response = TestClient::get("http://127.0.0.1:8698/destroy")
835 .send(&service)
836 .await;
837 assert_eq!(response.status_code, Some(StatusCode::OK));
838 }
839
840 #[tokio::test]
842 async fn test_session_save_unchanged_false() {
843 #[handler]
844 pub async fn no_change(depot: &mut Depot, res: &mut Response) {
845 let _ = depot.session();
847 res.render("no change");
848 }
849
850 let session_handler = SessionHandler::builder(
851 MemoryStore::new(),
852 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
853 )
854 .save_unchanged(false)
855 .build()
856 .unwrap();
857
858 let router = Router::new()
859 .hoop(session_handler)
860 .push(Router::with_path("nochange").get(no_change));
861 let service = Service::new(router);
862
863 let response = TestClient::get("http://127.0.0.1:8698/nochange")
864 .send(&service)
865 .await;
866 assert_eq!(response.status_code, Some(StatusCode::OK));
867 }
870
871 #[tokio::test]
873 async fn test_session_data_persistence() {
874 #[handler]
875 pub async fn set_data(depot: &mut Depot, res: &mut Response) {
876 if let Some(session) = depot.session_mut() {
877 session.insert("counter", 1).unwrap();
878 }
879 res.render("set");
880 }
881
882 #[handler]
883 pub async fn get_data(depot: &mut Depot, res: &mut Response) {
884 let counter = if let Some(session) = depot.session() {
885 session.get::<i32>("counter").unwrap_or(0)
886 } else {
887 0
888 };
889 res.render(format!("{}", counter));
890 }
891
892 let session_handler = SessionHandler::builder(
893 MemoryStore::new(),
894 b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
895 )
896 .build()
897 .unwrap();
898
899 let router = Router::new()
900 .hoop(session_handler)
901 .push(Router::with_path("set").get(set_data))
902 .push(Router::with_path("get").get(get_data));
903 let service = Service::new(router);
904
905 let response = TestClient::get("http://127.0.0.1:8698/set")
907 .send(&service)
908 .await;
909 let cookie = response.headers().get(SET_COOKIE).unwrap();
910
911 let mut response = TestClient::get("http://127.0.0.1:8698/get")
913 .add_header(COOKIE, cookie, true)
914 .send(&service)
915 .await;
916 assert_eq!(response.take_string().await.unwrap(), "1");
917 }
918
919 #[test]
921 fn test_session_key_constant() {
922 assert_eq!(SESSION_KEY, "::salvo::session");
923 }
924
925 #[test]
927 fn test_base64_digest_len() {
928 assert_eq!(BASE64_DIGEST_LEN, 44);
929 }
930}