salvo-routing
1
总安装量
1
周安装量
#51133
全站排名
安装命令
npx skills add https://github.com/salvo-rs/salvo-skills --skill salvo-routing
Agent 安装分布
amp
1
opencode
1
cursor
1
kimi-cli
1
github-copilot
1
Skill 文档
Salvo Routing
This skill helps configure advanced routing patterns in Salvo applications.
Salvo Routing Innovation
Salvo’s routing system has unique features:
- Path as Filter: Path matching is essentially a filter, allowing unified combination with method and custom conditions
- Reusable Routes: Routers can be added to multiple locations for flexible composition
- Unified Middleware Model: Middleware and handlers share the same concept via
hoop()method - Flexible Nesting: Use
push()for arbitrary depth hierarchical structures
Path Patterns
Static Paths
Router::with_path("users").get(list_users)
Path Parameters
Salvo uses {id} syntax for path parameters (since version 0.76). Earlier versions used <id> syntax, which is now deprecated.
// Basic parameter
Router::with_path("users/{id}").get(show_user)
// Typed parameter (num, i32, i64, etc.)
Router::with_path("users/{id:num}").get(show_user)
// Regex pattern
Router::with_path(r"users/{id|\d+}").get(show_user)
// Wildcard (captures rest of path)
Router::with_path("files/{**path}").get(serve_file)
Accessing Parameters
#[handler]
async fn show_user(req: &mut Request) -> String {
let id = req.param::<i64>("id").unwrap();
format!("User ID: {}", id)
}
Wildcard Types
Salvo supports multiple wildcard patterns (using {} syntax since version 0.76; earlier versions used <> syntax):
-
{*}: Matches any single path segmentRouter::new().path("{*}").get(catch_all) -
{**}: Matches all remaining path segments (including slashes)Router::new().path("static/{**path}").get(serve_static) // Matches: static/css/style.css, static/js/main.js, etc. -
Named wildcards: Can retrieve matched content in handler
Router::new().path("files/{*rest}").get(handler) // In handler: req.param::<String>("rest")
Nested Routers
Tree Structure
let router = Router::new()
.push(
Router::with_path("api/v1")
.push(
Router::with_path("users")
.get(list_users)
.post(create_user)
.push(
Router::with_path("{id}")
.get(show_user)
.patch(update_user)
.delete(delete_user)
)
)
.push(
Router::with_path("posts")
.get(list_posts)
.post(create_post)
)
);
Route Composition
fn user_routes() -> Router {
Router::with_path("users")
.get(list_users)
.post(create_user)
.push(
Router::with_path("{id}")
.get(get_user)
.patch(update_user)
.delete(delete_user)
)
}
fn post_routes() -> Router {
Router::with_path("posts")
.get(list_posts)
.post(create_post)
}
let api_v1 = Router::with_path("v1")
.push(user_routes())
.push(post_routes());
let api_v2 = Router::with_path("v2")
.push(user_routes())
.push(post_routes());
let router = Router::new()
.push(Router::with_path("api/v1/users").get(list_users).post(create_user))
.push(Router::with_path("api/v1/users/{id}").get(show_user).patch(update_user).delete(delete_user));
HTTP Methods
Router::new()
.get(handler) // GET
.post(handler) // POST
.put(handler) // PUT
.patch(handler) // PATCH
.delete(handler) // DELETE
.head(handler) // HEAD
.options(handler); // OPTIONS
Path Matching Behavior
When a request arrives, routing works as follows:
- Filter Matching: First attempts to match route filters (path, method, etc.)
- Match Failed: If no filter matches, that route’s middleware and handler are skipped
- Match Success: If matched, executes middleware and handler in order
use salvo::routing::filters;
// Path filter
Router::with_filter(filters::path("users"))
// Method filter
Router::with_filter(filters::get())
// Combined filters
Router::with_filter(filters::path("users").and(filters::get()))
Middleware with Routes
Use hoop() to add middleware to routes:
let router = Router::new()
.hoop(logging) // Applies to all routes
.path("api")
.push(
Router::new()
.hoop(auth_check) // Only applies to routes under this
.path("users")
.get(list_users)
.post(create_user)
);
HTTP Redirects
use salvo::prelude::*;
use salvo::writing::Redirect;
// Permanent redirect (301)
#[handler]
async fn permanent_redirect(res: &mut Response) {
res.render(Redirect::permanent("/new-location"));
}
// Temporary redirect (302)
#[handler]
async fn temporary_redirect(res: &mut Response) {
res.render(Redirect::found("/temporary-location"));
}
// See Other (303)
#[handler]
async fn see_other(res: &mut Response) {
res.render(Redirect::see_other("/another-page"));
}
Custom Route Filters
Create custom filters for complex matching logic:
use salvo::prelude::*;
use salvo::routing::filter::Filter;
use uuid::Uuid;
pub struct GuidFilter;
impl Filter for GuidFilter {
fn filter(&self, req: &mut Request, _state: &mut PathState) -> bool {
if let Some(param) = req.param::<String>("id") {
Uuid::parse_str(¶m).is_ok()
} else {
false
}
}
}
#[handler]
async fn get_user_by_guid(req: &mut Request) -> String {
let id = req.param::<Uuid>("id").unwrap();
format!("User GUID: {}", id)
}
let router = Router::new()
.path("users/{id}")
.filter(GuidFilter)
.get(get_user_by_guid);
Best Practices
- Use tree structure for complex APIs
- Use route composition functions for reusability
- Use regex constraints for path parameters when needed (
{id:/\d+/}) - Group related routes under common paths
- Use descriptive parameter names
- Apply middleware at the appropriate route level
- Prefer
{id}syntax for consistency