从零开始学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 的包管理器和构建工具)。
-
安装 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 -
创建新项目:
使用 Cargo 创建一个新的 Rust 项目:
bash
cargo new axum-tutorial
cd axum-tutorial
第一个 Axum 应用:Hello, World!
让我们从经典的 "Hello, World!" 开始,了解 Axum 的基本结构。
-
添加依赖:
在
Cargo.toml
文件中添加axum
和tokio
作为依赖:toml
[dependencies]
axum = "0.7" # 注意:版本号可能需要更新,请查看crates.io获取最新版本
tokio = { version = "1", features = ["full"] }
这里tokio
的features = ["full"]
启用了 Tokio 的所有功能. -
编写代码:
打开
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();
}
``` -
代码解读:
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服务器.
-
运行应用:
在终端中运行:
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 方法(如 get
、post
、put
、delete
等)和处理函数。
```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,许多类型都实现了它,包括字符串、元组、Result
、Json
等。你可以返回这些类型,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 使用提取器从请求中提取数据。提取器是实现了 FromRequestParts
或 FromRequest
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
format!("User ID: {}", id)
}
//使用HashMap 泛型从查询字符串提取参数
async fn query_handler(Query(params): Query
format!("Query parameters: {:?}", params)
}
// 使用自定义结构体从查询字符串提取参数
async fn query_handler_struct(Query(params):Query
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=30和
http://localhost:3000/search_struct?id=1`查看结果。
中间件(Middleware)
中间件是位于路由和处理函数之间的函数,它们可以修改请求或响应,或者执行一些额外的操作,如身份验证、日志记录、CORS 处理等。Axum 使用 Tower 的 Service
和 Layer
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
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
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
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/1
访问和
http://localhost:3000/users/2`查看不同的结果。
总结
本教程介绍了 Axum 框架的基础知识,包括环境搭建、路由、处理函数、提取器、中间件、状态管理和错误处理。通过这些知识,你应该能够开始构建自己的 Rust Web 应用程序。
Axum 的生态系统还在不断发展,还有许多高级特性和工具等待你去探索,例如:
- 数据库集成: 使用
sqlx
或diesel
等库连接数据库。 - 模板引擎: 使用
askama
或tera
等库渲染 HTML 模板。 - WebSockets: 使用
axum-extra
crate 支持 WebSocket 连接。 - OpenAPI/Swagger 集成: 使用
utoipa
自动生成 API 文档。 - 部署: 将 Axum 应用部署到服务器或云平台。
希望这篇教程能帮助你入门 Axum! 祝你使用 Rust 和 Axum 构建出色的 Web 应用!