16 releases (9 breaking)
0.14.2 | Oct 30, 2024 |
---|---|
0.13.0 | Jul 23, 2024 |
0.12.0 | Mar 20, 2024 |
0.8.2 | Dec 24, 2023 |
0.7.0 | Nov 27, 2023 |
#212 in Network programming
6,686 downloads per month
Used in axum-login
37KB
602 lines
tower-sessions-sqlx-store
SQLx session stores for `tower-sessions`.
🎨 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, 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 useif not exists
to avoid errors if you have already created the table you need.
🤸 Usage
MySQL example
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::{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<dyn std::error::Error>> {
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
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::{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<dyn std::error::Error>> {
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
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::{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<dyn std::error::Error>> {
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() },
}
}
Dependencies
~39–53MB
~1M SLoC