mirror of
https://github.com/actix/actix-extras.git
synced 2025-09-02 21:16:37 +02:00
Deploying to gh-pages from @ 1e682e7a59
🚀
This commit is contained in:
@@ -315,320 +315,320 @@
|
||||
<span id="313">313</span>
|
||||
<span id="314">314</span>
|
||||
<span id="315">315</span>
|
||||
</pre><pre class="rust"><code><span class="kw">use</span> <span class="ident">actix::Addr</span>;
|
||||
<span class="kw">use</span> <span class="ident">actix_redis</span>::{<span class="ident">resp_array</span>, <span class="ident">Command</span>, <span class="ident">RedisActor</span>, <span class="ident">RespValue</span>};
|
||||
<span class="kw">use</span> <span class="ident">actix_web::cookie::time::Duration</span>;
|
||||
<span class="kw">use</span> <span class="ident">anyhow::Error</span>;
|
||||
</pre><pre class="rust"><code><span class="kw">use </span>actix::Addr;
|
||||
<span class="kw">use </span>actix_redis::{resp_array, Command, RedisActor, RespValue};
|
||||
<span class="kw">use </span>actix_web::cookie::time::Duration;
|
||||
<span class="kw">use </span>anyhow::Error;
|
||||
|
||||
<span class="kw">use</span> <span class="ident"><span class="kw">super</span>::SessionKey</span>;
|
||||
<span class="kw">use</span> <span class="ident"><span class="kw">crate</span>::storage</span>::{
|
||||
<span class="ident">interface</span>::{<span class="ident">LoadError</span>, <span class="ident">SaveError</span>, <span class="ident">SessionState</span>, <span class="ident">UpdateError</span>},
|
||||
<span class="ident">utils::generate_session_key</span>,
|
||||
<span class="ident">SessionStore</span>,
|
||||
<span class="kw">use </span><span class="kw">super</span>::SessionKey;
|
||||
<span class="kw">use </span><span class="kw">crate</span>::storage::{
|
||||
interface::{LoadError, SaveError, SessionState, UpdateError},
|
||||
utils::generate_session_key,
|
||||
SessionStore,
|
||||
};
|
||||
|
||||
<span class="doccomment">/// Use Redis as session storage backend.</span>
|
||||
<span class="doccomment">///</span>
|
||||
<span class="doccomment">/// ```no_run</span>
|
||||
<span class="doccomment">/// use actix_web::{web, App, HttpServer, HttpResponse, Error};</span>
|
||||
<span class="doccomment">/// use actix_session::{SessionMiddleware, storage::RedisActorSessionStore};</span>
|
||||
<span class="doccomment">/// use actix_web::cookie::Key;</span>
|
||||
<span class="doccomment">///</span>
|
||||
<span class="doccomment">/// // The secret key would usually be read from a configuration file/environment variables.</span>
|
||||
<span class="doccomment">/// fn get_secret_key() -> Key {</span>
|
||||
<span class="doccomment">/// # todo!()</span>
|
||||
<span class="doccomment">/// // [...]</span>
|
||||
<span class="doccomment">/// }</span>
|
||||
<span class="doccomment">///</span>
|
||||
<span class="doccomment">/// #[actix_web::main]</span>
|
||||
<span class="doccomment">/// async fn main() -> std::io::Result<()> {</span>
|
||||
<span class="doccomment">/// let secret_key = get_secret_key();</span>
|
||||
<span class="doccomment">/// let redis_connection_string = "127.0.0.1:6379";</span>
|
||||
<span class="doccomment">/// HttpServer::new(move ||</span>
|
||||
<span class="doccomment">/// App::new()</span>
|
||||
<span class="doccomment">/// .wrap(</span>
|
||||
<span class="doccomment">/// SessionMiddleware::new(</span>
|
||||
<span class="doccomment">/// RedisActorSessionStore::new(redis_connection_string),</span>
|
||||
<span class="doccomment">/// secret_key.clone()</span>
|
||||
<span class="doccomment">/// )</span>
|
||||
<span class="doccomment">/// )</span>
|
||||
<span class="doccomment">/// .default_service(web::to(|| HttpResponse::Ok())))</span>
|
||||
<span class="doccomment">/// .bind(("127.0.0.1", 8080))?</span>
|
||||
<span class="doccomment">/// .run()</span>
|
||||
<span class="doccomment">/// .await</span>
|
||||
<span class="doccomment">/// }</span>
|
||||
<span class="doccomment">/// ```</span>
|
||||
<span class="doccomment">///</span>
|
||||
<span class="doccomment">/// # Implementation notes</span>
|
||||
<span class="doccomment">///</span>
|
||||
<span class="doccomment">/// `RedisActorSessionStore` leverages `actix-redis`'s `RedisActor` implementation - each thread</span>
|
||||
<span class="doccomment">/// worker gets its own connection to Redis.</span>
|
||||
<span class="doccomment">///</span>
|
||||
<span class="doccomment">/// ## Limitations</span>
|
||||
<span class="doccomment">///</span>
|
||||
<span class="doccomment">/// `RedisActorSessionStore` does not currently support establishing authenticated connections to</span>
|
||||
<span class="doccomment">/// Redis. Use [`RedisSessionStore`] if you need TLS support.</span>
|
||||
<span class="doccomment">///</span>
|
||||
<span class="doccomment">/// [`RedisSessionStore`]: crate::storage::RedisSessionStore</span>
|
||||
<span class="attribute">#[<span class="ident">cfg_attr</span>(<span class="ident">docsrs</span>, <span class="ident">doc</span>(<span class="ident">cfg</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">"redis-actor-session"</span>)))]</span>
|
||||
<span class="kw">pub</span> <span class="kw">struct</span> <span class="ident">RedisActorSessionStore</span> {
|
||||
<span class="ident">configuration</span>: <span class="ident">CacheConfiguration</span>,
|
||||
<span class="ident">addr</span>: <span class="ident">Addr</span><span class="op"><</span><span class="ident">RedisActor</span><span class="op">></span>,
|
||||
<span class="doccomment">/// Use Redis as session storage backend.
|
||||
///
|
||||
/// ```no_run
|
||||
/// use actix_web::{web, App, HttpServer, HttpResponse, Error};
|
||||
/// use actix_session::{SessionMiddleware, storage::RedisActorSessionStore};
|
||||
/// use actix_web::cookie::Key;
|
||||
///
|
||||
/// // The secret key would usually be read from a configuration file/environment variables.
|
||||
/// fn get_secret_key() -> Key {
|
||||
/// # todo!()
|
||||
/// // [...]
|
||||
/// }
|
||||
///
|
||||
/// #[actix_web::main]
|
||||
/// async fn main() -> std::io::Result<()> {
|
||||
/// let secret_key = get_secret_key();
|
||||
/// let redis_connection_string = "127.0.0.1:6379";
|
||||
/// HttpServer::new(move ||
|
||||
/// App::new()
|
||||
/// .wrap(
|
||||
/// SessionMiddleware::new(
|
||||
/// RedisActorSessionStore::new(redis_connection_string),
|
||||
/// secret_key.clone()
|
||||
/// )
|
||||
/// )
|
||||
/// .default_service(web::to(|| HttpResponse::Ok())))
|
||||
/// .bind(("127.0.0.1", 8080))?
|
||||
/// .run()
|
||||
/// .await
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Implementation notes
|
||||
///
|
||||
/// `RedisActorSessionStore` leverages `actix-redis`'s `RedisActor` implementation - each thread
|
||||
/// worker gets its own connection to Redis.
|
||||
///
|
||||
/// ## Limitations
|
||||
///
|
||||
/// `RedisActorSessionStore` does not currently support establishing authenticated connections to
|
||||
/// Redis. Use [`RedisSessionStore`] if you need TLS support.
|
||||
///
|
||||
/// [`RedisSessionStore`]: crate::storage::RedisSessionStore
|
||||
</span><span class="attribute">#[cfg_attr(docsrs, doc(cfg(feature = <span class="string">"redis-actor-session"</span>)))]
|
||||
</span><span class="kw">pub struct </span>RedisActorSessionStore {
|
||||
configuration: CacheConfiguration,
|
||||
addr: Addr<RedisActor>,
|
||||
}
|
||||
|
||||
<span class="kw">impl</span> <span class="ident">RedisActorSessionStore</span> {
|
||||
<span class="doccomment">/// A fluent API to configure [`RedisActorSessionStore`].</span>
|
||||
<span class="doccomment">///</span>
|
||||
<span class="doccomment">/// It takes as input the only required input to create a new instance of</span>
|
||||
<span class="doccomment">/// [`RedisActorSessionStore`]—a connection string for Redis.</span>
|
||||
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">builder</span><span class="op"><</span><span class="ident">S</span>: <span class="ident">Into</span><span class="op"><</span><span class="ident">String</span><span class="op">></span><span class="op">></span>(<span class="ident">connection_string</span>: <span class="ident">S</span>) -> <span class="ident">RedisActorSessionStoreBuilder</span> {
|
||||
<span class="ident">RedisActorSessionStoreBuilder</span> {
|
||||
<span class="ident">configuration</span>: <span class="ident">CacheConfiguration::default</span>(),
|
||||
<span class="ident">connection_string</span>: <span class="ident">connection_string</span>.<span class="ident">into</span>(),
|
||||
<span class="kw">impl </span>RedisActorSessionStore {
|
||||
<span class="doccomment">/// A fluent API to configure [`RedisActorSessionStore`].
|
||||
///
|
||||
/// It takes as input the only required input to create a new instance of
|
||||
/// [`RedisActorSessionStore`]—a connection string for Redis.
|
||||
</span><span class="kw">pub fn </span>builder<S: Into<String>>(connection_string: S) -> RedisActorSessionStoreBuilder {
|
||||
RedisActorSessionStoreBuilder {
|
||||
configuration: CacheConfiguration::default(),
|
||||
connection_string: connection_string.into(),
|
||||
}
|
||||
}
|
||||
|
||||
<span class="doccomment">/// Create a new instance of [`RedisActorSessionStore`] using the default configuration.</span>
|
||||
<span class="doccomment">/// It takes as input the only required input to create a new instance of [`RedisActorSessionStore`] - a</span>
|
||||
<span class="doccomment">/// connection string for Redis.</span>
|
||||
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">new</span><span class="op"><</span><span class="ident">S</span>: <span class="ident">Into</span><span class="op"><</span><span class="ident">String</span><span class="op">></span><span class="op">></span>(<span class="ident">connection_string</span>: <span class="ident">S</span>) -> <span class="ident">RedisActorSessionStore</span> {
|
||||
<span class="ident"><span class="self">Self</span>::builder</span>(<span class="ident">connection_string</span>).<span class="ident">build</span>()
|
||||
<span class="doccomment">/// Create a new instance of [`RedisActorSessionStore`] using the default configuration.
|
||||
/// It takes as input the only required input to create a new instance of [`RedisActorSessionStore`] - a
|
||||
/// connection string for Redis.
|
||||
</span><span class="kw">pub fn </span>new<S: Into<String>>(connection_string: S) -> RedisActorSessionStore {
|
||||
<span class="self">Self</span>::builder(connection_string).build()
|
||||
}
|
||||
}
|
||||
|
||||
<span class="kw">struct</span> <span class="ident">CacheConfiguration</span> {
|
||||
<span class="ident">cache_keygen</span>: <span class="ident">Box</span><span class="op"><</span><span class="kw">dyn</span> <span class="ident">Fn</span>(<span class="kw-2">&</span><span class="ident">str</span>) -> <span class="ident">String</span><span class="op">></span>,
|
||||
<span class="kw">struct </span>CacheConfiguration {
|
||||
cache_keygen: Box<<span class="kw">dyn </span>Fn(<span class="kw-2">&</span>str) -> String>,
|
||||
}
|
||||
|
||||
<span class="kw">impl</span> <span class="ident">Default</span> <span class="kw">for</span> <span class="ident">CacheConfiguration</span> {
|
||||
<span class="kw">fn</span> <span class="ident">default</span>() -> <span class="self">Self</span> {
|
||||
<span class="self">Self</span> {
|
||||
<span class="ident">cache_keygen</span>: <span class="ident">Box::new</span>(<span class="ident">str::to_owned</span>),
|
||||
<span class="kw">impl </span>Default <span class="kw">for </span>CacheConfiguration {
|
||||
<span class="kw">fn </span>default() -> <span class="self">Self </span>{
|
||||
<span class="self">Self </span>{
|
||||
cache_keygen: Box::new(str::to_owned),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<span class="doccomment">/// A fluent builder to construct a [`RedisActorSessionStore`] instance with custom configuration</span>
|
||||
<span class="doccomment">/// parameters.</span>
|
||||
<span class="attribute">#[<span class="ident">cfg_attr</span>(<span class="ident">docsrs</span>, <span class="ident">doc</span>(<span class="ident">cfg</span>(<span class="ident">feature</span> <span class="op">=</span> <span class="string">"redis-actor-session"</span>)))]</span>
|
||||
<span class="attribute">#[<span class="ident">must_use</span>]</span>
|
||||
<span class="kw">pub</span> <span class="kw">struct</span> <span class="ident">RedisActorSessionStoreBuilder</span> {
|
||||
<span class="ident">connection_string</span>: <span class="ident">String</span>,
|
||||
<span class="ident">configuration</span>: <span class="ident">CacheConfiguration</span>,
|
||||
<span class="doccomment">/// A fluent builder to construct a [`RedisActorSessionStore`] instance with custom configuration
|
||||
/// parameters.
|
||||
</span><span class="attribute">#[cfg_attr(docsrs, doc(cfg(feature = <span class="string">"redis-actor-session"</span>)))]
|
||||
#[must_use]
|
||||
</span><span class="kw">pub struct </span>RedisActorSessionStoreBuilder {
|
||||
connection_string: String,
|
||||
configuration: CacheConfiguration,
|
||||
}
|
||||
|
||||
<span class="kw">impl</span> <span class="ident">RedisActorSessionStoreBuilder</span> {
|
||||
<span class="doccomment">/// Set a custom cache key generation strategy, expecting a session key as input.</span>
|
||||
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">cache_keygen</span><span class="op"><</span><span class="ident">F</span><span class="op">></span>(<span class="kw-2">mut</span> <span class="self">self</span>, <span class="ident">keygen</span>: <span class="ident">F</span>) -> <span class="self">Self</span>
|
||||
<span class="kw">where</span>
|
||||
<span class="ident">F</span>: <span class="ident">Fn</span>(<span class="kw-2">&</span><span class="ident">str</span>) -> <span class="ident">String</span> <span class="op">+</span> <span class="lifetime">'static</span>,
|
||||
<span class="kw">impl </span>RedisActorSessionStoreBuilder {
|
||||
<span class="doccomment">/// Set a custom cache key generation strategy, expecting a session key as input.
|
||||
</span><span class="kw">pub fn </span>cache_keygen<F>(<span class="kw-2">mut </span><span class="self">self</span>, keygen: F) -> <span class="self">Self
|
||||
</span><span class="kw">where
|
||||
</span>F: Fn(<span class="kw-2">&</span>str) -> String + <span class="lifetime">'static</span>,
|
||||
{
|
||||
<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span> <span class="op">=</span> <span class="ident">Box::new</span>(<span class="ident">keygen</span>);
|
||||
<span class="self">self</span>
|
||||
}
|
||||
<span class="self">self</span>.configuration.cache_keygen = Box::new(keygen);
|
||||
<span class="self">self
|
||||
</span>}
|
||||
|
||||
<span class="doccomment">/// Finalise the builder and return a [`RedisActorSessionStore`] instance.</span>
|
||||
<span class="attribute">#[<span class="ident">must_use</span>]</span>
|
||||
<span class="kw">pub</span> <span class="kw">fn</span> <span class="ident">build</span>(<span class="self">self</span>) -> <span class="ident">RedisActorSessionStore</span> {
|
||||
<span class="ident">RedisActorSessionStore</span> {
|
||||
<span class="ident">configuration</span>: <span class="self">self</span>.<span class="ident">configuration</span>,
|
||||
<span class="ident">addr</span>: <span class="ident">RedisActor::start</span>(<span class="self">self</span>.<span class="ident">connection_string</span>),
|
||||
<span class="doccomment">/// Finalise the builder and return a [`RedisActorSessionStore`] instance.
|
||||
</span><span class="attribute">#[must_use]
|
||||
</span><span class="kw">pub fn </span>build(<span class="self">self</span>) -> RedisActorSessionStore {
|
||||
RedisActorSessionStore {
|
||||
configuration: <span class="self">self</span>.configuration,
|
||||
addr: RedisActor::start(<span class="self">self</span>.connection_string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<span class="attribute">#[<span class="ident">async_trait::async_trait</span>(<span class="question-mark">?</span><span class="ident">Send</span>)]</span>
|
||||
<span class="kw">impl</span> <span class="ident">SessionStore</span> <span class="kw">for</span> <span class="ident">RedisActorSessionStore</span> {
|
||||
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">load</span>(<span class="kw-2">&</span><span class="self">self</span>, <span class="ident">session_key</span>: <span class="kw-2">&</span><span class="ident">SessionKey</span>) -> <span class="prelude-ty">Result</span><span class="op"><</span><span class="prelude-ty">Option</span><span class="op"><</span><span class="ident">SessionState</span><span class="op">></span>, <span class="ident">LoadError</span><span class="op">></span> {
|
||||
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
|
||||
<span class="kw">let</span> <span class="ident">val</span> <span class="op">=</span> <span class="self">self</span>
|
||||
.<span class="ident">addr</span>
|
||||
.<span class="ident">send</span>(<span class="ident">Command</span>(<span class="macro">resp_array!</span>[<span class="string">"GET"</span>, <span class="ident">cache_key</span>]))
|
||||
.<span class="kw">await</span>
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">LoadError::Other</span>)<span class="question-mark">?</span>
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">LoadError::Other</span>)<span class="question-mark">?</span>;
|
||||
<span class="attribute">#[async_trait::async_trait(<span class="question-mark">?</span>Send)]
|
||||
</span><span class="kw">impl </span>SessionStore <span class="kw">for </span>RedisActorSessionStore {
|
||||
<span class="kw">async fn </span>load(<span class="kw-2">&</span><span class="self">self</span>, session_key: <span class="kw-2">&</span>SessionKey) -> <span class="prelude-ty">Result</span><<span class="prelude-ty">Option</span><SessionState>, LoadError> {
|
||||
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
|
||||
<span class="kw">let </span>val = <span class="self">self
|
||||
</span>.addr
|
||||
.send(Command(<span class="macro">resp_array!</span>[<span class="string">"GET"</span>, cache_key]))
|
||||
.<span class="kw">await
|
||||
</span>.map_err(Into::into)
|
||||
.map_err(LoadError::Other)<span class="question-mark">?
|
||||
</span>.map_err(Into::into)
|
||||
.map_err(LoadError::Other)<span class="question-mark">?</span>;
|
||||
|
||||
<span class="kw">match</span> <span class="ident">val</span> {
|
||||
<span class="ident">RespValue::Error</span>(<span class="ident">err</span>) => <span class="prelude-val">Err</span>(<span class="ident">LoadError::Other</span>(<span class="macro">anyhow::anyhow!</span>(<span class="ident">err</span>))),
|
||||
<span class="kw">match </span>val {
|
||||
RespValue::Error(err) => <span class="prelude-val">Err</span>(LoadError::Other(<span class="macro">anyhow::anyhow!</span>(err))),
|
||||
|
||||
<span class="ident">RespValue::SimpleString</span>(<span class="ident">s</span>) => <span class="prelude-val">Ok</span>(<span class="ident">serde_json::from_str</span>(<span class="kw-2">&</span><span class="ident">s</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">LoadError::Deserialization</span>)<span class="question-mark">?</span>),
|
||||
RespValue::SimpleString(s) => <span class="prelude-val">Ok</span>(serde_json::from_str(<span class="kw-2">&</span>s)
|
||||
.map_err(Into::into)
|
||||
.map_err(LoadError::Deserialization)<span class="question-mark">?</span>),
|
||||
|
||||
<span class="ident">RespValue::BulkString</span>(<span class="ident">s</span>) => <span class="prelude-val">Ok</span>(<span class="ident">serde_json::from_slice</span>(<span class="kw-2">&</span><span class="ident">s</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">LoadError::Deserialization</span>)<span class="question-mark">?</span>),
|
||||
RespValue::BulkString(s) => <span class="prelude-val">Ok</span>(serde_json::from_slice(<span class="kw-2">&</span>s)
|
||||
.map_err(Into::into)
|
||||
.map_err(LoadError::Deserialization)<span class="question-mark">?</span>),
|
||||
|
||||
<span class="kw">_</span> => <span class="prelude-val">Ok</span>(<span class="prelude-val">None</span>),
|
||||
<span class="kw">_ </span>=> <span class="prelude-val">Ok</span>(<span class="prelude-val">None</span>),
|
||||
}
|
||||
}
|
||||
|
||||
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">save</span>(
|
||||
<span class="kw">async fn </span>save(
|
||||
<span class="kw-2">&</span><span class="self">self</span>,
|
||||
<span class="ident">session_state</span>: <span class="ident">SessionState</span>,
|
||||
<span class="ident">ttl</span>: <span class="kw-2">&</span><span class="ident">Duration</span>,
|
||||
) -> <span class="prelude-ty">Result</span><span class="op"><</span><span class="ident">SessionKey</span>, <span class="ident">SaveError</span><span class="op">></span> {
|
||||
<span class="kw">let</span> <span class="ident">body</span> <span class="op">=</span> <span class="ident">serde_json::to_string</span>(<span class="kw-2">&</span><span class="ident">session_state</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">SaveError::Serialization</span>)<span class="question-mark">?</span>;
|
||||
<span class="kw">let</span> <span class="ident">session_key</span> <span class="op">=</span> <span class="ident">generate_session_key</span>();
|
||||
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
|
||||
session_state: SessionState,
|
||||
ttl: <span class="kw-2">&</span>Duration,
|
||||
) -> <span class="prelude-ty">Result</span><SessionKey, SaveError> {
|
||||
<span class="kw">let </span>body = serde_json::to_string(<span class="kw-2">&</span>session_state)
|
||||
.map_err(Into::into)
|
||||
.map_err(SaveError::Serialization)<span class="question-mark">?</span>;
|
||||
<span class="kw">let </span>session_key = generate_session_key();
|
||||
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
|
||||
|
||||
<span class="kw">let</span> <span class="ident">cmd</span> <span class="op">=</span> <span class="ident">Command</span>(<span class="macro">resp_array!</span>[
|
||||
<span class="kw">let </span>cmd = Command(<span class="macro">resp_array!</span>[
|
||||
<span class="string">"SET"</span>,
|
||||
<span class="ident">cache_key</span>,
|
||||
<span class="ident">body</span>,
|
||||
<span class="string">"NX"</span>, <span class="comment">// NX: only set the key if it does not already exist</span>
|
||||
<span class="string">"EX"</span>, <span class="comment">// EX: set expiry</span>
|
||||
<span class="macro">format!</span>(<span class="string">"{}"</span>, <span class="ident">ttl</span>.<span class="ident">whole_seconds</span>())
|
||||
cache_key,
|
||||
body,
|
||||
<span class="string">"NX"</span>, <span class="comment">// NX: only set the key if it does not already exist
|
||||
</span><span class="string">"EX"</span>, <span class="comment">// EX: set expiry
|
||||
</span><span class="macro">format!</span>(<span class="string">"{}"</span>, ttl.whole_seconds())
|
||||
]);
|
||||
|
||||
<span class="kw">let</span> <span class="ident">result</span> <span class="op">=</span> <span class="self">self</span>
|
||||
.<span class="ident">addr</span>
|
||||
.<span class="ident">send</span>(<span class="ident">cmd</span>)
|
||||
.<span class="kw">await</span>
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">SaveError::Other</span>)<span class="question-mark">?</span>
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">SaveError::Other</span>)<span class="question-mark">?</span>;
|
||||
<span class="kw">let </span>result = <span class="self">self
|
||||
</span>.addr
|
||||
.send(cmd)
|
||||
.<span class="kw">await
|
||||
</span>.map_err(Into::into)
|
||||
.map_err(SaveError::Other)<span class="question-mark">?
|
||||
</span>.map_err(Into::into)
|
||||
.map_err(SaveError::Other)<span class="question-mark">?</span>;
|
||||
|
||||
<span class="kw">match</span> <span class="ident">result</span> {
|
||||
<span class="ident">RespValue::SimpleString</span>(<span class="kw">_</span>) => <span class="prelude-val">Ok</span>(<span class="ident">session_key</span>),
|
||||
<span class="ident">RespValue::Nil</span> => <span class="prelude-val">Err</span>(<span class="ident">SaveError::Other</span>(<span class="macro">anyhow::anyhow!</span>(
|
||||
<span class="string">"Failed to save session state. A record with the same key already existed in Redis"</span>
|
||||
))),
|
||||
<span class="ident">err</span> => <span class="prelude-val">Err</span>(<span class="ident">SaveError::Other</span>(<span class="macro">anyhow::anyhow!</span>(
|
||||
<span class="kw">match </span>result {
|
||||
RespValue::SimpleString(<span class="kw">_</span>) => <span class="prelude-val">Ok</span>(session_key),
|
||||
RespValue::Nil => <span class="prelude-val">Err</span>(SaveError::Other(<span class="macro">anyhow::anyhow!</span>(
|
||||
<span class="string">"Failed to save session state. A record with the same key already existed in Redis"
|
||||
</span>))),
|
||||
err => <span class="prelude-val">Err</span>(SaveError::Other(<span class="macro">anyhow::anyhow!</span>(
|
||||
<span class="string">"Failed to save session state. {:?}"</span>,
|
||||
<span class="ident">err</span>
|
||||
err
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">update</span>(
|
||||
<span class="kw">async fn </span>update(
|
||||
<span class="kw-2">&</span><span class="self">self</span>,
|
||||
<span class="ident">session_key</span>: <span class="ident">SessionKey</span>,
|
||||
<span class="ident">session_state</span>: <span class="ident">SessionState</span>,
|
||||
<span class="ident">ttl</span>: <span class="kw-2">&</span><span class="ident">Duration</span>,
|
||||
) -> <span class="prelude-ty">Result</span><span class="op"><</span><span class="ident">SessionKey</span>, <span class="ident">UpdateError</span><span class="op">></span> {
|
||||
<span class="kw">let</span> <span class="ident">body</span> <span class="op">=</span> <span class="ident">serde_json::to_string</span>(<span class="kw-2">&</span><span class="ident">session_state</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">UpdateError::Serialization</span>)<span class="question-mark">?</span>;
|
||||
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
|
||||
session_key: SessionKey,
|
||||
session_state: SessionState,
|
||||
ttl: <span class="kw-2">&</span>Duration,
|
||||
) -> <span class="prelude-ty">Result</span><SessionKey, UpdateError> {
|
||||
<span class="kw">let </span>body = serde_json::to_string(<span class="kw-2">&</span>session_state)
|
||||
.map_err(Into::into)
|
||||
.map_err(UpdateError::Serialization)<span class="question-mark">?</span>;
|
||||
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
|
||||
|
||||
<span class="kw">let</span> <span class="ident">cmd</span> <span class="op">=</span> <span class="ident">Command</span>(<span class="macro">resp_array!</span>[
|
||||
<span class="kw">let </span>cmd = Command(<span class="macro">resp_array!</span>[
|
||||
<span class="string">"SET"</span>,
|
||||
<span class="ident">cache_key</span>,
|
||||
<span class="ident">body</span>,
|
||||
<span class="string">"XX"</span>, <span class="comment">// XX: Only set the key if it already exist.</span>
|
||||
<span class="string">"EX"</span>, <span class="comment">// EX: set expiry</span>
|
||||
<span class="macro">format!</span>(<span class="string">"{}"</span>, <span class="ident">ttl</span>.<span class="ident">whole_seconds</span>())
|
||||
cache_key,
|
||||
body,
|
||||
<span class="string">"XX"</span>, <span class="comment">// XX: Only set the key if it already exist.
|
||||
</span><span class="string">"EX"</span>, <span class="comment">// EX: set expiry
|
||||
</span><span class="macro">format!</span>(<span class="string">"{}"</span>, ttl.whole_seconds())
|
||||
]);
|
||||
|
||||
<span class="kw">let</span> <span class="ident">result</span> <span class="op">=</span> <span class="self">self</span>
|
||||
.<span class="ident">addr</span>
|
||||
.<span class="ident">send</span>(<span class="ident">cmd</span>)
|
||||
.<span class="kw">await</span>
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">UpdateError::Other</span>)<span class="question-mark">?</span>
|
||||
.<span class="ident">map_err</span>(<span class="ident">Into::into</span>)
|
||||
.<span class="ident">map_err</span>(<span class="ident">UpdateError::Other</span>)<span class="question-mark">?</span>;
|
||||
<span class="kw">let </span>result = <span class="self">self
|
||||
</span>.addr
|
||||
.send(cmd)
|
||||
.<span class="kw">await
|
||||
</span>.map_err(Into::into)
|
||||
.map_err(UpdateError::Other)<span class="question-mark">?
|
||||
</span>.map_err(Into::into)
|
||||
.map_err(UpdateError::Other)<span class="question-mark">?</span>;
|
||||
|
||||
<span class="kw">match</span> <span class="ident">result</span> {
|
||||
<span class="ident">RespValue::Nil</span> => {
|
||||
<span class="comment">// The SET operation was not performed because the XX condition was not verified.</span>
|
||||
<span class="comment">// This can happen if the session state expired between the load operation and the</span>
|
||||
<span class="comment">// update operation. Unlucky, to say the least. We fall back to the `save` routine</span>
|
||||
<span class="comment">// to ensure that the new key is unique.</span>
|
||||
<span class="self">self</span>.<span class="ident">save</span>(<span class="ident">session_state</span>, <span class="ident">ttl</span>)
|
||||
.<span class="kw">await</span>
|
||||
.<span class="ident">map_err</span>(<span class="op">|</span><span class="ident">err</span><span class="op">|</span> <span class="kw">match</span> <span class="ident">err</span> {
|
||||
<span class="ident">SaveError::Serialization</span>(<span class="ident">err</span>) => <span class="ident">UpdateError::Serialization</span>(<span class="ident">err</span>),
|
||||
<span class="ident">SaveError::Other</span>(<span class="ident">err</span>) => <span class="ident">UpdateError::Other</span>(<span class="ident">err</span>),
|
||||
<span class="kw">match </span>result {
|
||||
RespValue::Nil => {
|
||||
<span class="comment">// The SET operation was not performed because the XX condition was not verified.
|
||||
// This can happen if the session state expired between the load operation and the
|
||||
// update operation. Unlucky, to say the least. We fall back to the `save` routine
|
||||
// to ensure that the new key is unique.
|
||||
</span><span class="self">self</span>.save(session_state, ttl)
|
||||
.<span class="kw">await
|
||||
</span>.map_err(|err| <span class="kw">match </span>err {
|
||||
SaveError::Serialization(err) => UpdateError::Serialization(err),
|
||||
SaveError::Other(err) => UpdateError::Other(err),
|
||||
})
|
||||
}
|
||||
<span class="ident">RespValue::SimpleString</span>(<span class="kw">_</span>) => <span class="prelude-val">Ok</span>(<span class="ident">session_key</span>),
|
||||
<span class="ident">val</span> => <span class="prelude-val">Err</span>(<span class="ident">UpdateError::Other</span>(<span class="macro">anyhow::anyhow!</span>(
|
||||
RespValue::SimpleString(<span class="kw">_</span>) => <span class="prelude-val">Ok</span>(session_key),
|
||||
val => <span class="prelude-val">Err</span>(UpdateError::Other(<span class="macro">anyhow::anyhow!</span>(
|
||||
<span class="string">"Failed to update session state. {:?}"</span>,
|
||||
<span class="ident">val</span>
|
||||
val
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">update_ttl</span>(<span class="kw-2">&</span><span class="self">self</span>, <span class="ident">session_key</span>: <span class="kw-2">&</span><span class="ident">SessionKey</span>, <span class="ident">ttl</span>: <span class="kw-2">&</span><span class="ident">Duration</span>) -> <span class="prelude-ty">Result</span><span class="op"><</span>(), <span class="ident">Error</span><span class="op">></span> {
|
||||
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
|
||||
<span class="kw">async fn </span>update_ttl(<span class="kw-2">&</span><span class="self">self</span>, session_key: <span class="kw-2">&</span>SessionKey, ttl: <span class="kw-2">&</span>Duration) -> <span class="prelude-ty">Result</span><(), Error> {
|
||||
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
|
||||
|
||||
<span class="kw">let</span> <span class="ident">cmd</span> <span class="op">=</span> <span class="ident">Command</span>(<span class="macro">resp_array!</span>[
|
||||
<span class="kw">let </span>cmd = Command(<span class="macro">resp_array!</span>[
|
||||
<span class="string">"EXPIRE"</span>,
|
||||
<span class="ident">cache_key</span>,
|
||||
<span class="ident">ttl</span>.<span class="ident">whole_seconds</span>().<span class="ident">to_string</span>()
|
||||
cache_key,
|
||||
ttl.whole_seconds().to_string()
|
||||
]);
|
||||
|
||||
<span class="kw">match</span> <span class="self">self</span>.<span class="ident">addr</span>.<span class="ident">send</span>(<span class="ident">cmd</span>).<span class="kw">await</span><span class="question-mark">?</span> {
|
||||
<span class="prelude-val">Ok</span>(<span class="ident">RespValue::Integer</span>(<span class="kw">_</span>)) => <span class="prelude-val">Ok</span>(()),
|
||||
<span class="ident">val</span> => <span class="prelude-val">Err</span>(<span class="macro">anyhow::anyhow!</span>(
|
||||
<span class="kw">match </span><span class="self">self</span>.addr.send(cmd).<span class="kw">await</span><span class="question-mark">? </span>{
|
||||
<span class="prelude-val">Ok</span>(RespValue::Integer(<span class="kw">_</span>)) => <span class="prelude-val">Ok</span>(()),
|
||||
val => <span class="prelude-val">Err</span>(<span class="macro">anyhow::anyhow!</span>(
|
||||
<span class="string">"Failed to update the session state TTL: {:?}"</span>,
|
||||
<span class="ident">val</span>
|
||||
val
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">delete</span>(<span class="kw-2">&</span><span class="self">self</span>, <span class="ident">session_key</span>: <span class="kw-2">&</span><span class="ident">SessionKey</span>) -> <span class="prelude-ty">Result</span><span class="op"><</span>(), <span class="ident">anyhow::Error</span><span class="op">></span> {
|
||||
<span class="kw">let</span> <span class="ident">cache_key</span> <span class="op">=</span> (<span class="self">self</span>.<span class="ident">configuration</span>.<span class="ident">cache_keygen</span>)(<span class="ident">session_key</span>.<span class="ident">as_ref</span>());
|
||||
<span class="kw">async fn </span>delete(<span class="kw-2">&</span><span class="self">self</span>, session_key: <span class="kw-2">&</span>SessionKey) -> <span class="prelude-ty">Result</span><(), anyhow::Error> {
|
||||
<span class="kw">let </span>cache_key = (<span class="self">self</span>.configuration.cache_keygen)(session_key.as_ref());
|
||||
|
||||
<span class="kw">let</span> <span class="ident">res</span> <span class="op">=</span> <span class="self">self</span>
|
||||
.<span class="ident">addr</span>
|
||||
.<span class="ident">send</span>(<span class="ident">Command</span>(<span class="macro">resp_array!</span>[<span class="string">"DEL"</span>, <span class="ident">cache_key</span>]))
|
||||
<span class="kw">let </span>res = <span class="self">self
|
||||
</span>.addr
|
||||
.send(Command(<span class="macro">resp_array!</span>[<span class="string">"DEL"</span>, cache_key]))
|
||||
.<span class="kw">await</span><span class="question-mark">?</span>;
|
||||
|
||||
<span class="kw">match</span> <span class="ident">res</span> {
|
||||
<span class="comment">// Redis returns the number of deleted records</span>
|
||||
<span class="prelude-val">Ok</span>(<span class="ident">RespValue::Integer</span>(<span class="kw">_</span>)) => <span class="prelude-val">Ok</span>(()),
|
||||
<span class="ident">val</span> => <span class="prelude-val">Err</span>(<span class="macro">anyhow::anyhow!</span>(
|
||||
<span class="kw">match </span>res {
|
||||
<span class="comment">// Redis returns the number of deleted records
|
||||
</span><span class="prelude-val">Ok</span>(RespValue::Integer(<span class="kw">_</span>)) => <span class="prelude-val">Ok</span>(()),
|
||||
val => <span class="prelude-val">Err</span>(<span class="macro">anyhow::anyhow!</span>(
|
||||
<span class="string">"Failed to remove session from cache. {:?}"</span>,
|
||||
<span class="ident">val</span>
|
||||
val
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<span class="attribute">#[<span class="ident">cfg</span>(<span class="ident">test</span>)]</span>
|
||||
<span class="kw">mod</span> <span class="ident">tests</span> {
|
||||
<span class="kw">use</span> <span class="ident">std::collections::HashMap</span>;
|
||||
<span class="attribute">#[cfg(test)]
|
||||
</span><span class="kw">mod </span>tests {
|
||||
<span class="kw">use </span>std::collections::HashMap;
|
||||
|
||||
<span class="kw">use</span> <span class="ident">actix_web::cookie::time::Duration</span>;
|
||||
<span class="kw">use </span>actix_web::cookie::time::Duration;
|
||||
|
||||
<span class="kw">use</span> <span class="kw">super</span>::<span class="kw-2">*</span>;
|
||||
<span class="kw">use</span> <span class="ident"><span class="kw">crate</span>::test_helpers::acceptance_test_suite</span>;
|
||||
<span class="kw">use super</span>::<span class="kw-2">*</span>;
|
||||
<span class="kw">use </span><span class="kw">crate</span>::test_helpers::acceptance_test_suite;
|
||||
|
||||
<span class="kw">fn</span> <span class="ident">redis_actor_store</span>() -> <span class="ident">RedisActorSessionStore</span> {
|
||||
<span class="ident">RedisActorSessionStore::new</span>(<span class="string">"127.0.0.1:6379"</span>)
|
||||
<span class="kw">fn </span>redis_actor_store() -> RedisActorSessionStore {
|
||||
RedisActorSessionStore::new(<span class="string">"127.0.0.1:6379"</span>)
|
||||
}
|
||||
|
||||
<span class="attribute">#[<span class="ident">actix_web::test</span>]</span>
|
||||
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">test_session_workflow</span>() {
|
||||
<span class="ident">acceptance_test_suite</span>(<span class="ident">redis_actor_store</span>, <span class="bool-val">true</span>).<span class="kw">await</span>;
|
||||
<span class="attribute">#[actix_web::test]
|
||||
</span><span class="kw">async fn </span>test_session_workflow() {
|
||||
acceptance_test_suite(redis_actor_store, <span class="bool-val">true</span>).<span class="kw">await</span>;
|
||||
}
|
||||
|
||||
<span class="attribute">#[<span class="ident">actix_web::test</span>]</span>
|
||||
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">loading_a_missing_session_returns_none</span>() {
|
||||
<span class="kw">let</span> <span class="ident">store</span> <span class="op">=</span> <span class="ident">redis_actor_store</span>();
|
||||
<span class="kw">let</span> <span class="ident">session_key</span> <span class="op">=</span> <span class="ident">generate_session_key</span>();
|
||||
<span class="macro">assert!</span>(<span class="ident">store</span>.<span class="ident">load</span>(<span class="kw-2">&</span><span class="ident">session_key</span>).<span class="kw">await</span>.<span class="ident">unwrap</span>().<span class="ident">is_none</span>());
|
||||
<span class="attribute">#[actix_web::test]
|
||||
</span><span class="kw">async fn </span>loading_a_missing_session_returns_none() {
|
||||
<span class="kw">let </span>store = redis_actor_store();
|
||||
<span class="kw">let </span>session_key = generate_session_key();
|
||||
<span class="macro">assert!</span>(store.load(<span class="kw-2">&</span>session_key).<span class="kw">await</span>.unwrap().is_none());
|
||||
}
|
||||
|
||||
<span class="attribute">#[<span class="ident">actix_web::test</span>]</span>
|
||||
<span class="kw">async</span> <span class="kw">fn</span> <span class="ident">updating_of_an_expired_state_is_handled_gracefully</span>() {
|
||||
<span class="kw">let</span> <span class="ident">store</span> <span class="op">=</span> <span class="ident">redis_actor_store</span>();
|
||||
<span class="kw">let</span> <span class="ident">session_key</span> <span class="op">=</span> <span class="ident">generate_session_key</span>();
|
||||
<span class="kw">let</span> <span class="ident">initial_session_key</span> <span class="op">=</span> <span class="ident">session_key</span>.<span class="ident">as_ref</span>().<span class="ident">to_owned</span>();
|
||||
<span class="kw">let</span> <span class="ident">updated_session_key</span> <span class="op">=</span> <span class="ident">store</span>
|
||||
.<span class="ident">update</span>(<span class="ident">session_key</span>, <span class="ident">HashMap::new</span>(), <span class="kw-2">&</span><span class="ident">Duration::seconds</span>(<span class="number">1</span>))
|
||||
.<span class="kw">await</span>
|
||||
.<span class="ident">unwrap</span>();
|
||||
<span class="macro">assert_ne!</span>(<span class="ident">initial_session_key</span>, <span class="ident">updated_session_key</span>.<span class="ident">as_ref</span>());
|
||||
<span class="attribute">#[actix_web::test]
|
||||
</span><span class="kw">async fn </span>updating_of_an_expired_state_is_handled_gracefully() {
|
||||
<span class="kw">let </span>store = redis_actor_store();
|
||||
<span class="kw">let </span>session_key = generate_session_key();
|
||||
<span class="kw">let </span>initial_session_key = session_key.as_ref().to_owned();
|
||||
<span class="kw">let </span>updated_session_key = store
|
||||
.update(session_key, HashMap::new(), <span class="kw-2">&</span>Duration::seconds(<span class="number">1</span>))
|
||||
.<span class="kw">await
|
||||
</span>.unwrap();
|
||||
<span class="macro">assert_ne!</span>(initial_session_key, updated_session_key.as_ref());
|
||||
}
|
||||
}
|
||||
</code></pre></div>
|
||||
</section></div></main><div id="rustdoc-vars" data-root-path="../../../" data-current-crate="actix_session" data-themes="ayu,dark,light" data-resource-suffix="" data-rustdoc-version="1.65.0-nightly (34a6cae28 2022-08-09)" ></div></body></html>
|
||||
</section></div></main><div id="rustdoc-vars" data-root-path="../../../" data-current-crate="actix_session" data-themes="ayu,dark,light" data-resource-suffix="" data-rustdoc-version="1.65.0-nightly (060e47f74 2022-08-23)" ></div></body></html>
|
Reference in New Issue
Block a user