路由守卫 #

什么是路由守卫? #

路由守卫是一种机制,用于在请求到达处理函数之前进行检查。守卫可以决定是否允许请求继续处理,或者返回错误响应。

text
┌─────────────────────────────────────────────────────────────┐
│                      守卫执行流程                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  HTTP 请求                                                    │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   路由守卫                            │   │
│  │  检查请求方法、头部、参数等                           │   │
│  └─────────────────────────────────────────────────────┘   │
│         │                                                    │
│    ┌────┴────┐                                              │
│    │         │                                              │
│    ▼         ▼                                              │
│  通过      拒绝                                              │
│    │         │                                              │
│    ▼         ▼                                              │
│ 处理函数   错误响应                                          │
│                                                              │
└─────────────────────────────────────────────────────────────┘

内置守卫 #

方法守卫 #

rust
use actix_web::{guard, web, App, HttpResponse, HttpServer};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route(
                "/users",
                web::route()
                    .guard(guard::Get())
                    .to(get_users)
            )
            .route(
                "/users",
                web::route()
                    .guard(guard::Post())
                    .to(create_user)
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

async fn get_users() -> HttpResponse {
    HttpResponse::Ok().json(vec!["Alice", "Bob"])
}

async fn create_user() -> HttpResponse {
    HttpResponse::Created().body("User created")
}

头部守卫 #

rust
use actix_web::guard::Header;

App::new()
    .route(
        "/api",
        web::route()
            .guard(Header("content-type", "application/json"))
            .to(api_handler)
    )
    .route(
        "/admin",
        web::route()
            .guard(Header("X-Admin-Key", "secret123"))
            .to(admin_handler)
    )

Get 守卫 #

rust
App::new()
    .route(
        "/",
        web::route()
            .guard(guard::Get())
            .to(index)
    )

Not 守卫 #

rust
App::new()
    .route(
        "/api",
        web::route()
            .guard(guard::Not(guard::Get()))
            .to(api_handler)
    )

组合守卫 #

rust
App::new()
    .route(
        "/api",
        web::route()
            .guard(guard::All(guard::Get()).and(Header("X-Api-Key", "secret")))
            .to(api_handler)
    )
    .route(
        "/public",
        web::route()
            .guard(guard::Any(guard::Get()).or(guard::Post()))
            .to(public_handler)
    )

自定义守卫 #

实现 Guard Trait #

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

struct ContentTypeJson;

impl Guard for ContentTypeJson {
    fn check(&self, ctx: &GuardContext) -> bool {
        ctx.head()
            .headers()
            .get(header::CONTENT_TYPE)
            .map(|v| v == "application/json")
            .unwrap_or(false)
    }
}

App::new()
    .route(
        "/api",
        web::route()
            .guard(ContentTypeJson)
            .to(api_handler)
    )

带参数的守卫 #

rust
struct HeaderGuard {
    name: &'static str,
    value: &'static str,
}

impl HeaderGuard {
    fn new(name: &'static str, value: &'static str) -> Self {
        Self { name, value }
    }
}

impl Guard for HeaderGuard {
    fn check(&self, ctx: &GuardContext) -> bool {
        ctx.head()
            .headers()
            .get(self.name)
            .map(|v| v == self.value)
            .unwrap_or(false)
    }
}

App::new()
    .route(
        "/admin",
        web::route()
            .guard(HeaderGuard::new("X-Admin-Key", "secret"))
            .to(admin_handler)
    )

认证守卫 #

rust
struct AuthGuard {
    expected_token: String,
}

impl AuthGuard {
    fn new(token: &str) -> Self {
        Self {
            expected_token: token.to_string(),
        }
    }
}

impl Guard for AuthGuard {
    fn check(&self, ctx: &GuardContext) -> bool {
        ctx.head()
            .headers()
            .get(header::AUTHORIZATION)
            .and_then(|v| v.to_str().ok())
            .map(|v| v == format!("Bearer {}", self.expected_token))
            .unwrap_or(false)
    }
}

App::new()
    .route(
        "/protected",
        web::route()
            .guard(AuthGuard::new("my-secret-token"))
            .to(protected_handler)
    )

IP 地址守卫 #

rust
use std::net::IpAddr;

struct IpWhitelistGuard {
    allowed_ips: Vec<IpAddr>,
}

impl IpWhitelistGuard {
    fn new(ips: Vec<&str>) -> Self {
        Self {
            allowed_ips: ips
                .into_iter()
                .filter_map(|ip| ip.parse().ok())
                .collect(),
        }
    }
}

impl Guard for IpWhitelistGuard {
    fn check(&self, ctx: &GuardContext) -> bool {
        ctx.head()
            .peer_addr()
            .map(|addr| self.allowed_ips.contains(&addr.ip()))
            .unwrap_or(false)
    }
}

App::new()
    .route(
        "/admin",
        web::route()
            .guard(IpWhitelistGuard::new(vec!["127.0.0.1", "192.168.1.100"]))
            .to(admin_handler)
    )

守卫工厂函数 #

创建可复用守卫 #

rust
fn admin_guard() -> impl Guard {
    guard::All(guard::Get())
        .and(Header("X-Admin-Key", "secret"))
}

App::new()
    .route("/admin", web::route().guard(admin_guard()).to(admin_handler))
    .route("/admin/users", web::route().guard(admin_guard()).to(list_users))

动态守卫 #

rust
fn api_key_guard(api_key: &str) -> impl Guard + '_ {
    move |ctx: &GuardContext| {
        ctx.head()
            .headers()
            .get("X-Api-Key")
            .map(|v| v == api_key)
            .unwrap_or(false)
    }
}

范围守卫 #

Scope 级别守卫 #

rust
App::new()
    .service(
        web::scope("/api")
            .guard(guard::Header("X-Api-Version", "1"))
            .route("/users", web::get().to(get_users))
            .route("/posts", web::get().to(get_posts))
    )

Resource 级别守卫 #

rust
App::new()
    .service(
        web::resource("/admin")
            .guard(Header("X-Admin-Key", "secret"))
            .route(web::get().to(admin_panel))
            .route(web::post().to(admin_action))
    )

守卫与中间件对比 #

特性 守卫 中间件
执行时机 路由匹配前 路由匹配后
主要用途 请求过滤 请求/响应处理
返回值 bool Response
可修改请求
可修改响应

完整示例 #

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

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

struct AuthGuard;

impl Guard for AuthGuard {
    fn check(&self, ctx: &GuardContext) -> bool {
        ctx.head()
            .headers()
            .get("Authorization")
            .and_then(|v| v.to_str().ok())
            .map(|v| v.starts_with("Bearer "))
            .unwrap_or(false)
    }
}

struct AdminGuard;

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

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

#[actix_web::get("/profile")]
async fn profile() -> impl Responder {
    HttpResponse::Ok().json(User {
        id: 1,
        name: "Current User".to_string(),
    })
}

#[actix_web::get("/admin/users")]
async fn admin_list_users() -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "users": [
            {"id": 1, "name": "Alice", "role": "user"},
            {"id": 2, "name": "Bob", "role": "admin"}
        ]
    }))
}

#[actix_web::post("/admin/users")]
async fn admin_create_user(body: web::Json<CreateUser>) -> impl Responder {
    HttpResponse::Created().json(body.into_inner())
}

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

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(list_users)
            .service(
                web::scope("/protected")
                    .guard(AuthGuard)
                    .service(profile)
            )
            .service(
                web::scope("/admin")
                    .guard(AdminGuard)
                    .service(admin_list_users)
                    .service(admin_create_user)
            )
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

测试守卫 #

bash
# 普通请求
curl http://localhost:8080/users

# 需要认证的请求
curl -H "Authorization: Bearer token" http://localhost:8080/protected/profile

# 需要管理员权限的请求
curl -H "X-Admin-Key: admin-secret" http://localhost:8080/admin/users

守卫最佳实践 #

1. 单一职责 #

每个守卫只检查一个条件:

rust
struct MethodGuard;
struct AuthGuard;
struct AdminGuard;

2. 组合使用 #

使用 AllAny 组合守卫:

rust
guard::All(AuthGuard).and(AdminGuard)

3. 错误处理 #

守卫失败时返回适当的错误:

rust
App::new()
    .default_service(
        web::route()
            .guard(guard::Not(guard::Get()))
            .to(|| HttpResponse::MethodNotAllowed())
    )

下一步 #

现在你已经掌握了路由守卫,继续学习 请求参数,深入了解请求处理!

最后更新:2026-03-29