Rust会话管理库tower-sessions-dynamodb-store的使用:基于DynamoDB的高性能会话存储解决方案

Rust会话管理库tower-sessions-dynamodb-store的使用:基于DynamoDB的高性能会话存储解决方案

概述

这个crate为tower-sessions会话管理库提供了AWS DynamoDB后端支持,使用Rust AWS-SDK实现。

使用方法

设置DynamoDBStore需要两个参数:

  1. 一个aws_sdk_dynamodb::Client实例,用于连接AWS DynamoDB服务
  2. 一个DynamoDBStoreProps实例,告诉会话存储在DynamoDB中查找/保存数据的位置

aws_sdk_dynamodb::Client

设置aws_sdk_dynamodb::Client有多种方式,但都需要一个aws_config::Config实例。以下是获取aws_sdk_dynamodb::Client实例的基本示例:

use aws_config;
use aws_sdk_dynamodb;
let config = aws_config::load_defaults(aws_config::BehaviorVersion::v2023_11_09()).await;
let client = aws_sdk_dynamodb::Client::new(&config);

DynamoDBStoreProps

DynamoDBStoreProps实现了Default trait,因此可以使用DynamoDBStoreProps::default()获取一个最小实例;但更可能需要覆盖部分设置:

let store_props = DynamoDBStoreProps {
    table_name: "MyTowerSessions".to_string(),
    sort_key: Some(DynamoDBStoreKey {
        name: "sort_key".to_string(),
        prefix: Some("TOWER_SESSIONS::".to_string()),
        suffix: None,
    }),
    ..Default::default()
};

这个配置将:

  • 使用名为MyTowerSession的DynamoDB表
  • 使用默认的partition_key名称session_id,并在所有tower会话ID前添加SESSIONS::TOWER::前缀
  • 使用定义的sort_key属性名sort_key,并在会话ID前添加TOWER_SESSIONS::前缀
  • 使用默认的expirey_name属性名expire_at存储会话过期时间戳
  • 使用默认的data_name属性名data存储会话数据的二进制序列化

DynamoDBStore

有了aws_sdk_dynamodb::ClientDynamoDBStoreProps实例后,可以配置DynamoDBStore并传递给tower-sessions会话管理器:

use axum::{response::IntoResponse, routing::get, Router};
use time::Duration;
use tower_sessions::{Expiry, SessionManagerLayer};
use tower_sessions_dynamodb_store::DynamoDBStore;

let session_store = DynamoDBStore::new(client, store_props);
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);

完整示例

以下是一个完整的Axum应用程序示例,展示了如何使用tower-sessions-dynamodb-store:

use axum::{
    extract::State,
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Router,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use time::Duration;
use tower_sessions::{Expiry, Session, SessionManagerLayer};
use tower_sessions_dynamodb_store::DynamoDBStore;

// 应用程序状态
#[derive(Clone)]
struct AppState {
    dynamodb_client: aws_sdk_dynamodb::Client,
}

// 会话数据结构
#[derive(Serialize, Deserialize, Default)]
struct SessionData {
    count: usize,
}

// 计数器处理函数
async fn counter_handler(session: Session) -> impl IntoResponse {
    let count: usize = session.get("count").await.unwrap().unwrap_or(0);
    session.insert("count", count + 1).await.unwrap();
    format!("当前计数: {count}")
}

#[tokio::main]
async fn main() {
    // 初始化AWS配置
    let config = aws_config::load_defaults(aws_config::BehaviorVersion::v2023_11_09()).await;
    let dynamodb_client = aws_sdk_dynamodb::Client::new(&config);

    // 创建DynamoDB存储配置
    let store_props = DynamoDBStoreProps {
        table_name: "MyTowerSessions".to_string(),
        ..Default::default()
    };

    // 创建会话存储
    let session_store = DynamoDBStore::new(dynamodb_client.clone(), store_props);
    
    // 配置会话管理器
    let session_layer = SessionManagerLayer::new(session_store)
        .with_secure(false) // 仅用于开发环境
        .with_expiry(Expiry::OnInactivity(Duration::seconds(86400))); // 24小时不活动后过期

    // 创建应用程序状态
    let state = AppState { dynamodb_client };

    // 创建路由
    let app = Router::new()
        .route("/", get(counter_handler))
        .layer(session_layer)
        .with_state(state);

    // 启动服务器
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

构建与测试

格式化代码:

cargo fmt --all --check

运行Clippy:

cargo clippy --workspace --all-targets --all-features -- -D warnings

生成文档:

RUSTDOCFLAGS="-D rustdoc::broken-intra-doc-links" cargo doc --all-features --no-deps

这个库提供了基于DynamoDB的高性能会话存储解决方案,适合需要分布式会话管理的Web应用程序。

完整示例代码

以下是一个更完整的示例,展示了用户登录会话管理的实现:

use axum::{
    extract::{Form, State},
    http::{header, HeaderMap, StatusCode},
    response::{Html, IntoResponse, Redirect},
    routing::{get, post},
    Router,
};
use serde::{Deserialize, Serialize};
use time::Duration;
use tower_sessions::{Expiry, Session, SessionManagerLayer};
use tower_sessions_dynamodb_store::{DynamoDBStore, DynamoDBStoreProps};

// 用户数据结构
#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: String,
    username: String,
}

// 登录表单
#[derive(Deserialize)]
struct LoginForm {
    username: String,
    password: String,
}

// 应用程序状态
#[derive(Clone)]
struct AppState {
    dynamodb_client: aws_sdk_dynamodb::Client,
}

// 登录页面
async fn login_page() -> Html<&'static str> {
    Html(
        r#"
        <!DOCTYPE html>
        <html>
        <head>
            <title>登录</title>
        </head>
        <body>
            <form action="/login" method="post">
                <input type="text" name="username" placeholder="用户名">
                <input type="password" name="password" placeholder="密码">
                <button type="submit">登录</button>
            </form>
        </body>
        </html>
        "#,
    )
}

// 登录处理
async fn login_handler(
    session: Session,
    Form(form): Form<LoginForm>,
) -> Result<Redirect, (StatusCode, &'static str)> {
    // 这里应该验证用户名和密码
    // 为了示例,我们假设验证通过
    
    let user = User {
        id: "123".to_string(),
        username: form.username,
    };
    
    // 将用户数据存入会话
    session.insert("user", user).await.map_err(|_| {
        (StatusCode::INTERNAL_SERVER_ERROR, "无法创建会话")
    })?;
    
    Ok(Redirect::to("/dashboard"))
}

// 仪表板页面
async fn dashboard_handler(session: Session) -> impl IntoResponse {
    // 从会话中获取用户数据
    if let Some(user) = session.get::<User>("user").await.unwrap() {
        return Html(format!("欢迎回来,{}!", user.username));
    }
    
    (StatusCode::UNAUTHORIZED, "请先登录").into_response()
}

// 登出处理
async fn logout_handler(session: Session) -> Redirect {
    // 销毁会话
    session.delete();
    Redirect::to("/login")
}

#[tokio::main]
async fn main() {
    // 初始化AWS配置
    let config = aws_config::load_defaults(aws_config::BehaviorVersion::v2023_11_09()).await;
    let dynamodb_client = aws_sdk_dynamodb::Client::new(&config);

    // 创建DynamoDB存储配置
    let store_props = DynamoDBStoreProps {
        table_name: "MyTowerSessions".to_string(),
        ..Default::default()
    };

    // 创建会话存储
    let session_store = DynamoDBStore::new(dynamodb_client.clone(), store_props);
    
    // 配置会话管理器
    let session_layer = SessionManagerLayer::new(session_store)
        .with_secure(false) // 仅用于开发环境
        .with_expiry(Expiry::OnInactivity(Duration::seconds(86400))); // 24小时不活动后过期

    // 创建应用程序状态
    let state = AppState { dynamodb_client };

    // 创建路由
    let app = Router::new()
        .route("/login", get(login_page).post(login_handler))
        .route("/dashboard", get(dashboard_handler))
        .route("/logout", get(logout_handler))
        .layer(session_layer)
        .with_state(state);

    // 启动服务器
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

1 回复

Rust会话管理库tower-sessions-dynamodb-store的使用

tower-sessions-dynamodb-store是一个基于AWS DynamoDB的Rust会话存储解决方案,专为tower-sessions库设计,提供了高性能、可扩展的会话管理功能。

主要特性

  • 完全兼容tower-sessions接口
  • 利用DynamoDB的高可用性和可扩展性
  • 支持TTL(生存时间)自动过期会话
  • 异步操作,高性能设计

安装

在Cargo.toml中添加依赖:

[dependencies]
tower-sessions-dynamodb-store = "0.1"
tokio = { version = "1.0", features = ["full"] }
aws-config = "0.55"

基本使用方法

1. 初始化存储

use tower_sessions_dynamodb_store::{DynamoDbStore, DynamoDbStoreBuilder};
use aws_config::BehaviorVersion;

#[tokio::main]
async fn main() {
    // 加载AWS配置
    let aws_config = aws_config::load_defaults(BehaviorVersion::latest()).await;
    
    // 创建DynamoDB存储
    let store = DynamoDbStore::builder()
        .table_name("sessions")  // 设置表名
        .client(aws_sdk_dynamodb::Client::new(&aws_config))  // 使用AWS配置
        .build()
        .await
        .expect("Failed to create DynamoDB store");
}

2. 创建会话管理器

use tower_sessions::{Session, SessionManagerLayer};
use std::time::Duration;

let session_manager = SessionManagerLayer::new(store)
    .with_secure(true)  // 启用安全cookie
    .with_expiry(Duration::from_secs(3600));  // 设置会话过期时间为1小时

3. 在应用程序中使用

use axum::{Router, routing::get, Extension};

async fn handler(session: Session) -> String {
    // 从会话中读取数据
    let visits: usize = session.get("visits").await.unwrap().unwrap_or(0);
    
    // 更新会话数据
    session.insert("visits", visits + 1).await.unwrap();
    
    format!("You have visited this page {} times", visits + 1)
}

let app = Router::new()
    .route("/", get(handler))
    .layer(session_manager);

高级配置

自定义表结构

let store = DynamoDbStore::builder()
    .table_name("custom_sessions"
    .partition_key_name("custom_pk")  // 自定义分区键名
    .sort_key_name("custom_sk")      // 自定义排序键名
    .ttl_attribute_name("expires_at")  // 自定义TTL属性名
    .client(aws_sdk_dynamodb::Client::new(&aws_config))
    .build()
    .await?;

设置会话过期时间

use tower_sessions::cookie::time::OffsetDateTime;

async fn set_expiry(session: Session) {
    // 设置会话30分钟后过期
    let expiry = OffsetDateTime::now_utc() + time::Duration::minutes(30);
    session.set_expiry(Some(expiry)).await;
}

DynamoDB表结构要求

使用前需要创建DynamoDB表,基本要求:

  • 必须有一个分区键(默认为"pk")
  • 推荐添加一个TTL属性(默认为"expires_at")

示例创建表的AWS CLI命令:

aws dynamodb create-table \
    --table-name sessions \
    --attribute-definitions AttributeName=pk,AttributeType=S \
    --key-schema AttributeName=pk,KeyType=HASH \
    --billing-mode PAY_PER_REQUEST \
    --time-to-live-specification "Enabled=true, AttributeName=expires_at"

性能优化建议

  1. 确保会话ID在分区键上有良好的分布
  2. 根据负载调整DynamoDB的容量模式
  3. 考虑使用本地缓存减少DynamoDB读取
  4. 合理设置会话TTL以避免表无限增长

完整示例Demo

下面是一个完整的Axum web应用示例,展示了如何使用tower-sessions-dynamodb-store

use axum::{Router, routing::get, Extension};
use tower_sessions::{Session, SessionManagerLayer};
use tower_sessions_dynamodb_store::{DynamoDbStore, DynamoDbStoreBuilder};
use aws_config::BehaviorVersion;
use std::time::Duration;

#[tokio::main]
async fn main() {
    // 1. 初始化AWS配置和DynamoDB存储
    let aws_config = aws_config::load_defaults(BehaviorVersion::latest()).await;
    let dynamodb_client = aws_sdk_dynamodb::Client::new(&aws_config);
    
    let store = DynamoDbStore::builder()
        .table_name("sessions")
        .client(dynamodb_client)
        .build()
        .await
        .expect("Failed to create DynamoDB store");

    // 2. 创建会话管理器
    let session_manager = SessionManagerLayer::new(store)
        .with_secure(true)
        .with_expiry(Duration::from_secs(3600));

    // 3. 创建路由和应用
    let app = Router::new()
        .route("/", get(handler))
        .route("/reset", get(reset_handler))
        .layer(session_manager);

    // 4. 启动服务器
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    println!("Server running on http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}

// 处理主页请求
async fn handler(session: Session) -> String {
    let visits: usize = session.get("visits").await.unwrap().unwrap_or(0);
    session.insert("visits", visits + 1).await.unwrap();
    
    format!("Welcome! You have visited this page {} times", visits + 1)
}

// 重置访问计数
async fn reset_handler(session: Session) -> String {
    session.insert("visits", 0).await.unwrap();
    "Visit counter has been reset!".to_string()
}

要运行这个示例,你需要:

  1. 确保有一个名为"sessions"的DynamoDB表,并启用了TTL功能
  2. 配置好AWS凭证
  3. 将上述代码保存为main.rs
  4. 运行cargo run启动服务器

访问http://localhost:3000可以看到访问计数,访问http://localhost:3000/reset可以重置计数。

回到顶部