15.2 使用 Axum 或 Actix-web 构建 REST API

在 Rust 的 Web 框架生态中,AxumActix-web 是两个主流选择,均基于 tokio 异步运行时,性能出色且社区活跃。本节将以 Axum 为主进行演示(因其与 tower 生态深度集成、API 简洁),同时简要对比 Actix-web 的差异,帮助你根据项目需求做出选择。

为什么选择 Axum?

Axum 由 Tokio 团队维护,设计理念强调:

  • 基于 tower 的中间件模型,兼容大量现有组件;
  • 利用 Rust 的类型系统提供路由安全(如路径参数类型检查);
  • tracing 无缝集成,便于可观测性;
  • 无宏 DSL,使用函数和组合构建路由。

创建基本服务

首先添加依赖(Cargo.toml):

[dependencies]
axum = "0.7"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

src/main.rs 中启动一个简单服务:

use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/health", get(health_check));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn health_check() -> &'static str {
    "OK"
}

运行后,访问 http://localhost:3000/health 将返回 "OK"

实现任务管理 API

假设我们已定义好 Task 模型和相关 DTO(见 15.1 节),现在在 handlers/tasks.rs 中实现 CRUD 操作:

use axum::{
    extract::{Path, State},
    response::{IntoResponse, Response},
    Json,
};
use serde::{Deserialize, Serialize};
use crate::{error::AppError, models::task::{CreateTask, Task}, services::task_service::TaskService};

pub async fn create_task(
    State(service): State<TaskService>,
    Json(payload): Json<CreateTask>,
) -> Result<Json<Task>, AppError> {
    let task = service.create_task(payload).await?;
    Ok(Json(task))
}

pub async fn get_task(
    State(service): State<TaskService>,
    Path(id): Path<i64>,
) -> Result<Json<Task>, AppError> {
    let task = service.get_task(id).await?;
    Ok(Json(task))
}

// 其他 handler 略

注意:

  • 使用 State<T> 提取共享应用状态(如 service);
  • 请求体通过 Json<T> 自动反序列化;
  • 路径参数通过 Path<T> 提取;
  • 错误统一由 AppError 处理(见 15.5 节)。

注册路由

main.rs 或单独的 routes.rs 中组装路由:

use axum::Router;
use std::sync::Arc;

let shared_service = Arc::new(TaskService::new(repo));
let app = Router::new()
    .route("/tasks", post(handlers::tasks::create_task))
    .route("/tasks/:id", get(handlers::tasks::get_task))
    .with_state(shared_service);

Axum 的 with_state 允许将任意类型注入到 handler 中,支持多状态组合(如数据库连接池 + 配置)。

与 Actix-web 的简要对比

若选择 Actix-web,代码风格略有不同:

// Actix-web 示例
use actix_web::{web, HttpResponse, Result};

async fn create_task(
    payload: web::Json<CreateTask>,
    service: web::Data<TaskService>,
) -> Result<HttpResponse> {
    let task = service.create_task(payload.into_inner()).await?;
    Ok(HttpResponse::Ok().json(task))
}

// 注册
App::new()
    .app_data(web::Data::new(service))
    .service(web::resource("/tasks").route(web::post().to(create_task)))

主要差异:

  • Actix-web 使用 web::Data<T> 注入状态;
  • 路由通过 Appweb::resource 构建;
  • 错误处理需实现 ResponseError trait。

两者性能接近,Axum 更贴近现代 Rust 的泛型和 trait 设计,而 Actix-web 文档更成熟、功能更“全栈”(如内置 WebSocket、HTTP/2 支持)。

小结

无论选择 Axum 还是 Actix-web,Rust 的 Web 框架都提供了类型安全、高性能的 API 构建能力。本节以 Axum 为例,展示了如何定义 handler、提取请求数据、注入服务依赖并组织路由。下一步,我们将把数据库集成进来,使 API 具备持久化能力。

#Rust 入门教程 分享于 5 天前

内容由 AI 创作和分享,仅供参考