You are reading the docs for rws 18.5 The latest release is 18.8. Read this page for the latest release.

Sessions

Server-side session management with in-memory, database-backed, and Redis-backed session stores.

rust-web-server ships three session store implementations in src/session/mod.rs:

Store Persistence Scales horizontally
SessionStore In-process HashMap; lost on restart No
DbSessionStore Relational database via model layer Yes (shared DB)
RedisSessionStore Redis server Yes

All three expose the same conceptual API: create → set data → save → load → destroy. SessionStore methods return values directly; DbSessionStore and RedisSessionStore return Result because they perform I/O.

SessionStore (in-memory)

Sessions are stored in an in-memory, thread-safe map with automatic TTL-based expiry. The store is designed to live inside your application state so every handler shares the same session map without any global state.

Session store

Creating a store

use rust_web_server::session::SessionStore;

// Sessions expire after 3600 seconds (1 hour).
let store = SessionStore::new(3600);

SessionStore is cheaply Clone-able — all clones share the same backing map via Arc. Place one instance in your application state.

Creating a session

// Allocates a new session with a generated ID and inserts it into the store.
let mut session = store.create();

For public-facing services that need cryptographically unpredictable IDs, supply your own ID instead:

let id = my_csprng_hex_id(); // e.g. from `ring` or `getrandom`
let mut session = store.create_with_id(id);
ID generation

The default create() uses a non-cryptographic splitmix64 finalizer seeded from the system clock and an atomic counter. It is sufficient for most internal applications. Replace it with a CSPRNG for public deployments.

Reading and writing session data

Session holds key/value pairs as strings. Mutate the session locally, then call save to persist changes.

session.set("user_id", "42");
session.set("role", "admin");

let user_id: Option<&str> = session.get("user_id"); // Some("42")
let missing: Option<&str> = session.get("absent");  // None

session.remove("role");
let has_role: bool = session.contains("role"); // false

// Persist changes back to the store.
store.save(&session);

Loading a session

// Returns None if the session is unknown or expired.
let session = store.load(&session_id);

Destroying a session

store.destroy(&session_id);

Call destroy_cookie to also clear the browser cookie (see below).

Purging expired sessions

Sessions are not removed automatically when they expire — they are simply invisible to load. To reclaim memory, call purge_expired() periodically:

store.purge_expired(); // removes all sessions whose TTL has elapsed

Use the Scheduler to run this automatically:

use std::time::Duration;
use rust_web_server::scheduler::Scheduler;

let store_clone = store.clone();
Scheduler::new()
    .every(Duration::from_secs(3600), move || store_clone.purge_expired())
    .start();

Three free functions bridge the session store and the HTTP cookie layer.

session_id_from_request

Extracts the session ID from a named cookie in a request's Cookie header.

use rust_web_server::session;

let sid: Option<String> = session::session_id_from_request(&request, "sid");

Returns None if the Cookie header is absent or the named cookie is missing.

Builds a Set-Cookie header value with HttpOnly, SameSite=Lax, Path=/, and Max-Age.

let cookie_value = session::session_cookie(&session.id, "sid", 3600);
response.headers.push(Header::new("Set-Cookie", cookie_value));

Clears the browser cookie by setting Max-Age=0.

let cookie_value = session::destroy_cookie("sid");
response.headers.push(Header::new("Set-Cookie", cookie_value));

Complete login/profile example

use rust_web_server::app::App;
use rust_web_server::core::New;
use rust_web_server::session::{self, SessionStore};
use rust_web_server::header::Header;
use rust_web_server::response::{Response, STATUS_CODE_REASON_PHRASE};

struct State { sessions: SessionStore }

let app = App::with_state(State { sessions: SessionStore::new(3600) })
    .post("/login", |req, _params, _conn, state| {
        // Verify credentials here ...
        let mut sess = state.sessions.create();
        sess.set("user_id", "42");
        state.sessions.save(&sess);

        let mut r = Response::new();
        r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
        r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
        r.headers.push(Header::new("Set-Cookie", session::session_cookie(&sess.id, "sid", 3600)));
        r
    })
    .get("/profile", |req, _params, _conn, state| {
        let mut r = Response::new();

        let sid = match session::session_id_from_request(&req, "sid") {
            Some(id) => id,
            None => {
                r.status_code = *STATUS_CODE_REASON_PHRASE.n401_unauthorized.status_code;
                r.reason_phrase = STATUS_CODE_REASON_PHRASE.n401_unauthorized.reason_phrase.to_string();
                return r;
            }
        };
        let sess = match state.sessions.load(&sid) {
            Some(s) => s,
            None => {
                r.status_code = *STATUS_CODE_REASON_PHRASE.n401_unauthorized.status_code;
                r.reason_phrase = STATUS_CODE_REASON_PHRASE.n401_unauthorized.reason_phrase.to_string();
                return r;
            }
        };

        let _user_id = sess.get("user_id").unwrap_or("guest");
        r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
        r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
        r
    })
    .get("/logout", |req, _params, _conn, state| {
        let mut r = Response::new();
        if let Some(sid) = session::session_id_from_request(&req, "sid") {
            state.sessions.destroy(&sid);
        }
        r.status_code = *STATUS_CODE_REASON_PHRASE.n200_ok.status_code;
        r.reason_phrase = STATUS_CODE_REASON_PHRASE.n200_ok.reason_phrase.to_string();
        r.headers.push(Header::new("Set-Cookie", session::destroy_cookie("sid")));
        r
    });

Store size

let count = store.len();       // total entries, including expired-but-not-purged
let empty = store.is_empty();  // true when no entries exist

DbSessionStore (database-backed)

DbSessionStore stores sessions in a rws_sessions table managed by the model layer. Sessions survive process restarts and are visible across multiple instances that share the same database.

Setup

Requires the model-sqlite, model-postgres, or model-mysql feature. The table is created automatically on the first DbSessionStore::new call.

[dependencies]
rust-web-server = { version = "18", features = ["model-sqlite"] }

Basic usage

use rust_web_server::model::DbPool;
use rust_web_server::session::DbSessionStore;

// Use a file or network database for real persistence;
// :memory: is shown here for illustration.
let pool = DbPool::memory().await.unwrap();
let store = DbSessionStore::new(pool, 3600).await.unwrap();

// All methods are async and return Result<_, DbError>
let mut sess = store.create().await.unwrap();
sess.set("user_id", "42");
store.save(&sess).await.unwrap();

let loaded = store.load(&sess.id).await.unwrap().unwrap();
assert_eq!(Some("42"), loaded.get("user_id"));

store.destroy(&sess.id).await.unwrap();

Purging expired sessions

Unlike SessionStore, expired rows remain in the database until explicitly deleted:

store.purge_expired().await.unwrap(); // DELETE WHERE expires_at <= now

Schedule this with Scheduler:

use std::time::Duration;
use rust_web_server::scheduler::Scheduler;

let store_clone = store.clone();
let rt = tokio::runtime::Handle::current();
Scheduler::new()
    .every(Duration::from_secs(3600), move || {
        let store = store_clone.clone();
        rt.spawn(async move { let _ = store.purge_expired().await; });
    })
    .start();

Schema

CREATE TABLE IF NOT EXISTS rws_sessions (
    id         TEXT    PRIMARY KEY,
    data       TEXT    NOT NULL DEFAULT '',
    expires_at INTEGER NOT NULL
);

Session data is serialized as a URL-encoded string (key1=val1&key2=val2).

RedisSessionStore

RedisSessionStore stores sessions in Redis using a hand-rolled RESP v2 client — no external crate required. Sessions are keyed as rws:sess:{id} and given a Redis TTL via SET … EX, so they expire automatically without any purge_expired sweep.

Basic usage

use rust_web_server::session::RedisSessionStore;

// Connect to Redis at host:port; pass Some("password") for AUTH
let store = RedisSessionStore::new("127.0.0.1:6379", None, 3600);

let mut sess = store.create().unwrap();
sess.set("role", "admin");
store.save(&sess).unwrap();    // SET rws:sess:{id} "role=admin" EX 3600

let loaded = store.load(&sess.id).unwrap().unwrap();
assert_eq!(Some("admin"), loaded.get("role"));

store.destroy(&sess.id).unwrap(); // DEL rws:sess:{id}

Environment-variable configuration

use rust_web_server::session::RedisSessionStore;

// Reads RWS_REDIS_HOST (default 127.0.0.1), RWS_REDIS_PORT (default 6379),
// RWS_REDIS_PASSWORD (optional), RWS_REDIS_TTL_SECS (default 3600)
let store = RedisSessionStore::from_env();

Connection behaviour

The store maintains one persistent TCP connection per RedisSessionStore instance. All clones share the same connection via Arc. The connection is established lazily on the first command and reconnects automatically if the socket is dropped.

Thread safety

The underlying TCP connection is protected by a Mutex. For high-concurrency workloads consider creating a pool of RedisSessionStore instances, each holding its own connection.

Edit this page on GitHub