路由属性 #

什么是路由属性? #

路由属性是 Actix Web 提供的宏,用于简化路由定义。通过属性宏,可以直接在处理函数上声明路由信息。

对比:传统方式 vs 属性宏 #

rust
// 传统方式
App::new()
    .route("/users", web::get().to(list_users))
    .route("/users/{id}", web::get().to(get_user))
    .route("/users", web::post().to(create_user))

// 属性宏方式
App::new()
    .service(list_users)
    .service(get_user)
    .service(create_user)

#[actix_web::get("/users")]
async fn list_users() -> impl Responder { ... }

#[actix_web::get("/users/{id}")]
async fn get_user() -> impl Responder { ... }

#[actix_web::post("/users")]
async fn create_user() -> impl Responder { ... }

HTTP 方法属性 #

GET 属性 #

rust
#[actix_web::get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok().body("Home page")
}

#[actix_web::get("/users")]
async fn list_users() -> impl Responder {
    HttpResponse::Ok().json(vec!["Alice", "Bob"])
}

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

POST 属性 #

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

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

PUT 属性 #

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

#[actix_web::put("/users/{id}")]
async fn update_user(
    path: web::Path<u32>,
    body: web::Json<UpdateUser>,
) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "id": path.into_inner(),
        "updated": true
    }))
}

DELETE 属性 #

rust
#[actix_web::delete("/users/{id}")]
async fn delete_user(path: web::Path<u32>) -> impl Responder {
    HttpResponse::NoContent().finish()
}

PATCH 属性 #

rust
#[actix_web::patch("/users/{id}")]
async fn patch_user(
    path: web::Path<u32>,
    body: web::Json<UpdateUser>,
) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "id": path.into_inner(),
        "patched": true
    }))
}

HEAD 属性 #

rust
#[actix_web::head("/users/{id}")]
async fn head_user(path: web::Path<u32>) -> impl Responder {
    HttpResponse::Ok()
        .insert_header(("X-User-Id", path.into_inner().to_string()))
        .insert_header(("Content-Length", "0"))
        .finish()
}

OPTIONS 属性 #

rust
#[actix_web::options("/users")]
async fn options_users() -> impl Responder {
    HttpResponse::Ok()
        .insert_header(("Allow", "GET, POST, PUT, DELETE, PATCH"))
        .insert_header(("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH"))
        .finish()
}

路径参数 #

单个路径参数 #

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

多个路径参数 #

rust
#[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("/int/{id}")]
async fn get_int(path: web::Path<i32>) -> impl Responder {
    HttpResponse::Ok().body(format!("Integer: {}", 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
#[actix_web::get("/users/{id:\\d+}")]
async fn get_user_by_id(path: web::Path<u32>) -> impl Responder {
    HttpResponse::Ok().body(format!("User ID: {}", path.into_inner()))
}

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

自定义路径段 #

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

查询参数 #

基本查询参数 #

rust
#[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,
    limit: Option<u32>,
}

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

组合参数 #

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

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

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

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

服务注册 #

使用 service 方法 #

rust
#[actix_web::get("/")]
async fn index() -> impl Responder {
    HttpResponse::Ok().body("Index")
}

#[actix_web::get("/users")]
async fn users() -> impl Responder {
    HttpResponse::Ok().body("Users")
}

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

使用 configure #

rust
fn user_config(cfg: &mut web::ServiceConfig) {
    cfg.service(list_users)
        .service(get_user)
        .service(create_user);
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .configure(user_config)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

路由守卫 #

使用 guard 属性 #

rust
use actix_web::guard::{Guard, GuardContext};

struct AdminGuard;

impl Guard for AdminGuard {
    fn check(&self, ctx: &GuardContext) -> bool {
        ctx.head()
            .headers()
            .get("X-Admin")
            .map(|v| v == "true")
            .unwrap_or(false)
    }
}

#[actix_web::get("/admin", guard = "AdminGuard")]
async fn admin_panel() -> impl Responder {
    HttpResponse::Ok().body("Admin Panel")
}

使用 Header 守卫 #

rust
use actix_web::guard::Header;

#[actix_web::get("/api", guard = "Header(\"X-Api-Key\", \"secret\")")]
async fn api_endpoint() -> impl Responder {
    HttpResponse::Ok().body("API Response")
}

路由中间件 #

在属性中指定中间件 #

rust
use actix_web::middleware::Logger;

#[actix_web::get("/logged", wrap = "Logger::default()")]
async fn logged_endpoint() -> impl Responder {
    HttpResponse::Ok().body("This endpoint is logged")
}

完整 RESTful 示例 #

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

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

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

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

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

#[actix_web::get("/users")]
async fn list_users(query: web::Query<ListQuery>) -> 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,
        "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<u32>) -> impl Responder {
    let id = path.into_inner();
    
    HttpResponse::Ok().json(User {
        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<u32>,
    body: web::Json<UpdateUser>,
) -> impl Responder {
    let id = path.into_inner();
    
    HttpResponse::Ok().json(User {
        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<u32>) -> impl Responder {
    let id = path.into_inner();
    
    HttpResponse::Ok().json(serde_json::json!({
        "deleted": true,
        "id": 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
}

属性宏总结 #

属性 说明 示例
#[get("/path")] GET 请求 #[get("/users")]
#[post("/path")] POST 请求 #[post("/users")]
#[put("/path")] PUT 请求 #[put("/users/{id}")]
#[delete("/path")] DELETE 请求 #[delete("/users/{id}")]
#[patch("/path")] PATCH 请求 #[patch("/users/{id}")]
#[head("/path")] HEAD 请求 #[head("/users/{id}")]
#[options("/path")] OPTIONS 请求 #[options("/users")]
guard = "..." 路由守卫 guard = "AdminGuard"
wrap = "..." 中间件包装 wrap = "Logger::default()"

下一步 #

现在你已经掌握了路由属性,继续学习 动态路由,深入了解动态路由和参数提取!

最后更新:2026-03-29