tower-sessions-sqlx-store-chrono

🚃 Previously bundled session stores for `tower-sessions`. This crate is created for `sqlx` and `chrono` instead of `time`.

## 🎨 Overview - **Use the pool**: create a session store by providing your SQLx connection pool. - **Compact encoding**: session data is stored in the database using [MessagePack](https://github.com/3Hren/msgpack-rust), a compact self-describing serialization format. - **Custom schema and table names**: use the default of `tower_sessions.session` or set your own schema and table names; except for SQLite, in which you can define just the table name. - **Migrations**: the database table creation is handled by the `migrate` function on the store, which does just that. Remember to run it to ensure the session table exist. The creation commands use `if not exists` to avoid errors if you have already created the table you need. ## 🤸 Usage ### MySQL example ```rust use std::net::SocketAddr; use axum::{response::IntoResponse, routing::get, Router}; use serde::{Deserialize, Serialize}; use time::Duration; use tokio::{signal, task::AbortHandle}; use tower_sessions::{session_store::ExpiredDeletion, Expiry, Session, SessionManagerLayer}; use tower_sessions_sqlx_store_chrono::{sqlx::MySqlPool, MySqlStore}; const COUNTER_KEY: &str = "counter"; #[derive(Serialize, Deserialize, Default)] struct Counter(usize); async fn handler(session: Session) -> impl IntoResponse { let counter: Counter = session.get(COUNTER_KEY).await.unwrap().unwrap_or_default(); session.insert(COUNTER_KEY, counter.0 + 1).await.unwrap(); format!("Current count: {}", counter.0) } #[tokio::main] async fn main() -> Result<(), Box> { let database_url = std::option_env!("DATABASE_URL").expect("Missing DATABASE_URL."); let pool = MySqlPool::connect(database_url).await?; let session_store = MySqlStore::new(pool); session_store.migrate().await?; let deletion_task = tokio::task::spawn( session_store .clone() .continuously_delete_expired(tokio::time::Duration::from_secs(60)), ); let session_layer = SessionManagerLayer::new(session_store) .with_secure(false) .with_expiry(Expiry::OnInactivity(Duration::seconds(10))); let app = Router::new().route("/", get(handler)).layer(session_layer); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = tokio::net::TcpListener::bind(&addr).await?; // Ensure we use a shutdown signal to abort the deletion task. axum::serve(listener, app.into_make_service()) .with_graceful_shutdown(shutdown_signal(deletion_task.abort_handle())) .await?; deletion_task.await??; Ok(()) } async fn shutdown_signal(deletion_task_abort_handle: AbortHandle) { let ctrl_c = async { signal::ctrl_c() .await .expect("failed to install Ctrl+C handler"); }; #[cfg(unix)] let terminate = async { signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("failed to install signal handler") .recv() .await; }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => { deletion_task_abort_handle.abort() }, _ = terminate => { deletion_task_abort_handle.abort() }, } } ``` ### Postgres example ```rust use std::net::SocketAddr; use axum::{response::IntoResponse, routing::get, Router}; use serde::{Deserialize, Serialize}; use time::Duration; use tokio::{signal, task::AbortHandle}; use tower_sessions::{session_store::ExpiredDeletion, Expiry, Session, SessionManagerLayer}; use tower_sessions_sqlx_store_chrono::{sqlx::PgPool, PostgresStore}; const COUNTER_KEY: &str = "counter"; #[derive(Serialize, Deserialize, Default)] struct Counter(usize); async fn handler(session: Session) -> impl IntoResponse { let counter: Counter = session.get(COUNTER_KEY).await.unwrap().unwrap_or_default(); session.insert(COUNTER_KEY, counter.0 + 1).await.unwrap(); format!("Current count: {}", counter.0) } #[tokio::main] async fn main() -> Result<(), Box> { let database_url = std::option_env!("DATABASE_URL").expect("Missing DATABASE_URL."); let pool = PgPool::connect(database_url).await?; let session_store = PostgresStore::new(pool); session_store.migrate().await?; let deletion_task = tokio::task::spawn( session_store .clone() .continuously_delete_expired(tokio::time::Duration::from_secs(60)), ); let session_layer = SessionManagerLayer::new(session_store) .with_secure(false) .with_expiry(Expiry::OnInactivity(Duration::seconds(10))); let app = Router::new().route("/", get(handler)).layer(session_layer); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = tokio::net::TcpListener::bind(&addr).await?; // Ensure we use a shutdown signal to abort the deletion task. axum::serve(listener, app.into_make_service()) .with_graceful_shutdown(shutdown_signal(deletion_task.abort_handle())) .await?; deletion_task.await??; Ok(()) } async fn shutdown_signal(deletion_task_abort_handle: AbortHandle) { let ctrl_c = async { signal::ctrl_c() .await .expect("failed to install Ctrl+C handler"); }; #[cfg(unix)] let terminate = async { signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("failed to install signal handler") .recv() .await; }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => { deletion_task_abort_handle.abort() }, _ = terminate => { deletion_task_abort_handle.abort() }, } } ``` ### SQLite example ```rust use std::net::SocketAddr; use axum::{response::IntoResponse, routing::get, Router}; use serde::{Deserialize, Serialize}; use time::Duration; use tokio::{signal, task::AbortHandle}; use tower_sessions::{session_store::ExpiredDeletion, Expiry, Session, SessionManagerLayer}; use tower_sessions_sqlx_store_chrono::{sqlx::SqlitePool, SqliteStore}; const COUNTER_KEY: &str = "counter"; #[derive(Serialize, Deserialize, Default)] struct Counter(usize); async fn handler(session: Session) -> impl IntoResponse { let counter: Counter = session.get(COUNTER_KEY).await.unwrap().unwrap_or_default(); session.insert(COUNTER_KEY, counter.0 + 1).await.unwrap(); format!("Current count: {}", counter.0) } #[tokio::main] async fn main() -> Result<(), Box> { let pool = SqlitePool::connect("sqlite::memory:").await?; let session_store = SqliteStore::new(pool); session_store.migrate().await?; let deletion_task = tokio::task::spawn( session_store .clone() .continuously_delete_expired(tokio::time::Duration::from_secs(60)), ); let session_layer = SessionManagerLayer::new(session_store) .with_secure(false) .with_expiry(Expiry::OnInactivity(Duration::seconds(10))); let app = Router::new().route("/", get(handler)).layer(session_layer); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = tokio::net::TcpListener::bind(&addr).await?; // Ensure we use a shutdown signal to abort the deletion task. axum::serve(listener, app.into_make_service()) .with_graceful_shutdown(shutdown_signal(deletion_task.abort_handle())) .await?; deletion_task.await??; Ok(()) } async fn shutdown_signal(deletion_task_abort_handle: AbortHandle) { let ctrl_c = async { signal::ctrl_c() .await .expect("failed to install Ctrl+C handler"); }; #[cfg(unix)] let terminate = async { signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("failed to install signal handler") .recv() .await; }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => { deletion_task_abort_handle.abort() }, _ = terminate => { deletion_task_abort_handle.abort() }, } } ```