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

feature(session): add deadpool-redis compatibility #381

Merged
merged 39 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
20ca707
Add compatibility of `deadpool-redis` for the storage `redis_rs`.
0rangeFox Jan 8, 2024
ab41ca6
Keep up-to-date the `actix-redis` version.
0rangeFox Jan 8, 2024
69195e6
Format the project issued by command `cargo +nightly fmt`.
0rangeFox Jan 8, 2024
99fb626
Add `deadpool-redis` into the documentation and tests.
0rangeFox Jan 8, 2024
8834e3a
Merge branch 'actix:master' into session-deadpool_redis-compatibility
0rangeFox Jan 8, 2024
73348c6
Update CHANGES.md.
0rangeFox Jan 8, 2024
6ef48e0
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Jan 12, 2024
3eb98ef
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Jan 15, 2024
b8b2dee
Update the documentation of `Deadpool Redis` section on `redis_rs`.
0rangeFox Jan 22, 2024
30b7402
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Jan 22, 2024
d4b684c
Replace `no_run` with `ignore` attribute on "Deadpool Redis" example …
0rangeFox Jan 26, 2024
69924bf
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Jan 26, 2024
c97e239
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Jan 29, 2024
d2f029e
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Jan 31, 2024
1b86114
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Feb 5, 2024
90328ab
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Feb 15, 2024
b75c4e1
Rollback the renaming `redis::cmd` to `cmd` for better reading and av…
0rangeFox Feb 15, 2024
e974a2c
Format the project issued by command `cargo +nightly fmt`.
0rangeFox Feb 15, 2024
870cf7e
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Feb 21, 2024
7a391bb
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Mar 5, 2024
2a56691
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Mar 11, 2024
ce0caa3
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Mar 14, 2024
2a83eb3
Format.
0rangeFox Mar 14, 2024
a269380
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Apr 4, 2024
acc99ad
Merge branch 'master' into session-deadpool_redis-compatibility
robjtede Jun 9, 2024
5cb52c8
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Jun 10, 2024
201d8e7
Fix feature naming from the last merge.
0rangeFox Jun 10, 2024
e272290
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Jun 16, 2024
b396047
Merge branch 'master' into session-deadpool_redis-compatibility
0rangeFox Jul 5, 2024
83f7235
Merge branch 'master' into session-deadpool_redis-compatibility
robjtede Aug 1, 2024
eaba963
Fix feature missing from the last merge.
0rangeFox Aug 1, 2024
256b442
Format the project issued by command `cargo +nightly fmt`.
0rangeFox Aug 1, 2024
307299d
Re-import `cookie-session` feature. (Maybe was removed accidentally f…
0rangeFox Aug 1, 2024
a8d77af
tmp
robjtede Aug 3, 2024
4f08499
chore: bump deadpool-redis to 0.16
robjtede Aug 6, 2024
8ebe7a7
chore: fixup rest of redis code for pool
robjtede Aug 6, 2024
563ac73
fix: add missing cfg guard
robjtede Aug 6, 2024
33d65a2
Merge branch 'master' into session-deadpool_redis-compatibility
robjtede Aug 6, 2024
8ca14e4
docs: fix pool docs
robjtede Aug 6, 2024
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
4 changes: 4 additions & 0 deletions actix-session/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
## 0.9.0

- Remove use of `async-trait` on `SessionStore` trait.
- Add `deadpool-redis` dependency; Implement `deadpool_redis::Pool` for `RedisSessionStore`. [#381]
- Update `actix-redis` dependency to `0.13`.
- Minimum supported Rust version (MSRV) is now 1.75.

[#381]: https://github.com/actix/actix-extras/pull/381

## 0.8.0

- Set secure attribute when adding a session removal cookie.
Expand Down
4 changes: 4 additions & 0 deletions actix-session/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ cookie-session = []
redis-session = ["dep:redis", "dep:rand"]
redis-session-native-tls = ["redis-session", "redis/tokio-native-tls-comp"]
redis-session-rustls = ["redis-session", "redis/tokio-rustls-comp"]
redis-dp-session = ["dep:deadpool-redis", "dep:rand"]

[dependencies]
actix-service = "2"
Expand All @@ -39,6 +40,9 @@ tracing = { version = "0.1.30", default-features = false, features = ["log"] }
# redis-session
redis = { version = "0.26", default-features = false, features = ["tokio-comp", "connection-manager"], optional = true }

# redis-dp-session
deadpool-redis = { version = "0.14", optional = true }
robjtede marked this conversation as resolved.
Show resolved Hide resolved

[dev-dependencies]
actix-session = { path = ".", features = ["cookie-session", "redis-session"] }
actix-test = "0.1.0-beta.10"
Expand Down
8 changes: 4 additions & 4 deletions actix-session/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
#[cfg(feature = "cookie-session")]
mod cookie;
mod interface;
#[cfg(feature = "redis-session")]
#[cfg(any(feature = "redis-session", feature = "redis-dp-session"))]
mod redis_rs;
mod session_key;
#[cfg(feature = "redis-session")]
#[cfg(any(feature = "redis-session", feature = "redis-dp-session"))]
mod utils;

#[cfg(feature = "cookie-session")]
pub use self::cookie::CookieSessionStore;
#[cfg(feature = "redis-session")]
#[cfg(any(feature = "redis-session", feature = "redis-dp-session"))]
pub use self::redis_rs::{RedisSessionStore, RedisSessionStoreBuilder};
#[cfg(feature = "redis-session")]
#[cfg(any(feature = "redis-session", feature = "redis-dp-session"))]
pub use self::utils::generate_session_key;
pub use self::{
interface::{LoadError, SaveError, SessionStore, UpdateError},
Expand Down
120 changes: 107 additions & 13 deletions actix-session/src/storage/redis_rs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use std::sync::Arc;

use actix_web::cookie::time::Duration;
use anyhow::Error;
use redis::{aio::ConnectionManager, AsyncCommands, Cmd, FromRedisValue, RedisResult, Value};
#[cfg(not(feature = "redis-session"))]
use deadpool_redis::{redis, Pool};
#[cfg(feature = "redis-session")]
use redis::aio::ConnectionManager;
use redis::{AsyncCommands, Cmd, FromRedisValue, RedisResult, Value};

use super::SessionKey;
use crate::storage::{
Expand Down Expand Up @@ -56,14 +60,28 @@ use crate::storage::{
/// # })
/// ```
///
/// # Deadpool Redis
///
/// ```ignore
/// use actix_session::storage::RedisSessionStore;
/// use deadpool_redis::{Config, Runtime};
///
/// let redis_cfg = Config::from_url("redis://127.0.0.1:6379");
/// let redis_pool = redis_cfg.create_pool(Some(Runtime::Tokio1)).unwrap();
/// let store = RedisSessionStore::new(redis_pool.clone());
/// ```
///
/// # Implementation notes
/// `RedisSessionStore` leverages [`redis-rs`] as Redis client.
///
/// [`redis-rs`]: https://github.com/mitsuhiko/redis-rs
#[derive(Clone)]
pub struct RedisSessionStore {
configuration: CacheConfiguration,
#[cfg(feature = "redis-session")]
client: ConnectionManager,
#[cfg(not(feature = "redis-session"))]
pool: Pool,
}

#[derive(Clone)]
Expand All @@ -83,31 +101,53 @@ impl RedisSessionStore {
/// A fluent API to configure [`RedisSessionStore`].
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`] - a
/// connection string for Redis.
#[cfg(feature = "redis-session")]
pub fn builder<S: Into<String>>(connection_string: S) -> RedisSessionStoreBuilder {
RedisSessionStoreBuilder {
configuration: CacheConfiguration::default(),
connection_string: connection_string.into(),
}
}

/// A fluent API to configure [`RedisSessionStore`].
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`] - a
/// pool object for Redis.
#[cfg(not(feature = "redis-session"))]
pub fn builder<P: Into<Pool>>(pool: P) -> RedisSessionStoreBuilder {
RedisSessionStoreBuilder {
configuration: CacheConfiguration::default(),
pool: pool.into(),
}
}

/// Create a new instance of [`RedisSessionStore`] using the default configuration.
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`] - a
/// connection string for Redis.
pub async fn new<S: Into<String>>(
connection_string: S,
) -> Result<RedisSessionStore, anyhow::Error> {
#[cfg(feature = "redis-session")]
pub async fn new<S: Into<String>>(connection_string: S) -> Result<RedisSessionStore, Error> {
robjtede marked this conversation as resolved.
Show resolved Hide resolved
Self::builder(connection_string).build().await
}

/// Create a new instance of [`RedisSessionStore`] using the default configuration.
/// It takes as input the only required input to create a new instance of [`RedisSessionStore`] - a
/// pool object for Redis.
#[cfg(not(feature = "redis-session"))]
pub fn new<P: Into<Pool>>(pool: P) -> RedisSessionStore {
Self::builder(pool).build()
}
}

/// A fluent builder to construct a [`RedisSessionStore`] instance with custom configuration
/// parameters.
///
/// [`RedisSessionStore`]: crate::storage::RedisSessionStore
/// [`RedisSessionStore`]: RedisSessionStore
#[must_use]
pub struct RedisSessionStoreBuilder {
connection_string: String,
configuration: CacheConfiguration,
#[cfg(feature = "redis-session")]
connection_string: String,
#[cfg(not(feature = "redis-session"))]
pool: Pool,
}

impl RedisSessionStoreBuilder {
Expand All @@ -121,13 +161,27 @@ impl RedisSessionStoreBuilder {
}

/// Finalise the builder and return a [`RedisSessionStore`] instance.
pub async fn build(self) -> Result<RedisSessionStore, anyhow::Error> {
///
/// [`RedisSessionStore`]: RedisSessionStore
#[cfg(feature = "redis-session")]
pub async fn build(self) -> Result<RedisSessionStore, Error> {
let client = ConnectionManager::new(redis::Client::open(self.connection_string)?).await?;
Ok(RedisSessionStore {
configuration: self.configuration,
client,
})
}

/// Finalise the builder and return a [`RedisSessionStore`] instance.
///
/// [`RedisSessionStore`]: RedisSessionStore
#[cfg(not(feature = "redis-session"))]
pub fn build(self) -> RedisSessionStore {
RedisSessionStore {
configuration: self.configuration,
pool: self.pool,
}
}
}

impl SessionStore for RedisSessionStore {
Expand Down Expand Up @@ -190,7 +244,7 @@ impl SessionStore for RedisSessionStore {

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

let v: redis::Value = self
let v: Value = self
.execute_command(redis::cmd("SET").arg(&[
&cache_key,
&body,
Expand Down Expand Up @@ -226,15 +280,23 @@ impl SessionStore for RedisSessionStore {
async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> Result<(), Error> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

#[cfg(feature = "redis-session")]
self.client
.clone()
.expire::<_, ()>(&cache_key, ttl.whole_seconds())
.await?;

#[cfg(not(feature = "redis-session"))]
self.pool
.get()
.await?
.expire(&cache_key, ttl.whole_seconds())
.await?;

Ok(())
}

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

self.execute_command::<()>(redis::cmd("DEL").arg(&[&cache_key]))
Expand Down Expand Up @@ -264,8 +326,14 @@ impl RedisSessionStore {
async fn execute_command<T: FromRedisValue>(&self, cmd: &mut Cmd) -> RedisResult<T> {
let mut can_retry = true;

#[cfg(feature = "redis-session")]
let client = &mut self.client.clone();

#[cfg(not(feature = "redis-session"))]
let client = &mut self.pool.get().await.unwrap();

loop {
match cmd.query_async(&mut self.client.clone()).await {
match cmd.query_async(client).await {
0rangeFox marked this conversation as resolved.
Show resolved Hide resolved
Ok(value) => return Ok(value),
Err(err) => {
if can_retry && err.is_connection_dropped() {
Expand All @@ -291,14 +359,27 @@ mod tests {
use std::collections::HashMap;

use actix_web::cookie::time;
#[cfg(not(feature = "redis-session"))]
use deadpool_redis::{Config, Runtime};

use super::*;
use crate::test_helpers::acceptance_test_suite;

async fn redis_store() -> RedisSessionStore {
RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.unwrap()
#[cfg(feature = "redis-session")]
{
RedisSessionStore::new("redis://127.0.0.1:6379")
.await
.unwrap()
}

#[cfg(not(feature = "redis-session"))]
{
let redis_pool = Config::from_url("redis://127.0.0.1:6379")
.create_pool(Some(Runtime::Tokio1))
.unwrap();
RedisSessionStore::new(redis_pool.clone())
}
}

#[actix_web::test]
Expand All @@ -318,12 +399,25 @@ mod tests {
async fn loading_an_invalid_session_state_returns_deserialization_error() {
let store = redis_store().await;
let session_key = generate_session_key();

#[cfg(feature = "redis-session")]
store
.client
.clone()
.set::<_, _, ()>(session_key.as_ref(), "random-thing-which-is-not-json")
.await
.unwrap();

#[cfg(not(feature = "redis-session"))]
store
.pool
.get()
.await
.unwrap()
.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 Down
Loading