Rust会话管理库tower-sessions-dynamodb-store的使用:基于DynamoDB的高性能会话存储解决方案
Rust会话管理库tower-sessions-dynamodb-store的使用:基于DynamoDB的高性能会话存储解决方案
概述
这个crate为tower-sessions会话管理库提供了AWS DynamoDB后端支持,使用Rust AWS-SDK实现。
使用方法
设置DynamoDBStore
需要两个参数:
- 一个
aws_sdk_dynamodb::Client
实例,用于连接AWS DynamoDB服务 - 一个
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::Client
和DynamoDBStoreProps
实例后,可以配置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();
}
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"
性能优化建议
- 确保会话ID在分区键上有良好的分布
- 根据负载调整DynamoDB的容量模式
- 考虑使用本地缓存减少DynamoDB读取
- 合理设置会话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()
}
要运行这个示例,你需要:
- 确保有一个名为"sessions"的DynamoDB表,并启用了TTL功能
- 配置好AWS凭证
- 将上述代码保存为main.rs
- 运行
cargo run
启动服务器
访问http://localhost:3000
可以看到访问计数,访问http://localhost:3000/reset
可以重置计数。