动态路由 #

什么是动态路由? #

动态路由允许在 URL 路径中包含变量部分,这些变量可以被提取并传递给处理函数。

text
┌─────────────────────────────────────────────────────────────┐
│                      动态路由示例                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  静态路由:  /users                                           │
│  动态路由:  /users/{id}                                      │
│  动态路由:  /users/{user_id}/posts/{post_id}                │
│                                                              │
│  URL 匹配:                                                   │
│  /users/123         → id = 123                              │
│  /users/456/posts/1 → user_id = 456, post_id = 1           │
│                                                              │
└─────────────────────────────────────────────────────────────┘

路径参数 #

单个路径参数 #

基本用法 #

rust
use actix_web::{web, HttpResponse, Responder};

#[actix_web::get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> impl Responder {
    let id = path.into_inner();
    HttpResponse::Ok().body(format!("User ID: {}", id))
}

支持的类型 #

rust
#[actix_web::get("/int/{id}")]
async fn get_int(path: web::Path<i32>) -> impl Responder {
    HttpResponse::Ok().body(format!("Integer: {}", path.into_inner()))
}

#[actix_web::get("/uint/{id}")]
async fn get_uint(path: web::Path<u32>) -> impl Responder {
    HttpResponse::Ok().body(format!("Unsigned: {}", path.into_inner()))
}

#[actix_web::get("/string/{name}")]
async fn get_string(path: web::Path<String>) -> impl Responder {
    HttpResponse::Ok().body(format!("String: {}", path.into_inner()))
}

#[actix_web::get("/uuid/{id}")]
async fn get_uuid(path: web::Path<uuid::Uuid>) -> impl Responder {
    HttpResponse::Ok().body(format!("UUID: {}", path.into_inner()))
}

多个路径参数 #

使用结构体 #

rust
use serde::Deserialize;

#[derive(Deserialize)]
struct UserPostPath {
    user_id: u32,
    post_id: u32,
}

#[actix_web::get("/users/{user_id}/posts/{post_id}")]
async fn get_user_post(path: web::Path<UserPostPath>) -> impl Responder {
    let params = path.into_inner();
    HttpResponse::Ok().json(serde_json::json!({
        "user_id": params.user_id,
        "post_id": params.post_id
    }))
}

使用元组 #

rust
#[actix_web::get("/users/{user_id}/posts/{post_id}")]
async fn get_user_post(path: web::Path<(u32, u32)>) -> impl Responder {
    let (user_id, post_id) = path.into_inner();
    HttpResponse::Ok().json(serde_json::json!({
        "user_id": user_id,
        "post_id": post_id
    }))
}

路径参数约束 #

正则表达式约束 #

rust
#[actix_web::get("/users/{id:\\d+}")]
async fn get_user_numeric(path: web::Path<u32>) -> impl Responder {
    HttpResponse::Ok().body(format!("Numeric ID: {}", path.into_inner()))
}

#[actix_web::get("/files/{name:[a-zA-Z0-9_-]+}")]
async fn get_file(path: web::Path<String>) -> impl Responder {
    HttpResponse::Ok().body(format!("File: {}", path.into_inner()))
}

#[actix_web::get("/slug/{slug:[a-z0-9-]+}")]
async fn get_slug(path: web::Path<String>) -> impl Responder {
    HttpResponse::Ok().body(format!("Slug: {}", path.into_inner()))
}

通配符匹配 #

rust
#[actix_web::get("/path/{tail:.*}")]
async fn get_any_path(path: web::Path<String>) -> impl Responder {
    HttpResponse::Ok().body(format!("Path: {}", path.into_inner()))
}

查询参数 #

基本查询参数 #

rust
use serde::Deserialize;

#[derive(Deserialize)]
struct QueryParams {
    page: Option<u32>,
    per_page: Option<u32>,
}

#[actix_web::get("/users")]
async fn list_users(query: web::Query<QueryParams>) -> impl Responder {
    let page = query.page.unwrap_or(1);
    let per_page = query.per_page.unwrap_or(10);
    
    HttpResponse::Ok().json(serde_json::json!({
        "page": page,
        "per_page": per_page
    }))
}

必需查询参数 #

rust
#[derive(Deserialize)]
struct SearchQuery {
    q: String,
}

#[actix_web::get("/search")]
async fn search(query: web::Query<SearchQuery>) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "query": query.q
    }))
}

复杂查询参数 #

rust
#[derive(Deserialize)]
struct FilterQuery {
    status: Option<String>,
    sort: Option<String>,
    order: Option<String>,
    page: Option<u32>,
    limit: Option<u32>,
}

#[actix_web::get("/products")]
async fn list_products(query: web::Query<FilterQuery>) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "status": query.status.as_deref().unwrap_or("all"),
        "sort": query.sort.as_deref().unwrap_or("created_at"),
        "order": query.order.as_deref().unwrap_or("desc"),
        "page": query.page.unwrap_or(1),
        "limit": query.limit.unwrap_or(20)
    }))
}

数组查询参数 #

rust
#[derive(Deserialize)]
struct ArrayQuery {
    tags: Option<Vec<String>>,
}

#[actix_web::get("/articles")]
async fn list_articles(query: web::Query<ArrayQuery>) -> impl Responder {
    let tags = query.tags.clone().unwrap_or_default();
    HttpResponse::Ok().json(serde_json::json!({
        "tags": tags
    }))
}

请求示例:

text
GET /articles?tags=rust&tags=web&tags=async

组合参数 #

路径 + 查询参数 #

rust
#[derive(Deserialize)]
struct UserId {
    id: u32,
}

#[derive(Deserialize)]
struct DetailQuery {
    fields: Option<String>,
}

#[actix_web::get("/users/{id}")]
async fn get_user(
    path: web::Path<UserId>,
    query: web::Query<DetailQuery>,
) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "id": path.id,
        "fields": query.fields.as_deref().unwrap_or("all")
    }))
}

路径 + 查询 + 请求体 #

rust
#[derive(Deserialize)]
struct UserId {
    id: u32,
}

#[derive(Deserialize)]
struct UpdateQuery {
    force: Option<bool>,
}

#[derive(Deserialize)]
struct UpdateUser {
    name: Option<String>,
    email: Option<String>,
}

#[actix_web::put("/users/{id}")]
async fn update_user(
    path: web::Path<UserId>,
    query: web::Query<UpdateQuery>,
    body: web::Json<UpdateUser>,
) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "id": path.id,
        "force": query.force.unwrap_or(false),
        "name": body.name,
        "email": body.email
    }))
}

表单参数 #

URL 编码表单 #

rust
#[derive(Deserialize)]
struct LoginForm {
    username: String,
    password: String,
}

#[actix_web::post("/login")]
async fn login(form: web::Form<LoginForm>) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "username": form.username,
        "logged_in": true
    }))
}

多部分表单 #

rust
use actix_multipart::Multipart;

#[actix_web::post("/upload")]
async fn upload(mut payload: Multipart) -> impl Responder {
    while let Some(item) = payload.next().await {
        let mut field = item.unwrap();
        let content_type = field.content_type();
        let disposition = field.content_disposition();
        
        while let Some(chunk) = field.next().await {
            let data = chunk.unwrap();
        }
    }
    
    HttpResponse::Ok().body("Upload complete")
}

路径信息访问 #

访问原始路径 #

rust
use actix_web::HttpRequest;

#[actix_web::get("/info")]
async fn path_info(req: HttpRequest) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "path": req.path(),
        "query_string": req.query_string(),
        "method": req.method().as_str(),
        "headers": req.headers().len()
    }))
}

获取匹配信息 #

rust
#[actix_web::get("/users/{id}/posts/{post_id}")]
async fn get_post(req: HttpRequest, path: web::Path<(u32, u32)>) -> impl Responder {
    let (user_id, post_id) = path.into_inner();
    
    HttpResponse::Ok().json(serde_json::json!({
        "user_id": user_id,
        "post_id": post_id,
        "full_path": req.path()
    }))
}

参数验证 #

自定义验证 #

rust
use serde::Deserialize;
use validator::Validate;

#[derive(Deserialize, Validate)]
struct ValidatedQuery {
    #[validate(range(min = 1, max = 100))]
    page: Option<u32>,
    
    #[validate(range(min = 1, max = 100))]
    per_page: Option<u32>,
    
    #[validate(email)]
    email: Option<String>,
}

#[actix_web::get("/search")]
async fn search(query: web::Query<ValidatedQuery>) -> impl Responder {
    if let Err(e) = query.validate() {
        return HttpResponse::BadRequest().json(serde_json::json!({
            "error": e.to_string()
        }));
    }
    
    HttpResponse::Ok().json(serde_json::json!({
        "page": query.page.unwrap_or(1),
        "per_page": query.per_page.unwrap_or(10)
    }))
}

错误处理 #

rust
use actix_web::error::QueryPayloadError;

async fn query_error_handler(err: QueryPayloadError, _: &HttpRequest) -> actix_web::Error {
    actix_web::error::ErrorBadRequest(serde_json::json!({
        "error": "Invalid query parameters",
        "details": err.to_string()
    }))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .app_data(
                web::QueryConfig::default()
                    .error_handler(query_error_handler)
            )
            .service(search)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

完整示例 #

rust
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

#[derive(Deserialize)]
struct UserPath {
    id: u32,
}

#[derive(Deserialize)]
struct UserQuery {
    fields: Option<String>,
    verbose: Option<bool>,
}

#[derive(Deserialize)]
struct ListQuery {
    page: Option<u32>,
    per_page: Option<u32>,
    sort: Option<String>,
    order: Option<String>,
}

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[derive(Deserialize)]
struct UpdateUser {
    name: Option<String>,
    email: Option<String>,
}

#[actix_web::get("/users")]
async fn list_users(query: web::Query<ListQuery>) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "page": query.page.unwrap_or(1),
        "per_page": query.per_page.unwrap_or(10),
        "sort": query.sort.as_deref().unwrap_or("id"),
        "order": query.order.as_deref().unwrap_or("asc"),
        "users": [
            {"id": 1, "name": "Alice", "email": "alice@example.com"},
            {"id": 2, "name": "Bob", "email": "bob@example.com"}
        ]
    }))
}

#[actix_web::get("/users/{id}")]
async fn get_user(
    path: web::Path<UserPath>,
    query: web::Query<UserQuery>,
) -> impl Responder {
    let verbose = query.verbose.unwrap_or(false);
    
    HttpResponse::Ok().json(User {
        id: path.id,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    })
}

#[actix_web::post("/users")]
async fn create_user(body: web::Json<CreateUser>) -> impl Responder {
    HttpResponse::Created().json(User {
        id: 1,
        name: body.name.clone(),
        email: body.email.clone(),
    })
}

#[actix_web::put("/users/{id}")]
async fn update_user(
    path: web::Path<UserPath>,
    body: web::Json<UpdateUser>,
) -> impl Responder {
    HttpResponse::Ok().json(User {
        id: path.id,
        name: body.name.clone().unwrap_or_default(),
        email: body.email.clone().unwrap_or_default(),
    })
}

#[actix_web::delete("/users/{id}")]
async fn delete_user(path: web::Path<UserPath>) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "deleted": true,
        "id": path.id
    }))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(list_users)
            .service(get_user)
            .service(create_user)
            .service(update_user)
            .service(delete_user)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

参数提取器总结 #

提取器 说明 示例
web::Path<T> 路径参数 /users/{id}
web::Query<T> 查询参数 ?page=1&limit=10
web::Json<T> JSON 请求体 {"name": "Alice"}
web::Form<T> 表单数据 username=Alice
web::Bytes 原始字节 文件上传
web::Payload 流式请求体 大文件上传

下一步 #

现在你已经掌握了动态路由,继续学习 路由守卫,了解如何保护你的路由!

最后更新:2026-03-29