Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add sled session backend #269

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ guide/build/
*.pyc
*.pid
*.sock
*.db
*~
.DS_Store
6 changes: 5 additions & 1 deletion actix-session/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ path = "src/lib.rs"
[features]
default = []
cookie-session = []
sled-session = ["sled"]
redis-actor-session = ["actix-redis", "actix", "futures-core", "rand"]
redis-rs-session = ["redis", "rand"]
redis-rs-tls-session = ["redis-rs-session", "redis/tokio-native-tls-comp"]
Expand All @@ -40,6 +41,9 @@ serde = { version = "1" }
serde_json = { version = "1" }
tracing = { version = "0.1.30", default-features = false, features = ["log"] }

# sled-session
sled = { version = "0.34", optional = true }

# redis-actor-session
actix = { version = "0.13", default-features = false, optional = true }
actix-redis = { version = "0.12", optional = true }
Expand All @@ -57,7 +61,7 @@ log = "0.4"

[[example]]
name = "basic"
required-features = ["redis-actor-session"]
required-features = ["sled-session"]

[[example]]
name = "authentication"
Expand Down
8 changes: 5 additions & 3 deletions actix-session/examples/basic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use actix_session::{storage::RedisActorSessionStore, Session, SessionMiddleware};
use actix_session::{storage::SledSessionStore, Session, SessionMiddleware};
use actix_web::{cookie::Key, middleware, web, App, Error, HttpRequest, HttpServer, Responder};

/// simple handler
Expand All @@ -21,7 +21,9 @@ async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

// The signing key would usually be read from a configuration file/environment variables.
let signing_key = Key::generate();
let signing_key = Key::from(&[0; 64]);

let sled_session_store = SledSessionStore::new("./session.db").unwrap();

log::info!("starting HTTP server at http://localhost:8080");

Expand All @@ -31,7 +33,7 @@ async fn main() -> std::io::Result<()> {
.wrap(middleware::Logger::default())
// cookie session middleware
.wrap(SessionMiddleware::new(
RedisActorSessionStore::new("127.0.0.1:6379"),
sled_session_store.clone(),
signing_key.clone(),
))
// register simple route, handle all methods
Expand Down
20 changes: 20 additions & 0 deletions actix-session/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,26 @@ pub mod test_helpers {

use crate::{config::CookieContentSecurity, storage::SessionStore};

/// Prints name of function it is called in.
///
/// Taken from: https://docs.rs/stdext/0.3.1/src/stdext/macros.rs.html
#[allow(unused_macros)]
macro_rules! function_name {
() => {{
// Okay, this is ugly, I get it. However, this is the best we can get on a stable rust.
fn f() {}
fn type_name_of<T>(_: T) -> &'static str {
std::any::type_name::<T>()
}
let name = type_name_of(f);
// `3` is the length of the `::f`.
&name[..name.len() - 3]
}};
}

#[allow(unused_imports)]
pub(crate) use function_name;

/// Generate a random cookie signing/encryption key.
pub fn key() -> Key {
Key::generate()
Expand Down
17 changes: 8 additions & 9 deletions actix-session/src/storage/cookie.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
use std::convert::TryInto;
use std::convert::TryInto as _;

use actix_web::cookie::time::Duration;
use anyhow::Error;

use super::SessionKey;
use crate::storage::{
interface::{LoadError, SaveError, SessionState, UpdateError},
SessionStore,
};
use super::{interface::SessionState, LoadError, SaveError, SessionKey, SessionStore, UpdateError};

/// Use the session key, stored in the session cookie, as storage backend for the session state.
///
Expand Down Expand Up @@ -67,7 +62,7 @@ impl SessionStore for CookieSessionStore {
_ttl: &Duration,
) -> Result<SessionKey, SaveError> {
let session_key = serde_json::to_string(&session_state)
.map_err(anyhow::Error::new)
.map_err(Into::into)
.map_err(SaveError::Serialization)?;

Ok(session_key
Expand All @@ -90,7 +85,11 @@ impl SessionStore for CookieSessionStore {
})
}

async fn update_ttl(&self, _session_key: &SessionKey, _ttl: &Duration) -> Result<(), Error> {
async fn update_ttl(
&self,
_session_key: &SessionKey,
_ttl: &Duration,
) -> Result<(), anyhow::Error> {
Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion actix-session/src/storage/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub enum UpdateError {
Serialization(anyhow::Error),

/// Something went wrong when updating the session state.
#[display(fmt = "Something went wrong when updating the session state.")]
#[display(fmt = "Something went wrong when updating the session state")]
Other(anyhow::Error),
}

Expand Down
11 changes: 8 additions & 3 deletions actix-session/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pub use self::session_key::SessionKey;
#[cfg(feature = "cookie-session")]
mod cookie;

#[cfg(feature = "sled-session")]
mod sled;

#[cfg(feature = "redis-actor-session")]
mod redis_actor;

Expand All @@ -19,8 +22,10 @@ mod redis_rs;
mod utils;

#[cfg(feature = "cookie-session")]
pub use cookie::CookieSessionStore;
pub use self::cookie::CookieSessionStore;
#[cfg(feature = "redis-actor-session")]
pub use redis_actor::{RedisActorSessionStore, RedisActorSessionStoreBuilder};
pub use self::redis_actor::{RedisActorSessionStore, RedisActorSessionStoreBuilder};
#[cfg(feature = "redis-rs-session")]
pub use redis_rs::{RedisSessionStore, RedisSessionStoreBuilder};
pub use self::redis_rs::{RedisSessionStore, RedisSessionStoreBuilder};
#[cfg(feature = "sled-session")]
pub use self::sled::SledSessionStore;
16 changes: 9 additions & 7 deletions actix-session/src/storage/redis_actor.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
use actix::Addr;
use actix_redis::{resp_array, Command, RedisActor, RespValue};
use actix_web::cookie::time::Duration;
use anyhow::Error;

use super::SessionKey;
use crate::storage::{
interface::{LoadError, SaveError, SessionState, UpdateError},
utils::generate_session_key,
SessionStore,
use super::{
interface::SessionState, utils::generate_session_key, LoadError, SaveError, SessionKey,
SessionStore, UpdateError,
};

/// Use Redis as session storage backend.
Expand Down Expand Up @@ -156,6 +153,7 @@ impl SessionStore for RedisActorSessionStore {
let body = serde_json::to_string(&session_state)
.map_err(Into::into)
.map_err(SaveError::Serialization)?;

let session_key = generate_session_key();
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

Expand Down Expand Up @@ -239,7 +237,11 @@ impl SessionStore for RedisActorSessionStore {
}
}

async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> Result<(), Error> {
async fn update_ttl(
&self,
session_key: &SessionKey,
ttl: &Duration,
) -> Result<(), anyhow::Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

let cmd = Command(resp_array![
Expand Down
34 changes: 21 additions & 13 deletions actix-session/src/storage/redis_rs.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::{convert::TryInto, sync::Arc};
use std::{convert::TryInto as _, sync::Arc};

use actix_web::cookie::time::Duration;
use anyhow::{Context, Error};
use anyhow::Context as _;
use redis::{aio::ConnectionManager, AsyncCommands, Cmd, FromRedisValue, RedisResult, Value};

use super::SessionKey;
use crate::storage::{
interface::{LoadError, SaveError, SessionState, UpdateError},
utils::generate_session_key,
SessionStore,
use super::{
interface::SessionState, utils::generate_session_key, LoadError, SaveError, SessionKey,
SessionStore, UpdateError,
};

/// Use Redis as session storage backend.
Expand Down Expand Up @@ -139,8 +137,8 @@ impl SessionStore for RedisSessionStore {
async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

let value: Option<String> = self
.execute_command(redis::cmd("GET").arg(&[&cache_key]))
let value = self
.execute_command::<Option<String>>(redis::cmd("GET").arg(&[&cache_key]))
.await
.map_err(Into::into)
.map_err(LoadError::Other)?;
Expand All @@ -161,6 +159,7 @@ impl SessionStore for RedisSessionStore {
let body = serde_json::to_string(&session_state)
.map_err(Into::into)
.map_err(SaveError::Serialization)?;

let session_key = generate_session_key();
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

Expand All @@ -169,7 +168,7 @@ impl SessionStore for RedisSessionStore {
&body,
"NX", // NX: only set the key if it does not already exist
"EX", // EX: set expiry
&format!("{}", ttl.whole_seconds()),
ttl.whole_seconds().to_string().as_str(),
]))
.await
.map_err(Into::into)
Expand All @@ -194,9 +193,9 @@ impl SessionStore for RedisSessionStore {
.execute_command(redis::cmd("SET").arg(&[
&cache_key,
&body,
"XX", // XX: Only set the key if it already exist.
"XX", // XX: only set the key if it already exists
"EX", // EX: set expiry
&format!("{}", ttl.whole_seconds()),
ttl.whole_seconds().to_string().as_str(),
]))
.await
.map_err(Into::into)
Expand All @@ -223,7 +222,11 @@ impl SessionStore for RedisSessionStore {
}
}

async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> Result<(), Error> {
async fn update_ttl(
&self,
session_key: &SessionKey,
ttl: &Duration,
) -> Result<(), anyhow::Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

self.client
Expand All @@ -235,6 +238,7 @@ impl SessionStore for RedisSessionStore {
)?,
)
.await?;

Ok(())
}

Expand Down Expand Up @@ -321,12 +325,14 @@ mod tests {
async fn loading_an_invalid_session_state_returns_deserialization_error() {
let store = redis_store().await;
let session_key = generate_session_key();

store
.client
.clone()
.set::<_, _, ()>(session_key.as_ref(), "random-thing-which-is-not-json")
.await
.unwrap();

assert!(matches!(
store.load(&session_key).await.unwrap_err(),
LoadError::Deserialization(_),
Expand All @@ -338,10 +344,12 @@ mod tests {
let store = redis_store().await;
let session_key = generate_session_key();
let initial_session_key = session_key.as_ref().to_owned();

let updated_session_key = store
.update(session_key, HashMap::new(), &time::Duration::seconds(1))
.await
.unwrap();

assert_ne!(initial_session_key, updated_session_key.as_ref());
}
}
Loading