Security audit workflow - vulnerability scan β verification
npx skills add salvo-rs/salvo-skills --skill "salvo-middleware"
Install specific skill from multi-skill repository
# Description
Implement middleware for authentication, logging, CORS, and request processing. Use for cross-cutting concerns and request/response modification.
# SKILL.md
name: salvo-middleware
description: Implement middleware for authentication, logging, CORS, and request processing. Use for cross-cutting concerns and request/response modification.
Salvo Middleware
This skill helps implement middleware in Salvo applications. In Salvo, middleware is just a handler with flow control - the same concept applies to both.
Basic Middleware Pattern
Middleware uses FlowCtrl to control execution flow:
use salvo::prelude::*;
#[handler]
async fn logger(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
println!("Request: {} {}", req.method(), req.uri().path());
// Continue to next handler
ctrl.call_next(req, depot, res).await;
println!("Response status: {}", res.status_code().unwrap_or(StatusCode::OK));
}
Middleware Attachment
Use hoop() to attach middleware:
let router = Router::new()
.hoop(logger)
.hoop(auth_check)
.get(handler);
Middleware applies to all child routes:
let router = Router::new()
.push(
Router::with_path("api")
.hoop(auth_check) // Only applies to /api routes
.get(protected_handler)
)
.get(public_handler); // No auth check
Middleware Scopes
Global Middleware
let router = Router::new()
.hoop(global_middleware) // Applies to all routes
.push(Router::with_path("/api").get(api_handler))
.push(Router::with_path("/admin").get(admin_handler));
Route-Level Middleware
let router = Router::new()
.push(
Router::with_path("/api")
.hoop(api_middleware) // Only applies to /api
.get(api_handler)
)
.push(Router::with_path("/admin").get(admin_handler));
Combined Usage
let router = Router::new()
.hoop(logger) // Global logging
.push(
Router::with_path("/api")
.hoop(auth_middleware) // API authentication
.hoop(rate_limiter) // API rate limiting
.get(api_handler)
)
.push(
Router::with_path("/public")
.get(public_handler) // No auth required
);
Common Middleware Patterns
Authentication
#[handler]
async fn auth_check(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let token = req.header::<String>("Authorization");
match token {
Some(token) if validate_token(&token) => {
depot.insert("user_id", extract_user_id(&token));
ctrl.call_next(req, depot, res).await;
}
_ => {
res.status_code(StatusCode::UNAUTHORIZED);
res.render("Unauthorized");
ctrl.skip_rest();
}
}
}
Request Logging with Timing
#[handler]
async fn request_logger(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let start = std::time::Instant::now();
let method = req.method().clone();
let path = req.uri().path().to_string();
ctrl.call_next(req, depot, res).await;
let duration = start.elapsed();
let status = res.status_code().unwrap_or(StatusCode::OK);
println!("{} {} - {} ({:?})", method, path, status, duration);
}
Add Custom Response Header
#[handler]
async fn add_custom_header(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
res.headers_mut().insert("X-Custom-Header", "Salvo".parse().unwrap());
ctrl.call_next(req, depot, res).await;
}
CORS
use salvo::cors::Cors;
use salvo::http::Method;
let cors = Cors::new()
.allow_origin("https://example.com")
.allow_methods(vec![Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers(vec!["Content-Type", "Authorization"])
.into_handler();
let router = Router::new().hoop(cors);
Rate Limiting
use salvo::rate_limiter::{RateLimiter, FixedGuard, RemoteIpIssuer, BasicQuota, MokaStore};
use std::time::Duration;
let limiter = RateLimiter::new(
FixedGuard::new(),
MokaStore::new(),
RemoteIpIssuer,
BasicQuota::per_second(10),
);
let router = Router::new().hoop(limiter);
Using Depot for Data Sharing
Store data in middleware for use in handlers:
#[handler]
async fn auth_middleware(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let user = authenticate(req).await;
depot.insert("user", user);
ctrl.call_next(req, depot, res).await;
}
#[handler]
async fn protected_handler(depot: &mut Depot) -> String {
let user = depot.get::<User>("user").unwrap();
format!("Hello, {}", user.name)
}
Type-Safe Depot Usage
// Store different types
depot.insert("string_value", "hello");
depot.insert("int_value", 42);
depot.insert("bool_value", true);
// Safely retrieve values (type must match)
if let Some(str_val) = depot.get::<&str>("string_value") {
println!("String value: {}", str_val);
}
if let Some(int_val) = depot.get::<i32>("int_value") {
println!("Int value: {}", int_val);
}
Early Response
Stop execution and return response immediately:
#[handler]
async fn validate_input(req: &mut Request, depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
if !is_valid_request(req) {
res.status_code(StatusCode::BAD_REQUEST);
res.render("Invalid request");
ctrl.skip_rest(); // Stop processing
return;
}
ctrl.call_next(req, depot, res).await;
}
FlowCtrl Methods
FlowCtrl provides methods to control middleware chain execution:
call_next(): Call the next middleware or handlerskip_rest(): Skip remaining middleware and handlersis_ceased(): Check if execution has been stopped
Built-in Middleware
Salvo provides many built-in middleware:
use salvo::compression::Compression;
use salvo::cors::Cors;
use salvo::logging::Logger;
use salvo::timeout::Timeout;
use std::time::Duration;
let router = Router::new()
.hoop(Logger::new())
.hoop(Compression::new())
.hoop(Cors::permissive())
.hoop(Timeout::new(Duration::from_secs(30)));
Common Built-in Middleware
| Middleware | Feature | Description |
|---|---|---|
Logger |
logging |
Request/response logging |
Compression |
compression |
Response compression (gzip, brotli) |
Cors |
cors |
Cross-Origin Resource Sharing |
Timeout |
timeout |
Request timeout handling |
CsrfHandler |
csrf |
CSRF protection |
RateLimiter |
rate-limiter |
Rate limiting |
ConcurrencyLimiter |
concurrency-limiter |
Concurrent request limiting |
SizeLimiter |
size-limiter |
Request body size limiting |
Middleware Execution Order (Onion Model)
Middleware executes in an onion-like pattern:
Router::new()
.hoop(middleware_a) // Runs first (outer layer)
.hoop(middleware_b) // Runs second
.hoop(middleware_c) // Runs third (inner layer)
.get(handler); // Core handler
// Execution order:
// middleware_a (before) -> middleware_b (before) -> middleware_c (before)
// -> handler
// -> middleware_c (after) -> middleware_b (after) -> middleware_a (after)
Best Practices
- Use
ctrl.call_next()to continue execution - Use
ctrl.skip_rest()to stop early - Store shared data in
Depot - Apply middleware at appropriate router level
- Order middleware by dependency (auth before authorization)
- Use built-in middleware when available
- Keep middleware focused on single concern
- Put logging middleware first to capture all requests
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.