원클릭으로
salvo-flash
Implement flash messages for one-time notifications across redirects. Use for success/error messages after form submissions.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Implement flash messages for one-time notifications across redirects. Use for success/error messages after form submissions.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Implement authentication and authorization using JWT, Basic Auth, or custom schemes. Use for securing API endpoints and user management.
Create basic Salvo web applications with handlers, routers, and server setup. Use when starting a new Salvo project or adding basic HTTP endpoints.
Implement caching strategies for improved performance. Use for reducing database load and speeding up responses.
Compress HTTP responses using gzip, brotli, zstd, or deflate. Use for reducing bandwidth and improving load times.
Limit concurrent requests to protect resources. Use for file uploads, expensive operations, and preventing resource exhaustion.
Configure Cross-Origin Resource Sharing (CORS) and security headers. Use for APIs accessed from browsers on different domains.
| name | salvo-flash |
| description | Implement flash messages for one-time notifications across redirects. Use for success/error messages after form submissions. |
| version | 0.94.0 |
| tags | ["advanced","flash-messages","notifications"] |
[dependencies]
salvo = { version = "0.94.0", features = ["flash"] }
FlashHandler middleware stores messages between requests. The set
(outgoing) is written by the current handler, the previous request's messages
are loaded into the "incoming" slot before handlers run, and the old entries
are cleared automatically after display.
use std::fmt::Write;
use salvo::flash::{CookieStore, FlashDepotExt};
use salvo::prelude::*;
#[handler]
async fn set_flash(depot: &mut Depot, res: &mut Response) {
let flash = depot.outgoing_flash_mut();
flash.info("Operation completed successfully!");
flash.debug("Debug information here");
res.render(Redirect::other("/show"));
}
#[handler]
async fn show_flash(depot: &mut Depot, res: &mut Response) {
let mut output = String::new();
if let Some(flash) = depot.incoming_flash() {
for msg in flash.iter() {
writeln!(output, "[{}] {}", msg.level, msg.value).unwrap();
}
}
if output.is_empty() { output.push_str("No flash messages"); }
res.render(Text::Plain(output));
}
#[tokio::main]
async fn main() {
let router = Router::new()
.hoop(CookieStore::new().into_handler())
.push(Router::with_path("set").get(set_flash))
.push(Router::with_path("show").get(show_flash));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Note incoming_flash takes &mut Depot, so any helper that reads flash must
also take &mut Depot.
Five levels on Flash (chainable &mut Self setters):
let flash = depot.outgoing_flash_mut();
flash.debug("...").info("...").success("...").warning("...").error("...");
FlashLevel is an enum, not a String. Match on the variants (it implements
Display / Debug as the lowercase name), or call .to_str():
use salvo::flash::FlashLevel;
for msg in flash.iter() {
let class = match msg.level {
FlashLevel::Success => "alert-success",
FlashLevel::Error => "alert-error",
FlashLevel::Warning => "alert-warning",
FlashLevel::Info => "alert-info",
FlashLevel::Debug => "alert-debug",
};
println!("<div class=\"{class}\">{}</div>", msg.value);
}
msg.level.as_str() does NOT exist — use to_str() or the Display impl.
CookieStore configurationBuilders on CookieStore (all #[must_use]): name, max_age, same_site,
secure, secure_cookie_policy, http_only, path, key. Defaults: name
"salvo.flash", max_age = 60s, SameSite::Lax,
SecureCookiePolicy::AutoFromScheme, http_only = true, path = "/".
use salvo::http::SecureCookiePolicy;
use salvo::http::cookie::{time::Duration, SameSite};
let store = CookieStore::new()
.name("app.flash")
.max_age(Duration::seconds(300))
.same_site(SameSite::Strict)
.secure_cookie_policy(SecureCookiePolicy::Always)
.path("/app");
let router = Router::new().hoop(store.into_handler());
Use .secure(true) or .secure_cookie_policy(SecureCookiePolicy::Always) when TLS terminates before Salvo and flash cookies must still carry the Secure attribute.
When messages are too large for a cookie, use SessionStore (requires a
SessionHandler in front of it).
use salvo::flash::{FlashDepotExt, SessionStore};
use salvo::session::{CookieStore as SessionCookieStore, SessionHandler};
use salvo::prelude::*;
#[tokio::main]
async fn main() {
let session = SessionHandler::builder(
SessionCookieStore::new(),
b"secretabsecretabsecretabsecretabsecretabsecretabsecretabsecretab",
)
.build()
.unwrap();
let router = Router::new()
.hoop(session) // session first
.hoop(SessionStore::new().into_handler()) // then flash
.push(Router::with_path("set").get(set_flash))
.push(Router::with_path("show").get(show_flash));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
FlashHandler::minimum_level(level) drops outgoing messages below the given
level (useful in production to suppress debug noise):
use salvo::flash::{CookieStore, FlashHandler, FlashLevel};
let mut handler = FlashHandler::new(CookieStore::new());
handler.minimum_level(FlashLevel::Warning);
let router = Router::new().hoop(handler);
use salvo::flash::{CookieStore, FlashDepotExt, FlashLevel};
use salvo::prelude::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct ContactForm { name: String, email: String, message: String }
#[handler]
async fn show_form(depot: &mut Depot, res: &mut Response) {
let mut alerts = String::new();
if let Some(flash) = depot.incoming_flash() {
for msg in flash.iter() {
let class = match msg.level {
FlashLevel::Success => "alert-success",
FlashLevel::Error => "alert-error",
FlashLevel::Warning => "alert-warning",
_ => "alert-info",
};
alerts.push_str(&format!(r#"<div class="{class}">{}</div>"#, msg.value));
}
}
res.render(Text::Html(format!(r#"
<!DOCTYPE html><html><body>
{alerts}
<form method="post" action="/contact">
<input name="name" required />
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
</body></html>
"#)));
}
#[handler]
async fn handle_form(req: &mut Request, depot: &mut Depot, res: &mut Response) {
match req.parse_form::<ContactForm>().await {
Ok(form) => {
// ... process form ...
let _ = form;
depot.outgoing_flash_mut().success("Thank you! Your message has been sent.");
}
Err(e) => {
depot.outgoing_flash_mut().error(format!("Error: {e}"));
}
}
res.render(Redirect::other("/contact"));
}
#[tokio::main]
async fn main() {
let router = Router::new()
.hoop(CookieStore::new().into_handler())
.push(Router::with_path("contact").get(show_form).post(handle_form));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
fn flash_to_json(depot: &mut Depot) -> serde_json::Value {
let messages: Vec<_> = depot
.incoming_flash()
.map(|flash| {
flash
.iter()
.map(|m| serde_json::json!({ "level": m.level.to_str(), "message": m.value }))
.collect()
})
.unwrap_or_default();
serde_json::json!({ "flash": messages })
}
SessionStore