动态路由 #

动态路由允许在URL路径中包含变量部分,使路由更加灵活。Rocket提供了强大的参数解析和类型转换功能。

路径参数 #

基本语法 #

使用 <param> 语法定义动态参数:

rust
#[get("/user/<id>")]
fn get_user(id: u32) -> String {
    format!("User ID: {}", id)
}

单个参数 #

rust
#[get("/hello/<name>")]
fn hello(name: &str) -> String {
    format!("Hello, {}!", name)
}

#[get("/age/<age>")]
fn age(age: u32) -> String {
    format!("Age: {}", age)
}

#[get("/price/<amount>")]
fn price(amount: f64) -> String {
    format!("Price: ${:.2}", amount)
}

多个参数 #

rust
#[get("/user/<user_id>/post/<post_id>")]
fn user_post(user_id: u32, post_id: u32) -> String {
    format!("User {}'s post {}", user_id, post_id)
}

#[get("/category/<category>/product/<product>")]
fn product(category: &str, product: &str) -> String {
    format!("Category: {}, Product: {}", category, product)
}

参数类型 #

基本类型 #

Rocket自动将路径参数转换为指定类型:

rust
#[get("/int/<num>")]
fn int_param(num: i32) -> String {
    format!("Integer: {}", num)
}

#[get("/uint/<num>")]
fn uint_param(num: u32) -> String {
    format!("Unsigned: {}", num)
}

#[get("/float/<num>")]
fn float_param(num: f64) -> String {
    format!("Float: {}", num)
}

#[get("/bool/<val>")]
fn bool_param(val: bool) -> String {
    format!("Boolean: {}", val)
}

字符串类型 #

rust
#[get("/string/<s>")]
fn string_param(s: String) -> String {
    format!("String: {}", s)
}

#[get("/str/<s>")]
fn str_param(s: &str) -> String {
    format!("&str: {}", s)
}

可选参数 #

rust
#[get("/maybe/<name>")]
fn maybe_name(name: Option<&str>) -> String {
    match name {
        Some(n) => format!("Hello, {}!", n),
        None => "Hello, stranger!".to_string(),
    }
}

Result类型 #

rust
use rocket::http::Status;

#[get("/user/<id>")]
fn get_user(id: Result<u32, &str>) -> Result<String, Status> {
    match id {
        Ok(id) => Ok(format!("User ID: {}", id)),
        Err(_) => Err(Status::BadRequest),
    }
}

多段路径参数 #

PathBuf类型 #

使用 <path..> 捕获多段路径:

rust
use std::path::PathBuf;

#[get("/file/<path..>")]
fn get_file(path: PathBuf) -> String {
    format!("File path: {:?}", path)
}

访问 /file/docs/readme.md 返回 File path: "docs/readme.md"

访问 /file/a/b/c/d.txt 返回 File path: "a/b/c/d.txt"

结合静态路径 #

rust
#[get("/static/<path..>")]
fn static_files(path: PathBuf) -> String {
    format!("Static file: {:?}", path)
}

#[get("/api/v1/<path..>")]
fn api_v1(path: PathBuf) -> String {
    format!("API v1: {:?}", path)
}

查询参数 #

单个查询参数 #

rust
#[get("/search?<q>")]
fn search(q: &str) -> String {
    format!("Searching for: {}", q)
}

访问 /search?q=rocket 返回 Searching for: rocket

多个查询参数 #

rust
#[get("/search?<q>&<page>")]
fn search_with_page(q: &str, page: u32) -> String {
    format!("Searching '{}' on page {}", q, page)
}

结构化查询参数 #

rust
use rocket::request::Query;
use rocket::FromForm;

#[derive(FromForm)]
struct SearchParams {
    q: String,
    page: Option<usize>,
    sort: Option<String>,
    order: Option<String>,
}

#[get("/search?<params..>")]
fn search(params: Query<SearchParams>) -> String {
    let page = params.page.unwrap_or(1);
    let sort = params.sort.as_deref().unwrap_or("relevance");
    let order = params.order.as_deref().unwrap_or("desc");
    
    format!("Search '{}' - Page {}, Sort by {} {}", params.q, page, sort, order)
}

数组查询参数 #

rust
#[derive(FromForm)]
struct FilterParams {
    #[field(name = "tag")]
    tags: Vec<String>,
}

#[get("/filter?<params..>")]
fn filter(params: Query<FilterParams>) -> String {
    format!("Tags: {:?}", params.tags)
}

访问 /filter?tag=rust&tag=web&tag=rocket 返回 Tags: ["rust", "web", "rocket"]

参数验证 #

类型验证 #

Rocket自动进行类型验证:

rust
#[get("/user/<id>")]
fn get_user(id: u32) -> String {
    format!("User ID: {}", id)
}

访问 /user/abc 会返回404,因为 “abc” 不是有效的 u32。

自定义验证 #

rust
use rocket::request::{FromParam, Request};
use rocket::http::Status;

struct PositiveInt(u32);

impl<'a> FromParam<'a> for PositiveInt {
    type Error = &'a str;

    fn from_param(param: &'a str) -> Result<Self, Self::Error> {
        let num: u32 = param.parse().map_err(|_| param)?;
        if num > 0 {
            Ok(PositiveInt(num))
        } else {
            Err(param)
        }
    }
}

#[get("/positive/<num>")]
fn positive(num: Result<PositiveInt, &str>) -> String {
    match num {
        Ok(PositiveInt(n)) => format!("Positive: {}", n),
        Err(_) => "Must be a positive integer".to_string(),
    }
}

枚举验证 #

rust
use rocket::request::FromParam;

enum Status {
    Active,
    Inactive,
    Pending,
}

impl<'a> FromParam<'a> for Status {
    type Error = &'a str;

    fn from_param(param: &'a str) -> Result<Self, Self::Error> {
        match param.to_lowercase().as_str() {
            "active" => Ok(Status::Active),
            "inactive" => Ok(Status::Inactive),
            "pending" => Ok(Status::Pending),
            _ => Err(param),
        }
    }
}

#[get("/status/<status>")]
fn get_status(status: Result<Status, &str>) -> String {
    match status {
        Ok(s) => format!("Status: {:?}", s),
        Err(_) => "Invalid status".to_string(),
    }
}

参数默认值 #

rust
#[derive(FromForm)]
struct PageParams {
    #[field(default = 1)]
    page: usize,
    
    #[field(default = 10)]
    per_page: usize,
}

#[get("/items?<params..>")]
fn list_items(params: Query<PageParams>) -> String {
    format!("Page {} with {} items", params.page, params.per_page)
}

完整示例 #

rust
#[macro_use] extern crate rocket;

use rocket::request::Query;
use rocket::FromForm;
use rocket::serde::json::Json;
use rocket::serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Serialize, Deserialize, FromForm)]
#[serde(crate = "rocket::serde")]
struct User {
    id: u32,
    name: String,
    email: String,
}

#[derive(FromForm)]
struct SearchQuery {
    q: String,
    page: Option<usize>,
    limit: Option<usize>,
}

#[get("/")]
fn index() -> &'static str {
    "API Root"
}

#[get("/users/<id>")]
fn get_user(id: u32) -> Json<User> {
    Json(User {
        id,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    })
}

#[get("/users?<query..>")]
fn search_users(query: Query<SearchQuery>) -> String {
    let page = query.page.unwrap_or(1);
    let limit = query.limit.unwrap_or(10);
    format!("Searching '{}' - Page {} (limit: {})", query.q, page, limit)
}

#[get("/files/<path..>")]
fn get_file(path: PathBuf) -> String {
    format!("File: {:?}", path)
}

#[get("/user/<user_id>/posts/<post_id>")]
fn get_user_post(user_id: u32, post_id: u32) -> String {
    format!("User {}'s post {}", user_id, post_id)
}

#[launch]
fn rocket() -> _ {
    rocket::build()
        .mount("/api", routes![
            index,
            get_user,
            search_users,
            get_file,
            get_user_post
        ])
}

测试示例 #

bash
# 获取用户
curl http://127.0.0.1:8000/api/users/1

# 搜索用户
curl "http://127.0.0.1:8000/api/users?q=alice&page=1&limit=5"

# 获取文件
curl http://127.0.0.1:8000/api/files/docs/readme.md

# 获取用户文章
curl http://127.0.0.1:8000/api/user/1/posts/42

下一步 #

掌握了动态路由后,让我们继续学习 路由守卫,了解如何在路由级别进行权限控制。

最后更新:2026-03-28