181 lines
5.8 KiB
Rust
181 lines
5.8 KiB
Rust
use std::sync::Arc;
|
|
|
|
use actix_web::cookie::time::Duration;
|
|
use redis::Commands;
|
|
use redis::cluster::ClusterClient;
|
|
use tokio::task;
|
|
|
|
use super::SessionKey;
|
|
use crate::storage::{
|
|
SessionStore,
|
|
format::{deserialize_session_state, serialize_session_state},
|
|
interface::{LoadError, SaveError, SessionState, UpdateError},
|
|
utils::generate_session_key,
|
|
};
|
|
|
|
#[derive(Clone)]
|
|
pub struct RedisClusterSessionStore {
|
|
configuration: CacheConfiguration,
|
|
client: ClusterClient,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct CacheConfiguration {
|
|
cache_keygen: Arc<dyn Fn(&str) -> String + Send + Sync>,
|
|
}
|
|
|
|
impl Default for CacheConfiguration {
|
|
fn default() -> Self {
|
|
Self {
|
|
cache_keygen: Arc::new(str::to_owned),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl RedisClusterSessionStore {
|
|
pub fn builder(connection_strings: Vec<String>) -> RedisClusterSessionStoreBuilder {
|
|
RedisClusterSessionStoreBuilder {
|
|
configuration: CacheConfiguration::default(),
|
|
connection_strings,
|
|
}
|
|
}
|
|
|
|
pub async fn new(connection_strings: Vec<String>) -> anyhow::Result<RedisClusterSessionStore> {
|
|
Self::builder(connection_strings).build().await
|
|
}
|
|
|
|
fn get_connection(&self) -> anyhow::Result<redis::cluster::ClusterConnection> {
|
|
self.client.get_connection().map_err(|e| anyhow::anyhow!(e))
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub struct RedisClusterSessionStoreBuilder {
|
|
configuration: CacheConfiguration,
|
|
connection_strings: Vec<String>,
|
|
}
|
|
|
|
impl RedisClusterSessionStoreBuilder {
|
|
pub fn cache_keygen<F>(mut self, keygen: F) -> Self
|
|
where
|
|
F: Fn(&str) -> String + 'static + Send + Sync,
|
|
{
|
|
self.configuration.cache_keygen = Arc::new(keygen);
|
|
self
|
|
}
|
|
|
|
pub async fn build(self) -> anyhow::Result<RedisClusterSessionStore> {
|
|
let client = ClusterClient::new(self.connection_strings)?;
|
|
Ok(RedisClusterSessionStore {
|
|
configuration: self.configuration,
|
|
client,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl SessionStore for RedisClusterSessionStore {
|
|
async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
|
|
let cache_key = self.configuration.cache_keygen.as_ref()(session_key.as_ref());
|
|
let conn = self.get_connection().map_err(LoadError::Other)?;
|
|
|
|
let value: Option<String> = task::spawn_blocking(move || {
|
|
let mut conn = conn;
|
|
conn.get::<_, Option<String>>(&cache_key)
|
|
})
|
|
.await
|
|
.map_err(|_| LoadError::Other(anyhow::anyhow!("Task panicked")))?
|
|
.map_err(|e: redis::RedisError| LoadError::Other(anyhow::anyhow!(e)))?;
|
|
|
|
match value {
|
|
None => Ok(None),
|
|
Some(value) => Ok(Some(
|
|
deserialize_session_state(&value).map_err(LoadError::Deserialization)?,
|
|
)),
|
|
}
|
|
}
|
|
|
|
async fn save(
|
|
&self,
|
|
session_state: SessionState,
|
|
ttl: &Duration,
|
|
) -> Result<SessionKey, SaveError> {
|
|
let body = serialize_session_state(&session_state).map_err(SaveError::Serialization)?;
|
|
let session_key = generate_session_key();
|
|
let cache_key = self.configuration.cache_keygen.as_ref()(session_key.as_ref());
|
|
let ttl_secs = ttl.whole_seconds() as u64;
|
|
let conn = self.get_connection().map_err(SaveError::Other)?;
|
|
|
|
task::spawn_blocking(move || {
|
|
let mut conn = conn;
|
|
conn.set_ex::<_, _, ()>(&cache_key, &body, ttl_secs)
|
|
})
|
|
.await
|
|
.map_err(|_| SaveError::Other(anyhow::anyhow!("Task panicked")))?
|
|
.map_err(|e: redis::RedisError| SaveError::Other(anyhow::anyhow!(e)))?;
|
|
|
|
Ok(session_key)
|
|
}
|
|
|
|
async fn update(
|
|
&self,
|
|
session_key: SessionKey,
|
|
session_state: SessionState,
|
|
ttl: &Duration,
|
|
) -> Result<SessionKey, UpdateError> {
|
|
let body = serialize_session_state(&session_state).map_err(UpdateError::Serialization)?;
|
|
let cache_key = self.configuration.cache_keygen.as_ref()(session_key.as_ref());
|
|
let ttl_secs = ttl.whole_seconds() as u64;
|
|
let conn = self.get_connection().map_err(UpdateError::Other)?;
|
|
|
|
let existed: bool = task::spawn_blocking(move || {
|
|
let mut conn = conn;
|
|
conn.set_ex::<_, _, bool>(&cache_key, &body, ttl_secs)
|
|
})
|
|
.await
|
|
.map_err(|_| UpdateError::Other(anyhow::anyhow!("Task panicked")))?
|
|
.map_err(|e: redis::RedisError| UpdateError::Other(anyhow::anyhow!(e)))?;
|
|
|
|
if !existed {
|
|
self.save(session_state, ttl)
|
|
.await
|
|
.map_err(|err| match err {
|
|
SaveError::Serialization(err) => UpdateError::Serialization(err),
|
|
SaveError::Other(err) => UpdateError::Other(err),
|
|
})
|
|
} else {
|
|
Ok(session_key)
|
|
}
|
|
}
|
|
|
|
async fn update_ttl(&self, session_key: &SessionKey, ttl: &Duration) -> anyhow::Result<()> {
|
|
let cache_key = self.configuration.cache_keygen.as_ref()(session_key.as_ref());
|
|
let ttl_secs = ttl.whole_seconds() as i64;
|
|
let conn = self.get_connection()?;
|
|
|
|
task::spawn_blocking(move || {
|
|
let mut conn = conn;
|
|
conn.expire::<_, bool>(&cache_key, ttl_secs)
|
|
})
|
|
.await
|
|
.map_err(|_| anyhow::anyhow!("Task panicked"))?
|
|
.map_err(|e: redis::RedisError| anyhow::anyhow!(e))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error> {
|
|
let cache_key = self.configuration.cache_keygen.as_ref()(session_key.as_ref());
|
|
let conn = self.get_connection()?;
|
|
|
|
task::spawn_blocking(move || {
|
|
let mut conn = conn;
|
|
conn.del::<_, i64>(&cache_key)
|
|
})
|
|
.await
|
|
.map_err(|_| anyhow::anyhow!("Task panicked"))?
|
|
.map_err(|e: redis::RedisError| anyhow::anyhow!(e))?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|