salvo-session
1
总安装量
1
周安装量
#48354
全站排名
安装命令
npx skills add https://github.com/salvo-rs/salvo-skills --skill salvo-session
Agent 安装分布
amp
1
opencode
1
cursor
1
kimi-cli
1
github-copilot
1
Skill 文档
Salvo Session Management
This skill helps implement session management in Salvo applications for maintaining user state across requests.
What are Sessions?
Sessions allow you to store user-specific data (like login status, shopping cart contents, preferences) on the server side, with a session ID stored in a cookie on the client.
Setup
[dependencies]
salvo = { version = "0.89.0", features = ["session"] }
Basic Session Setup
use salvo::prelude::*;
use salvo::session::{CookieStore, Session, SessionDepotExt, SessionHandler};
#[tokio::main]
async fn main() {
// Create session handler with cookie store
// Secret key must be 64 bytes for security
let session_handler = SessionHandler::builder(
CookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
let router = Router::new()
.hoop(session_handler)
.get(home)
.push(Router::with_path("login").get(login).post(login))
.push(Router::with_path("logout").get(logout));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Session Operations
Creating a Session
use salvo::session::{Session, SessionDepotExt};
#[handler]
async fn login(req: &mut Request, depot: &mut Depot, res: &mut Response) {
if req.method() == salvo::http::Method::POST {
let username = req.form::<String>("username").await.unwrap();
// Create new session
let mut session = Session::new();
session.insert("username", username).unwrap();
session.insert("logged_in", true).unwrap();
// Set session in depot
depot.set_session(session);
res.render(Redirect::other("/"));
} else {
res.render(Text::Html(LOGIN_FORM));
}
}
Reading Session Data
#[handler]
async fn home(depot: &mut Depot, res: &mut Response) {
if let Some(session) = depot.session_mut() {
if let Some(username) = session.get::<String>("username") {
res.render(Text::Html(format!("Hello, {}!", username)));
return;
}
}
res.render(Text::Html("Please login"));
}
Updating Session Data
#[handler]
async fn update_preferences(depot: &mut Depot, res: &mut Response) {
if let Some(session) = depot.session_mut() {
// Update existing value
session.insert("theme", "dark").unwrap();
// Increment a counter
let visits: i32 = session.get("visits").unwrap_or(0);
session.insert("visits", visits + 1).unwrap();
}
res.render("Preferences updated");
}
Removing Session Data
#[handler]
async fn logout(depot: &mut Depot, res: &mut Response) {
if let Some(session) = depot.session_mut() {
// Remove specific key
session.remove("username");
// Or clear all session data
// session.clear();
}
res.render(Redirect::other("/"));
}
Session Stores
Cookie Store (Default)
Stores session data encrypted in cookies. Simple, no external dependencies.
use salvo::session::CookieStore;
let session_handler = SessionHandler::builder(
CookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
Memory Store
Stores sessions in memory. Fast but lost on restart.
use salvo::session::MemoryStore;
let session_handler = SessionHandler::builder(
MemoryStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
Session Configuration
use std::time::Duration;
use salvo::session::{CookieStore, SessionHandler};
let session_handler = SessionHandler::builder(
CookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
// Session expiration time
.session_ttl(Some(Duration::from_secs(3600))) // 1 hour
// Cookie name
.cookie_name("session_id")
// Cookie path
.cookie_path("/")
// Cookie domain (optional)
// .cookie_domain("example.com")
// Secure cookie (HTTPS only)
.cookie_secure(true)
// HTTP only (no JavaScript access)
.cookie_http_only(true)
// Same site policy
.cookie_same_site(salvo::http::cookie::SameSite::Strict)
.build()
.unwrap();
Complete Login Example
use salvo::prelude::*;
use salvo::session::{CookieStore, Session, SessionDepotExt, SessionHandler};
#[handler]
async fn home(depot: &mut Depot, res: &mut Response) {
let content = if let Some(session) = depot.session_mut()
&& let Some(username) = session.get::<String>("username")
{
format!(r#"
<h1>Welcome, {username}!</h1>
<p><a href="/logout">Logout</a></p>
"#)
} else {
r#"
<h1>Welcome, Guest!</h1>
<p><a href="/login">Login</a></p>
"#.to_string()
};
res.render(Text::Html(content));
}
#[handler]
async fn login(req: &mut Request, depot: &mut Depot, res: &mut Response) {
if req.method() == salvo::http::Method::POST {
let username = req.form::<String>("username").await.unwrap_or_default();
let password = req.form::<String>("password").await.unwrap_or_default();
// Validate credentials (example only)
if username == "admin" && password == "password" {
let mut session = Session::new();
session.insert("username", username).unwrap();
session.insert("role", "admin").unwrap();
depot.set_session(session);
res.render(Redirect::other("/"));
} else {
res.render(Text::Html("Invalid credentials. <a href='/login'>Try again</a>"));
}
} else {
res.render(Text::Html(r#"
<!DOCTYPE html>
<html>
<body>
<h1>Login</h1>
<form method="post">
<p><input type="text" name="username" placeholder="Username" /></p>
<p><input type="password" name="password" placeholder="Password" /></p>
<button type="submit">Login</button>
</form>
</body>
</html>
"#));
}
}
#[handler]
async fn logout(depot: &mut Depot, res: &mut Response) {
if let Some(session) = depot.session_mut() {
session.remove("username");
session.remove("role");
}
res.render(Redirect::other("/"));
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt().init();
let session_handler = SessionHandler::builder(
CookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
let router = Router::new()
.hoop(session_handler)
.get(home)
.push(Router::with_path("login").get(login).post(login))
.push(Router::with_path("logout").get(logout));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Session with Authentication Middleware
use salvo::prelude::*;
use salvo::session::SessionDepotExt;
#[handler]
async fn require_login(
depot: &mut Depot,
res: &mut Response,
ctrl: &mut FlowCtrl,
) {
let logged_in = depot
.session_mut()
.and_then(|s| s.get::<bool>("logged_in"))
.unwrap_or(false);
if !logged_in {
res.render(Redirect::other("/login"));
ctrl.skip_rest();
}
}
#[handler]
async fn require_admin(
depot: &mut Depot,
res: &mut Response,
ctrl: &mut FlowCtrl,
) {
let is_admin = depot
.session_mut()
.and_then(|s| s.get::<String>("role"))
.map(|r| r == "admin")
.unwrap_or(false);
if !is_admin {
res.status_code(StatusCode::FORBIDDEN);
res.render("Admin access required");
ctrl.skip_rest();
}
}
#[tokio::main]
async fn main() {
let session_handler = SessionHandler::builder(
CookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
let router = Router::new()
.hoop(session_handler)
.get(home)
.push(Router::with_path("login").get(login).post(login))
.push(
Router::with_path("dashboard")
.hoop(require_login)
.get(dashboard)
)
.push(
Router::with_path("admin")
.hoop(require_login)
.hoop(require_admin)
.get(admin_panel)
);
}
Shopping Cart Example
use salvo::prelude::*;
use salvo::session::{Session, SessionDepotExt};
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize)]
struct CartItem {
product_id: u32,
name: String,
quantity: u32,
price: f64,
}
#[handler]
async fn add_to_cart(req: &mut Request, depot: &mut Depot, res: &mut Response) {
let product_id: u32 = req.param("id").unwrap();
let session = depot.session_mut().unwrap();
let mut cart: Vec<CartItem> = session.get("cart").unwrap_or_default();
// Add or update item
if let Some(item) = cart.iter_mut().find(|i| i.product_id == product_id) {
item.quantity += 1;
} else {
cart.push(CartItem {
product_id,
name: format!("Product {}", product_id),
quantity: 1,
price: 9.99,
});
}
session.insert("cart", cart).unwrap();
res.render(Redirect::other("/cart"));
}
#[handler]
async fn view_cart(depot: &mut Depot, res: &mut Response) {
let cart: Vec<CartItem> = depot
.session_mut()
.and_then(|s| s.get("cart"))
.unwrap_or_default();
let total: f64 = cart.iter().map(|i| i.price * i.quantity as f64).sum();
res.render(Json(serde_json::json!({
"items": cart,
"total": total
})));
}
#[handler]
async fn clear_cart(depot: &mut Depot, res: &mut Response) {
if let Some(session) = depot.session_mut() {
session.remove("cart");
}
res.render(Redirect::other("/cart"));
}
Best Practices
- Use secure secret keys: Generate 64 random bytes for session encryption
- Set appropriate TTL: Balance security with user convenience
- Use HTTPS: Always use secure cookies in production
- Set HttpOnly: Prevent JavaScript access to session cookies
- Use SameSite: Protect against CSRF attacks
- Validate session data: Don’t trust session data blindly
- Regenerate session on login: Prevent session fixation attacks
- Clean up on logout: Remove all sensitive session data