diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 1a78fad2e..880ce3bf9 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -55,6 +55,7 @@ struct CookieSessionInner { name: String, path: String, domain: Option, + lazy: bool, secure: bool, http_only: bool, max_age: Option, @@ -70,6 +71,7 @@ impl CookieSessionInner { name: "actix-session".to_owned(), path: "/".to_owned(), domain: None, + lazy: false, secure: true, http_only: true, max_age: None, @@ -84,6 +86,11 @@ impl CookieSessionInner { state: impl Iterator, ) -> Result<(), Error> { let state: HashMap = state.collect(); + + if self.lazy && state.is_empty() { + return Ok(()); + } + let value = serde_json::to_string(&state).map_err(CookieSessionError::Serialize)?; if value.len() > 4064 { @@ -247,6 +254,15 @@ impl CookieSession { self } + /// When true, prevents adding session cookies to responses until + /// the session contains data. Default is `false`. + /// + /// Useful when trying to comply with laws that require consent for setting cookies. + pub fn lazy(mut self, value: bool) -> CookieSession { + Rc::get_mut(&mut self.0).unwrap().lazy = value; + self + } + /// Sets the `secure` field in the session cookie being built. /// /// If the `secure` field is set, a cookie will only be transmitted when the @@ -426,6 +442,37 @@ mod tests { .any(|c| c.name() == "actix-session")); } + #[actix_rt::test] + async fn lazy_cookie() { + let mut app = test::init_service( + App::new() + .wrap(CookieSession::signed(&[0; 32]).secure(false).lazy(true)) + .service(web::resource("/count").to(|ses: Session| async move { + let _ = ses.set("counter", 100); + "counting" + })) + .service(web::resource("/").to(|_ses: Session| async move { + "test" + })), + ) + .await; + + let request = test::TestRequest::get().to_request(); + let response = app.call(request).await.unwrap(); + assert!(response + .response() + .cookies() + .count() == 0); + + let request = test::TestRequest::with_uri("/count").to_request(); + let response = app.call(request).await.unwrap(); + + assert!(response + .response() + .cookies() + .any(|c| c.name() == "actix-session")); + } + #[actix_rt::test] async fn cookie_session_extractor() { let mut app = test::init_service(