动态路由 #
什么是动态路由? #
动态路由允许在 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