请求参数 #

什么是提取器? #

提取器(Extractor)是 Actix Web 中从请求中提取数据的组件。每个提取器都实现了 FromRequest trait,可以自动从请求中解析数据并传递给处理函数。

text
┌─────────────────────────────────────────────────────────────┐
│                      提取器工作流程                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  HTTP 请求                                                    │
│         │                                                    │
│         ▼                                                    │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   提取器                              │   │
│  │  从请求中提取数据                                     │   │
│  │  - Path: 路径参数                                    │   │
│  │  - Query: 查询参数                                   │   │
│  │  - Json: JSON 请求体                                 │   │
│  │  - Form: 表单数据                                    │   │
│  │  - Header: 请求头                                    │   │
│  └─────────────────────────────────────────────────────┘   │
│         │                                                    │
│         ▼                                                    │
│  处理函数参数                                                 │
│                                                              │
└─────────────────────────────────────────────────────────────┘

内置提取器 #

Path - 路径参数 #

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

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

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

Query - 查询参数 #

rust
#[derive(Deserialize)]
struct SearchQuery {
    q: String,
    page: Option<u32>,
    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,
        "page": query.page.unwrap_or(1),
        "limit": query.limit.unwrap_or(10)
    }))
}

Json - JSON 请求体 #

rust
use serde::{Deserialize, Serialize};

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

#[actix_web::post("/users")]
async fn create_user(user: web::Json<User>) -> impl Responder {
    HttpResponse::Created().json(user.into_inner())
}

Form - 表单数据 #

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,
        "success": true
    }))
}

Bytes - 原始字节 #

rust
#[actix_web::post("/raw")]
async fn raw_body(body: web::Bytes) -> impl Responder {
    HttpResponse::Ok()
        .content_type("application/octet-stream")
        .body(body)
}

String - 字符串请求体 #

rust
#[actix_web::post("/text")]
async fn text_body(body: String) -> impl Responder {
    HttpResponse::Ok()
        .content_type("text/plain")
        .body(format!("Received: {}", body))
}

高级提取器 #

HttpRequest - 原始请求 #

rust
use actix_web::HttpRequest;

#[actix_web::get("/info")]
async fn request_info(req: HttpRequest) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "method": req.method().as_str(),
        "path": req.path(),
        "query": req.query_string(),
        "headers": req.headers().iter().map(|(k, v)| (k.to_string(), v.to_str().unwrap_or(""))).collect::<Vec<_>>()
    }))
}

Header - 请求头 #

rust
use actix_web::http::header;

#[actix_web::get("/headers")]
async fn headers(req: HttpRequest) -> impl Responder {
    let content_type = req.headers().get(header::CONTENT_TYPE);
    let user_agent = req.headers().get(header::USER_AGENT);
    let authorization = req.headers().get(header::AUTHORIZATION);
    
    HttpResponse::Ok().json(serde_json::json!({
        "content_type": content_type.map(|v| v.to_str().unwrap_or("")),
        "user_agent": user_agent.map(|v| v.to_str().unwrap_or("")),
        "authorization": authorization.map(|v| v.to_str().unwrap_or(""))
    }))
}
rust
use actix_web::cookie::Cookie;

#[actix_web::get("/cookie")]
async fn get_cookie(req: HttpRequest) -> impl Responder {
    let cookie = req.cookie("session");
    
    HttpResponse::Ok().json(serde_json::json!({
        "session": cookie.map(|c| c.value())
    }))
}

#[actix_web::post("/cookie")]
async fn set_cookie() -> impl Responder {
    let cookie = Cookie::build("session", "abc123")
        .path("/")
        .secure(true)
        .http_only(true)
        .finish();
    
    HttpResponse::Ok()
        .cookie(cookie)
        .body("Cookie set")
}

Payload - 流式请求体 #

rust
use actix_web::web::Payload;
use futures::StreamExt;

#[actix_web::post("/stream")]
async fn stream_body(mut payload: Payload) -> impl Responder {
    let mut bytes = Vec::new();
    
    while let Some(item) = payload.next().await {
        bytes.extend_from_slice(&item.unwrap());
    }
    
    HttpResponse::Ok().body(bytes)
}

组合提取器 #

多个提取器 #

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

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

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

#[actix_web::put("/users/{id}")]
async fn update_user(
    path: web::Path<UserPath>,
    query: web::Query<UserQuery>,
    body: web::Json<UpdateUser>,
    req: HttpRequest,
) -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "id": path.id,
        "verbose": query.verbose.unwrap_or(false),
        "name": body.name,
        "email": body.email,
        "content_type": req.headers().get("content-type").and_then(|v| v.to_str().ok())
    }))
}

自定义提取器 #

实现 FromRequest #

rust
use actix_web::{dev::Payload, Error, FromRequest, HttpRequest};
use futures::future::{ready, Ready};

struct ApiKey(String);

impl FromRequest for ApiKey {
    type Error = Error;
    type Future = Ready<Result<Self, Self::Error>>;

    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
        match req.headers().get("X-Api-Key") {
            Some(key) => ready(Ok(ApiKey(key.to_str().unwrap_or("").to_string()))),
            None => ready(Err(actix_web::error::ErrorBadRequest("Missing API key"))),
        }
    }
}

#[actix_web::get("/protected")]
async fn protected(api_key: ApiKey) -> impl Responder {
    HttpResponse::Ok().body(format!("API Key: {}", api_key.0))
}

带状态的自定义提取器 #

rust
use actix_web::web::Data;
use std::sync::Arc;

struct AppState {
    valid_api_keys: Vec<String>,
}

struct AuthenticatedUser {
    user_id: String,
}

impl FromRequest for AuthenticatedUser {
    type Error = Error;
    type Future = Ready<Result<Self, Self::Error>>;

    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
        let state = req.app_data::<Data<Arc<AppState>>>();
        
        match state {
            Some(state) => {
                let key = req.headers().get("X-Api-Key")
                    .and_then(|v| v.to_str().ok())
                    .unwrap_or("");
                
                if state.valid_api_keys.contains(&key.to_string()) {
                    ready(Ok(AuthenticatedUser {
                        user_id: "user_123".to_string(),
                    }))
                } else {
                    ready(Err(actix_web::error::ErrorUnauthorized("Invalid API key")))
                }
            }
            None => ready(Err(actix_web::error::ErrorInternalServerError("State not found"))),
        }
    }
}

提取器配置 #

JSON 配置 #

rust
use actix_web::web::JsonConfig;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .app_data(JsonConfig::default()
                .limit(4096)
                .error_handler(|err, _| {
                    actix_web::error::ErrorBadRequest(serde_json::json!({
                        "error": err.to_string()
                    }))
                })
            )
            .route("/users", web::post().to(create_user))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Query 配置 #

rust
use actix_web::web::QueryConfig;

App::new()
    .app_data(QueryConfig::default()
        .error_handler(|err, _| {
            actix_web::error::ErrorBadRequest(serde_json::json!({
                "error": "Invalid query parameters",
                "details": err.to_string()
            }))
        })
    )

提取器总结 #

提取器 说明 用途
web::Path<T> 路径参数 /users/{id}
web::Query<T> 查询参数 ?page=1&limit=10
web::Json<T> JSON 请求体 API 请求
web::Form<T> 表单数据 表单提交
web::Bytes 原始字节 文件上传
String 字符串请求体 文本处理
HttpRequest 原始请求 获取完整请求信息
web::Payload 流式请求体 大文件上传
自定义提取器 自定义逻辑 认证、验证等

完整示例 #

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

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

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

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

#[derive(Deserialize)]
struct ListQuery {
    page: Option<u32>,
    per_page: Option<u32>,
    sort: 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"),
        "users": []
    }))
}

#[actix_web::get("/users/{id}")]
async fn get_user(
    path: web::Path<UserId>,
    query: web::Query<UserQuery>,
    req: HttpRequest,
) -> impl Responder {
    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<UserId>,
    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<UserId>) -> impl Responder {
    HttpResponse::NoContent().finish()
}

#[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
}

下一步 #

现在你已经掌握了请求参数提取,继续学习 表单数据,深入了解表单处理!

最后更新:2026-03-29