Skip to content

Conversation

bbogdan95
Copy link

PR Type

Feature

PR Checklist

  • Tests for the changes have been added / updated.
  • Documentation comments have been added / updated.
  • A changelog entry has been made for the appropriate packages.
  • Format code with the nightly rustfmt (cargo +nightly fmt).

Overview

Implemented Sqlite session backend. The database/table will get created automatically if they don't exist.

Default garbage collection of stale sessions happens through a database trigger. (it can be turned off and the trigger will be dropped, and garbage collection can be implemented outside if needed.)

@bbogdan95
Copy link
Author

I fixed the doctest errors in sqlite.rs (SqliteSessionStore), but I still have these errors locally and I'm not sure what am I missing:

running 11 tests
test src/lib.rs - (line 70) - compile ... ok
test src/lib.rs - (line 41) - compile ... FAILED
test src/middleware.rs - middleware::SessionMiddleware (line 48) - compile ... FAILED
test src/middleware.rs - middleware::SessionMiddleware (line 81) - compile ... FAILED
test src/storage/cookie.rs - storage::cookie::CookieSessionStore (line 14) - compile ... ok
test src/storage/sqlite.rs - storage::sqlite::SqliteSessionStore (line 17) - compile ... ok
test src/session.rs - session::Session (line 265) ... ok
test src/config.rs - config::SessionMiddlewareBuilder<Store>::session_lifecycle (line 252) ... ok
test src/config.rs - config::PersistentSession (line 102) ... ok
test src/session.rs - session::Session (line 25) ... ok
test src/storage/session_key.rs - storage::session_key::SessionKey (line 12) ... ok

failures:

---- src/lib.rs - (line 41) stdout ----
error[E0432]: unresolved import `actix_session::storage::RedisActorSessionStore`
 --> src/lib.rs:43:49
  |
4 | use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore};
  |                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `RedisActorSessionStore` in `storage`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0432`.
Couldn't compile the test.
---- src/middleware.rs - middleware::SessionMiddleware (line 48) stdout ----
error[E0432]: unresolved import `actix_session::storage::RedisActorSessionStore`
 --> src/middleware.rs:50:49
  |
4 | use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore};
  |                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `RedisActorSessionStore` in `storage`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0432`.
Couldn't compile the test.
---- src/middleware.rs - middleware::SessionMiddleware (line 81) stdout ----
error[E0432]: unresolved import `actix_session::storage::RedisActorSessionStore`
 --> src/middleware.rs:83:49
  |
4 | use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore};
  |                                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `RedisActorSessionStore` in `storage`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0432`.
Couldn't compile the test.

failures:
    src/lib.rs - (line 41)
    src/middleware.rs - middleware::SessionMiddleware (line 48)
    src/middleware.rs - middleware::SessionMiddleware (line 81)

test result: FAILED. 8 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.53s

Any suggestions please ?


[dev-dependencies]
actix-session = { path = ".", features = ["cookie-session", "redis-actor-session", "redis-rs-session"] }
actix-session = { path = ".", features = ["cookie-session", "sqlite-session"] }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
actix-session = { path = ".", features = ["cookie-session", "sqlite-session"] }
actix-session = { path = ".", features = ["cookie-session", "redis-actor-session", "sqlite-session"] }

We should keep enabling all features for the test suite, that's where your error is coming from.

Comment on lines -60 to +66
required-features = ["redis-actor-session"]
required-features = ["sqlite-session"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add a new example for sqlite instead of modifying the existing one for Redis.

/// executes before every insert on the `sessions` table.
pub fn new(
pool: Pool<SqliteConnectionManager>,
garbage_collect: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's almost never a good idea to use a boolean in a public API - let's add an enum with two variants using self-describing names.

Comment on lines +93 to +125
conn.execute(
"CREATE TABLE IF NOT EXISTS sessions (
id INTEGER NOT NULL,
session_key TEXT NOT NULL,
session_state TEXT NOT NULL,
expiry INTEGER NOT NULL,
PRIMARY KEY(id AUTOINCREMENT)
)",
[],
)
.map_err(Into::into)
.map_err(LoadError::Other)?;

// in order to garbage collect stale sessions, we will use a trigger
if garbage_collect {
conn.execute(
"
CREATE TRIGGER IF NOT EXISTS garbage_collect
BEFORE INSERT
ON sessions
BEGIN
DELETE FROM sessions WHERE expiry < STRFTIME('%s');
END;
",
[],
)
.map_err(Into::into)
.map_err(LoadError::Other)?;
} else {
conn.execute("DROP TRIGGER IF EXISTS garbage_collect", [])
.map_err(Into::into)
.map_err(LoadError::Other)?;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a one-off setup action that should not be executed every single time somebody tries to use Sqlite as a session backend in their applications.
It should live in a separate routine, new should assume that the initialization has already taken place.

async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>, LoadError> {
let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

let conn = self.pool.get().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't want to unwrap here.

session_key TEXT NOT NULL,
session_state TEXT NOT NULL,
expiry INTEGER NOT NULL,
PRIMARY KEY(id AUTOINCREMENT)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows for multiple rows sharing the same session_key - that's not what we want. I'd suggest using the session_key as primary key or, at the very least, add a UNIQUE constraint on it.

.unwrap()
.unix_timestamp();

let conn = self.pool.get().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not want to unwrap here.

Comment on lines +194 to +197
let mut stmt = match statement {
Ok(v) => v,
Err(e) => return Err(SaveError::Other(anyhow::anyhow!(e))),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let mut stmt = match statement {
Ok(v) => v,
Err(e) => return Err(SaveError::Other(anyhow::anyhow!(e))),
};
let mut stmt = statement.map_err(Into::into).map_err(SaveError::Other)?;

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

let conn = self.pool.get().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not want to unwrap here.

@LukeMathWalker
Copy link
Contributor

Left a bunch of comments - sorry for the long delay!

Another issue that needs to be addressed: rustqlite offers a sync API, while we are running in an async context. We'll need to spawn the queries on the blocking task pool to avoid blocking the runtime.

@jayvdb
Copy link
Contributor

jayvdb commented Feb 14, 2024

@bbogdan95 , will you be able to complete this?

@robjtede robjtede added A-session Project: actix-session B-semver-minor labels Jun 9, 2024
@robjtede robjtede changed the title Feature: Add Sqlite session backend feature(session): Add Sqlite session backend Jun 9, 2024
@robjtede robjtede changed the title feature(session): Add Sqlite session backend session: Add Sqlite session backend Jun 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-session Project: actix-session B-semver-minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants