从零开始学Axum:Rust Web应用开发实战教程

从零开始学 Axum:Rust Web 应用开发实战教程

Axum 是一个由 Tokio 团队开发的符合人体工程学的、模块化的 Rust Web 框架。它充分利用了 Rust 的类型系统、async/await 特性以及 Tokio 运行时,提供了一种高性能、安全且易于开发的方式来构建 Web 应用程序和 API。本教程将带你从零开始,一步步学习 Axum 的核心概念,并通过实战示例掌握其用法。

为什么选择 Axum?

在选择 Web 框架之前,了解其优势至关重要。Axum 相较于其他 Rust Web 框架(如 Rocket、Actix-web)或其他语言的框架,具有以下特点:

  • 与 Tokio 生态紧密集成: Axum 构建于 Tokio 之上,可以直接利用 Tokio 强大的异步运行时、tracing(日志和追踪)和 Tower(中间件)生态系统。
  • 类型安全: Rust 的类型系统保证了 Axum 应用的安全性。类型错误会在编译时被捕获,避免了运行时意外。
  • 符合人体工程学: Axum 的 API 设计注重开发体验。它使用简洁的函数和 trait 来定义路由、处理程序和中间件,代码可读性高。
  • 模块化: Axum 的功能被分解为多个独立的 crate,你可以按需选择所需的功能,避免不必要的依赖。
  • 高性能: 基于 Tokio 和 Rust 的异步特性,Axum 能够处理大量并发请求,提供出色的性能。
  • 易于测试: Axum 的设计使得单元测试和集成测试变得简单。

准备工作:环境搭建

在开始编写代码之前,你需要安装 Rust 和 Cargo(Rust 的包管理器和构建工具)。

  1. 安装 Rust:

    最简单的方法是使用 Rust 官方提供的 rustup 安装程序:

    bash
    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

    按照提示操作即可。安装完成后,重启终端或运行以下命令使环境变量生效:

    bash
    source $HOME/.cargo/env

    验证安装:

    bash
    rustc --version
    cargo --version

  2. 创建新项目:

    使用 Cargo 创建一个新的 Rust 项目:

    bash
    cargo new axum-tutorial
    cd axum-tutorial

第一个 Axum 应用:Hello, World!

让我们从经典的 "Hello, World!" 开始,了解 Axum 的基本结构。

  1. 添加依赖:

    Cargo.toml 文件中添加 axumtokio 作为依赖:

    toml
    [dependencies]
    axum = "0.7" # 注意:版本号可能需要更新,请查看crates.io获取最新版本
    tokio = { version = "1", features = ["full"] }

    这里 tokiofeatures = ["full"] 启用了 Tokio 的所有功能.

  2. 编写代码:

    打开 src/main.rs 文件,替换为以下代码:

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

    [tokio::main]

    async fn main() {
    // 构建我们的应用,包含一个根路由 ("/"),它会响应 "Hello, World!"
    let app = Router::new().route("/", get(|| async { "Hello, World!" }));

    // 运行服务器
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
    

    }
    ```

  3. 代码解读:

    • use axum::{routing::get, Router};:导入必要的模块。Router 用于定义路由,get 用于处理 GET 请求。
    • #[tokio::main]:这是 Tokio 提供的宏,用于标记异步 main 函数的入口点。
    • Router::new().route("/", get(|| async { "Hello, World!" }))
      • Router::new():创建一个新的 Router 实例。
      • .route("/", get(|| async { "Hello, World!" })):定义一个路由。
        • /:表示根路径。
        • get(...):指定该路由处理 GET 请求。
        • || async { "Hello, World!" }:这是一个闭包,作为处理函数。它是一个异步函数,返回字符串 "Hello, World!"。
    • tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();: 绑定到本地地址和端口3000,并开始监听。0.0.0.0 表示监听所有可用的网络接口。
    • axum::serve(listener, app).await.unwrap();: 使用axum::serve函数启动HTTP服务器.
  4. 运行应用:

    在终端中运行:

    bash
    cargo run

    如果一切顺利,你会看到类似以下的输出:

    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
    Running `target/debug/axum-tutorial`

    现在,在浏览器中访问 http://localhost:3000,你将看到 "Hello, World!"。

路由和处理函数

Axum 的核心是路由和处理函数。路由将请求的 URL 映射到相应的处理函数,处理函数负责生成响应。

路由

Axum 使用 Router 类型来定义路由。你可以使用 .route() 方法添加路由,并指定 HTTP 方法(如 getpostputdelete 等)和处理函数。

```rust
use axum::{
routing::{get, post},
Router,
};

async fn get_handler() -> &'static str {
"This is a GET request handler"
}

async fn post_handler() -> &'static str {
"This is a POST request handler"
}

[tokio::main]

async fn main() {
let app = Router::new()
.route("/get", get(get_handler))
.route("/post", post(post_handler));

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

}

```

处理函数

处理函数是异步函数,它们接收请求并返回响应。Axum 提供了多种方式来定义处理函数:

  • 普通异步函数: 如上面的示例,直接返回一个字符串。
  • 返回 impl IntoResponse 的类型: Axum 定义了 IntoResponse trait,许多类型都实现了它,包括字符串、元组、ResultJson 等。你可以返回这些类型,Axum 会自动将它们转换为 HTTP 响应。

```rust
use axum::{
response::{Html, IntoResponse, Json},
routing::get,
Router,
};
use serde::Serialize;

[derive(Serialize)]

struct MyData {
message: String,
}

async fn html_handler() -> Html<&'static str> {
Html("

Hello, Axum!

")
}

async fn json_handler() -> Json {
Json(MyData {
message: "Hello from JSON".to_string(),
})
}
async fn plain_text_handler() -> impl IntoResponse {
"This is plain text"
}
async fn tuple_handler() -> impl IntoResponse {
(StatusCode::CREATED, "Resource created")
}
use axum::http::StatusCode;

[tokio::main]

async fn main() {
let app = Router::new()
.route("/html", get(html_handler))
.route("/json", get(json_handler))
.route("/plain", get(plain_text_handler))
.route("/tuple", get(tuple_handler));

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

}

```

提取器(Extractors)

Axum 使用提取器从请求中提取数据。提取器是实现了 FromRequestPartsFromRequest trait 的类型。常见的提取器包括:

  • Path:从 URL 路径中提取参数。
  • Query:从查询字符串中提取参数。
  • Json:从请求体中提取 JSON 数据。
  • Form:从请求体中提取表单数据。
  • State:访问共享的应用状态。
  • Request:获取整个http::Request对象.

```rust
use axum::{
extract::{Path, Query},
routing::get,
Router,
};
use serde::Deserialize;
use std::collections::HashMap;

[derive(Deserialize)]

struct Params {
id: i32,
}

// 使用 Path 提取 URL 参数
async fn path_handler(Path(id): Path) -> String {
format!("User ID: {}", id)
}
//使用HashMap 泛型从查询字符串提取参数
async fn query_handler(Query(params): Query>) -> String {
format!("Query parameters: {:?}", params)
}

// 使用自定义结构体从查询字符串提取参数
async fn query_handler_struct(Query(params):Query) -> String{
format!("Query parameters: {:?}", params.id)
}

[tokio::main]

async fn main() {
let app = Router::new()
.route("/users/:id", get(path_handler))
.route("/search", get(query_handler))
.route("/search_struct", get(query_handler_struct));

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

}
``
在浏览器分别访问
http://localhost:3000/users/123,http://localhost:3000/search?name=John&age=30http://localhost:3000/search_struct?id=1`查看结果。

中间件(Middleware)

中间件是位于路由和处理函数之间的函数,它们可以修改请求或响应,或者执行一些额外的操作,如身份验证、日志记录、CORS 处理等。Axum 使用 Tower 的 ServiceLayer trait 来实现中间件。

```rust
use axum::{
middleware::{self, Next},
response::Response,
routing::get,
Router,
};
use http::{Request, StatusCode};

async fn auth_middleware(req: Request, next: Next) -> Result {
let auth_header = req.headers()
.get("Authorization")
.and_then(|header| header.to_str().ok());

match auth_header {
    Some(auth_header) if auth_header == "Bearer my_secret_token" => Ok(next.run(req).await),
    _ => Err(StatusCode::UNAUTHORIZED),
}

}

[tokio::main]

async fn main() {
let app = Router::new()
.route("/protected", get(|| async { "Protected resource" }))
.layer(middleware::from_fn(auth_middleware)); // 使用 from_fn 将中间件函数转换为 Layer

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

}

```

在这个例子中,auth_middleware 检查请求头中的 Authorization 字段,只有当它等于 "Bearer my_secret_token" 时才允许访问 /protected 路由。 使用curl -H "Authorization: Bearer my_secret_token" http://localhost:3000/protected测试.

状态管理

Axum 允许你在应用中共享状态。你可以使用 State 提取器来访问共享状态。

```rust
use axum::{
extract::State,
routing::get,
Router,
};
use std::sync::Arc;
use tokio::sync::Mutex;

[derive(Clone)]

struct AppState {
count: Arc>,
}

async fn increment_counter(State(state): State) -> String {
let mut count = state.count.lock().await;
count += 1;
format!("Counter: {}",
count)
}

[tokio::main]

async fn main() {
let state = AppState {
count: Arc::new(Mutex::new(0)),
};

let app = Router::new()
    .route("/increment", get(increment_counter))
    .with_state(state);  // 使用 with_state 将状态添加到 Router

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

}
```

在这个例子中,AppState 包含一个计数器。increment_counter 处理函数使用 State 提取器获取 AppState,并增加计数器的值。 注意:Arc 和 Mutex 用于在多线程环境中安全地共享和修改状态。

错误处理

Axum 提供了灵活的错误处理机制。你可以使用 Result 类型来表示处理函数可能发生的错误,并使用 .map_err().or_else() 方法来处理错误。 此外也可以自定义错误类型,只要实现了IntoResponse trait.

```rust
use axum::{
response::{IntoResponse, Response},
routing::get,
Router,
Json,
http::StatusCode,
};
use serde::Serialize;

[derive(Debug)]

enum AppError {
UserNotFound,
InternalServerError,
}
// Tell axum how to convert AppError into a response.
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, error_message) = match self {
AppError::UserNotFound => (StatusCode::NOT_FOUND, "User not found"),
AppError::InternalServerError => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"),
};
let body = Json(ErrorMessage {
error: error_message.to_string(),
});
(status, body).into_response()
}
}

// This enables using ? on functions that return Result<_, anyhow::Error> to turn them into
// Result<_, AppError>. That way you don't need to do that manually.
impl From for AppError {
fn from(_err: anyhow::Error) -> Self {
AppError::InternalServerError
}
}

[derive(Serialize)]

struct ErrorMessage{
error:String
}

async fn get_user(user_id: u32) -> Result {
if user_id == 1 {
Ok("John Doe".to_string())
} else {
Err(AppError::UserNotFound)
}
}
use axum::extract::Path;
async fn handler(Path(user_id): Path) -> Result {
get_user(user_id)
}

[tokio::main]

async fn main() {
let app = Router::new().route("/users/:id", get(handler));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
``
访问
http://localhost:3000/users/1http://localhost:3000/users/2`查看不同的结果。

总结

本教程介绍了 Axum 框架的基础知识,包括环境搭建、路由、处理函数、提取器、中间件、状态管理和错误处理。通过这些知识,你应该能够开始构建自己的 Rust Web 应用程序。

Axum 的生态系统还在不断发展,还有许多高级特性和工具等待你去探索,例如:

  • 数据库集成: 使用 sqlxdiesel 等库连接数据库。
  • 模板引擎: 使用 askamatera 等库渲染 HTML 模板。
  • WebSockets: 使用 axum-extra crate 支持 WebSocket 连接。
  • OpenAPI/Swagger 集成: 使用 utoipa 自动生成 API 文档。
  • 部署: 将 Axum 应用部署到服务器或云平台。

希望这篇教程能帮助你入门 Axum! 祝你使用 Rust 和 Axum 构建出色的 Web 应用!

THE END