Axum 是 Rust 生态中基于 Tokio 异步运行时 和 Tower 中间件体系打造的高性能 Web 框架,以"类型安全、无宏入侵、轻量高效"为核心优势,广泛应用于云原生、微服务、API 网关等场景。它摒弃了传统 Web 框架的宏魔法,完全依赖 Rust 的类型系统实现路由匹配、请求解析、响应处理,兼顾了开发效率与运行性能。
本文将从环境搭建、核心概念、路由设计、请求处理、中间件开发到生产级实战,全方位拆解 Axum 的使用技巧,每个知识点均配套可运行的示例代码,帮助开发者从入门到精通,快速构建高性能的 Rust Web 应用。
一、环境准备与项目初始化
1.1 前置条件
- 安装 Rust 环境:确保
rustc版本 ≥ 1.64(Axum 对 Rust 版本有最低要求),可通过rustup update升级。 - 熟悉 Tokio 异步编程:Axum 基于 Tokio 运行时,需掌握
async/await语法。 - 了解 Tower 中间件:Axum 底层复用 Tower 生态,中间件设计与 Tower 完全兼容。
1.2 创建 Axum 项目
首先创建一个新的 Rust 项目,并添加 Axum 及相关依赖:
bash
cargo new axum-demo && cd axum-demo
修改 Cargo.toml,添加核心依赖:
toml
[package]
name = "axum-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
# Axum 核心依赖
axum = "0.7"
# Tokio 异步运行时(必须启用 full 特性)
tokio = { version = "1.0", features = ["full"] }
# HTTP 请求/响应类型定义
http = "1.0"
# 日志处理
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# JSON 序列化/反序列化
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
1.3 编写第一个 Axum 应用
创建一个最简的 HTTP 服务,监听 0.0.0.0:3000,提供一个根路由 /:
rust
use axum::{routing::get, Router, Server};
use std::net::SocketAddr;
// 根路由处理函数:返回 "Hello, Axum!"
async fn root_handler() -> &'static str {
"Hello, Axum!"
}
#[tokio::main]
async fn main() {
// 初始化日志
tracing_subscriber::fmt::init();
// 构建路由:将 GET 请求 / 映射到 root_handler
let app = Router::new().route("/", get(root_handler));
// 定义监听地址
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
println!("服务器运行在 http://{}", addr);
// 启动服务器
Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
运行项目:
bash
cargo run
访问 http://localhost:3000,即可看到 Hello, Axum!,标志着第一个 Axum 应用搭建成功。
二、核心概念:路由、提取器与响应
Axum 的核心设计围绕 路由(Router) 、提取器(Extractor) 和 响应(Response) 三大组件展开,三者协同工作,完成从请求接收、参数解析到响应返回的全流程。
2.1 路由(Router):请求分发的核心
Router 是 Axum 的路由管理器,负责将不同 HTTP 方法和路径映射到对应的处理函数。它支持路由嵌套 、方法匹配 、路径参数等功能。
2.1.1 基本路由与 HTTP 方法
Axum 支持 get、post、put、delete 等常见 HTTP 方法,通过 route 方法绑定处理函数:
rust
use axum::{routing::{get, post}, Router};
async fn get_handler() -> &'static str {
"这是 GET 请求"
}
async fn post_handler() -> &'static str {
"这是 POST 请求"
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/get", get(get_handler))
.route("/post", post(post_handler));
// 启动服务器...
}
2.1.2 路径参数
通过 /:param 语法定义路径参数,处理函数通过提取器 获取参数值。支持 String、u32、i64 等多种类型:
rust
use axum::{extract::Path, routing::get, Router};
use std::collections::HashMap;
// 提取单个路径参数
async fn user_handler(Path(user_id): Path<u32>) -> String {
format!("用户 ID:{}", user_id)
}
// 提取多个路径参数
async fn article_handler(Path((user_id, article_id)): Path<(u32, String)>) -> String {
format!("用户 {} 的文章:{}", user_id, article_id)
}
// 提取路径参数到 HashMap
async fn map_handler(Path(params): Path<HashMap<String, String>>) -> String {
format!("路径参数:{:?}", params)
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/user/:user_id", get(user_handler))
.route("/article/:user_id/:article_id", get(article_handler))
.route("/map/:k1/:k2", get(map_handler));
// 启动服务器...
}
测试路径:
http://localhost:3000/user/100→ 输出用户 ID:100http://localhost:3000/article/100/rust-axum→ 输出用户 100 的文章:rust-axum
2.1.3 路由嵌套
通过 nest 方法实现路由嵌套,适合按业务模块划分路由(如用户模块、文章模块),提升代码可读性:
rust
use axum::{routing::get, Router};
// 用户模块路由
fn user_routes() -> Router {
Router::new()
.route("/", get(|| async { "用户列表" }))
.route("/:id", get(|Path(id): Path<u32>| async move { format!("用户详情:{}", id) }))
}
// 文章模块路由
fn article_routes() -> Router {
Router::new()
.route("/", get(|| async { "文章列表" }))
.route("/:id", get(|Path(id): Path<String>| async move { format!("文章详情:{}", id) }))
}
#[tokio::main]
async fn main() {
let app = Router::new()
.nest("/user", user_routes())
.nest("/article", article_routes());
// 启动服务器...
}
测试嵌套路由:
http://localhost:3000/user→ 输出用户列表http://localhost:3000/article/rust-axum→ 输出文章详情:rust-axum
2.2 提取器(Extractor):请求参数的解析利器
提取器是 Axum 的灵魂特性,它允许处理函数通过函数参数的形式,自动从 HTTP 请求中提取所需数据,无需手动解析请求体或查询参数。Axum 内置了多种提取器,覆盖绝大多数场景。
2.2.1 常见内置提取器
| 提取器 | 作用 | 示例 |
|---|---|---|
Path<T> |
提取路径参数 | Path(user_id): Path<u32> |
Query<T> |
提取 URL 查询参数 | Query(params): Query<HashMap<String, String>> |
Json<T> |
提取 JSON 请求体并反序列化为 T |
Json(user): Json<User> |
Form<T> |
提取表单请求体(application/x-www-form-urlencoded) |
Form(form): Form<LoginForm> |
HeaderMap |
提取请求头 | headers: HeaderMap |
State<T> |
提取应用全局状态 | state: State<AppState> |
2.2.2 提取器实战:解析 JSON 请求体
定义一个用户结构体,通过 Json 提取器接收并解析 JSON 请求体:
rust
use axum::{extract::Json, routing::post, Router};
use serde::Deserialize;
// 定义用户结构体,派生 Deserialize 特性以支持 JSON 反序列化
#[derive(Deserialize)]
struct CreateUserRequest {
name: String,
age: u32,
email: String,
}
// 处理函数:提取 JSON 请求体
async fn create_user(Json(req): Json<CreateUserRequest>) -> String {
format!(
"创建用户成功:姓名={}, 年龄={}, 邮箱={}",
req.name, req.age, req.email
)
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/user", post(create_user));
// 启动服务器...
}
使用 curl 测试 POST 请求:
bash
curl -X POST -H "Content-Type: application/json" -d '{"name":"张三","age":25,"email":"zhangsan@example.com"}' http://localhost:3000/user
输出结果:创建用户成功:姓名=张三, 年龄=25, 邮箱=zhangsan@example.com
2.2.3 提取器实战:解析查询参数
通过 Query 提取器解析 URL 中的查询参数,支持自动反序列化为结构体:
rust
use axum::{extract::Query, routing::get, Router};
use serde::Deserialize;
#[derive(Deserialize)]
struct Pagination {
page: u32,
size: u32,
}
async fn list_users(Query(pagination): Query<Pagination>) -> String {
format!("查询用户列表:第 {} 页,每页 {} 条", pagination.page, pagination.size)
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/users", get(list_users));
// 启动服务器...
}
测试 URL:http://localhost:3000/users?page=1&size=10 → 输出 查询用户列表:第 1 页,每页 10 条
2.2.4 多提取器组合使用
一个处理函数可以同时使用多个提取器,Axum 会按顺序自动解析:
rust
use axum::{extract::{Path, Query}, routing::get, Router};
use serde::Deserialize;
#[derive(Deserialize)]
struct Filter {
keyword: String,
}
async fn search_articles(
Path(user_id): Path<u32>,
Query(filter): Query<Filter>,
) -> String {
format!("用户 {} 搜索文章:关键词={}", user_id, filter.keyword)
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/user/:user_id/articles", get(search_articles));
// 启动服务器...
}
测试 URL:http://localhost:3000/user/100/articles?keyword=rust → 输出 用户 100 搜索文章:关键词=rust
2.3 响应(Response):灵活的返回值处理
Axum 支持多种类型的返回值作为响应,无需手动构建 http::Response。常见的响应类型包括:
- 字符串、
&str→ 自动转为text/plain响应 Json<T>→ 自动转为application/json响应StatusCode→ 仅返回 HTTP 状态码(StatusCode, Json<T>)→ 返回状态码 + JSON 响应- 自定义响应体
2.3.1 基础响应类型
rust
use axum::{
extract::Path,
http::StatusCode,
response::Json,
routing::get,
Router,
};
use serde::Serialize;
#[derive(Serialize)]
struct User {
id: u32,
name: String,
}
async fn success_response() -> Json<User> {
Json(User {
id: 100,
name: "张三".to_string(),
})
}
async fn error_response() -> StatusCode {
StatusCode::NOT_FOUND
}
async fn custom_response(Path(id): Path<u32>) -> (StatusCode, String) {
if id == 100 {
(StatusCode::OK, "请求成功".to_string())
} else {
(StatusCode::BAD_REQUEST, "无效 ID".to_string())
}
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/success", get(success_response))
.route("/error", get(error_response))
.route("/custom/:id", get(custom_response));
// 启动服务器...
}
2.3.2 自定义响应头
通过 ResponseBuilder 可以自定义响应头,例如设置 Content-Type、Authorization 等:
rust
use axum::{
http::header,
response::IntoResponse,
routing::get,
Router,
};
async fn custom_header() -> impl IntoResponse {
(
[
(header::CONTENT_TYPE, "application/xml"),
(header::X_POWERED_BY, "Axum"),
],
"<user><id>100</id><name>张三</name></user>",
)
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/xml", get(custom_header));
// 启动服务器...
}
三、全局状态管理:State 提取器
在 Web 应用中,经常需要共享全局资源(如数据库连接池、配置信息、缓存客户端)。Axum 通过 State 提取器实现全局状态管理,状态会被所有路由共享,且支持类型安全的访问。
3.1 定义与注入全局状态
首先定义一个全局状态结构体,然后通过 Router::with_state 注入到应用中:
rust
use axum::{extract::State, routing::get, Router, Server};
use std::net::SocketAddr;
use std::sync::Arc;
// 定义全局状态:包含应用名称和版本
#[derive(Clone)]
struct AppState {
app_name: String,
app_version: String,
// 实际项目中可添加数据库连接池、Redis 客户端等
// db_pool: sqlx::PgPool,
}
// 提取全局状态并使用
async fn get_app_info(State(state): State<Arc<AppState>>) -> String {
format!(
"应用名称:{},版本:{}",
state.app_name, state.app_version
)
}
#[tokio::main]
async fn main() {
// 初始化全局状态,使用 Arc 实现线程安全的共享
let app_state = Arc::new(AppState {
app_name: "Axum Demo".to_string(),
app_version: "1.0.0".to_string(),
});
// 注入状态到路由
let app = Router::new()
.route("/info", get(get_app_info))
.with_state(app_state);
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
关键注意点:
- 状态结构体必须实现
Clone特性(Axum 内部会克隆状态传递给处理函数)。 - 对于重量级资源(如数据库连接池),需用
Arc包裹,避免频繁克隆的性能开销。 - 状态是不可变 的,若需修改状态,需配合
Mutex/RwLock等同步原语(异步场景推荐使用tokio::sync::Mutex)。
3.2 状态修改实战:计数器
通过 Arc + tokio::sync::Mutex 实现线程安全的可变状态:
rust
use axum::{extract::State, routing::get, Router, Server};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::Mutex;
// 定义包含计数器的全局状态
struct AppState {
counter: Mutex<u32>,
}
async fn increment_counter(State(state): State<Arc<AppState>>) -> String {
let mut counter = state.counter.lock().await;
*counter += 1;
format!("当前计数器值:{}", counter)
}
async fn get_counter(State(state): State<Arc<AppState>>) -> String {
let counter = state.counter.lock().await;
format!("当前计数器值:{}", counter)
}
#[tokio::main]
async fn main() {
let app_state = Arc::new(AppState {
counter: Mutex::new(0),
});
let app = Router::new()
.route("/increment", get(increment_counter))
.route("/counter", get(get_counter))
.with_state(app_state);
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
访问 http://localhost:3000/increment 可递增计数器,访问 http://localhost:3000/counter 可查看当前值。
四、中间件:请求/响应的拦截与处理
中间件是 Axum 处理请求生命周期的核心机制,它可以在请求到达处理函数之前拦截请求(如鉴权、日志、限流),或在响应返回客户端之前修改响应(如添加响应头、压缩响应体)。
Axum 中间件完全兼容 Tower 生态,可直接使用 Tower 提供的中间件,也可自定义中间件。
4.1 内置中间件:日志与压缩
首先演示如何使用 Tower 提供的 tower-http 中间件,实现请求日志和响应压缩。
添加依赖到 Cargo.toml:
toml
tower-http = { version = "0.5", features = ["logging", "compression"] }
使用日志和压缩中间件:
rust
use axum::{routing::get, Router, Server};
use std::net::SocketAddr;
use tower_http::{compression::CompressionLayer, logging::LoggingLayer};
async fn root() -> &'static str {
"Hello, Axum with Middleware!"
}
#[tokio::main]
async fn main() {
// 初始化日志
tracing_subscriber::fmt::init();
let app = Router::new()
.route("/", get(root))
// 添加日志中间件:记录所有请求的方法、路径、状态码等
.layer(LoggingLayer::new())
// 添加压缩中间件:自动压缩响应体(支持 gzip、br 等)
.layer(CompressionLayer::new());
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
运行项目后,控制台会输出请求日志,响应体也会被自动压缩。
4.2 自定义中间件:鉴权与限流
Axum 支持两种自定义中间件的方式:基于函数的中间件 (简单场景)和 基于 Tower Service 的中间件(复杂场景)。
4.2.1 基于函数的中间件:API 鉴权
实现一个简单的鉴权中间件,检查请求头中是否包含有效的 Authorization 令牌:
rust
use axum::{
body::Body,
extract::Request,
http::{header::AUTHORIZATION, StatusCode},
middleware::Next,
response::Response,
routing::get,
Router,
};
// 自定义鉴权中间件
async fn auth_middleware(mut req: Request<Body>, next: Next) -> Result<Response, StatusCode> {
// 从请求头中获取 Authorization
let auth_header = req.headers().get(AUTHORIZATION);
if let Some(token) = auth_header {
// 简单校验令牌是否为 "Bearer axum-token"
if token == "Bearer axum-token" {
// 令牌有效,继续处理请求
return Ok(next.run(req).await);
}
}
// 令牌无效,返回 401 未授权
Err(StatusCode::UNAUTHORIZED)
}
async fn protected_route() -> &'static str {
"这是受保护的路由,鉴权成功!"
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/protected", get(protected_route))
// 为特定路由添加中间件
.route_layer(axum::middleware::from_fn(auth_middleware));
// 启动服务器...
}
测试:
- 携带有效令牌:
curl -H "Authorization: Bearer axum-token" http://localhost:3000/protected→ 输出这是受保护的路由,鉴权成功! - 无令牌或无效令牌:返回
401 Unauthorized
4.2.2 全局中间件与路由级中间件
Axum 支持全局中间件 (作用于所有路由)和路由级中间件(仅作用于特定路由):
rust
use axum::{
middleware::from_fn,
routing::get,
Router,
};
// 全局日志中间件
async fn log_middleware(req: Request<Body>, next: Next) -> Result<Response, StatusCode> {
println!("请求路径:{}", req.uri().path());
Ok(next.run(req).await)
}
// 路由级鉴权中间件
async fn auth_middleware(req: Request<Body>, next: Next) -> Result<Response, StatusCode> {
// 鉴权逻辑...
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/public", get(|| async { "公共路由" }))
.route("/protected", get(protected_route))
// 路由级中间件
.route_layer(from_fn(auth_middleware))
// 全局中间件:作用于所有路由
.layer(from_fn(log_middleware));
// 启动服务器...
}
五、生产级实战:Axum + SQLx 构建 RESTful API
本节将结合 Axum 和 SQLx(Rust 生态的异步 SQL 工具),构建一个完整的用户管理 RESTful API,包含数据库连接 、CRUD 操作 、错误处理等生产级特性。
5.1 依赖准备
添加 SQLx 及 PostgreSQL 依赖到 Cargo.toml:
toml
sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-native-tls", "macros", "json"] }
dotenv = "0.15"
创建 .env 文件,配置数据库连接信息:
env
DATABASE_URL=postgres://username:password@localhost:5432/axum_demo
5.2 数据库初始化
创建 users 表,并编写 SQLx 迁移脚本:
bash
# 创建迁移目录
mkdir -p migrations
# 创建 0001_create_users_table.sql 迁移文件
echo "
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL,
age INT NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
" > migrations/0001_create_users_table.up.sql
echo "
DROP TABLE users;
" > migrations/0001_create_users_table.down.sql
运行迁移脚本,创建数据库表:
bash
sqlx migrate run --database-url postgres://username:password@localhost:5432/axum_demo
5.3 编写 RESTful API
实现用户的创建、查询、更新、删除(CRUD)操作:
rust
use axum::{
extract::{Json, Path, Query, State},
http::StatusCode,
middleware::from_fn,
response::IntoResponse,
routing::{delete, get, post, put},
Router, Server,
};
use dotenv::dotenv;
use serde::{Deserialize, Serialize};
use sqlx::{postgres::PgPoolOptions, PgPool};
use std::collections::HashMap;
use std::env;
use std::net::SocketAddr;
use std::sync::Arc;
// 定义全局状态:包含数据库连接池
struct AppState {
db_pool: PgPool,
}
// 用户结构体
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
struct User {
id: i32,
name: String,
age: i32,
email: String,
}
// 创建用户请求体
#[derive(Debug, Deserialize)]
struct CreateUserRequest {
name: String,
age: i32,
email: String,
}
// 更新用户请求体
#[derive(Debug, Deserialize)]
struct UpdateUserRequest {
name: Option<String>,
age: Option<i32>,
email: Option<String>,
}
// 错误响应结构体
#[derive(Debug, Serialize)]
struct ErrorResponse {
message: String,
}
// 自定义错误类型
enum AppError {
DatabaseError(sqlx::Error),
NotFound,
BadRequest(String),
}
// 实现 IntoResponse,将 AppError 转为 HTTP 响应
impl IntoResponse for AppError {
fn into_response(self) -> axum::response::Response {
let (status, message) = match self {
AppError::DatabaseError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
AppError::NotFound => (StatusCode::NOT_FOUND, "资源不存在".to_string()),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
};
(status, Json(ErrorResponse { message })).into_response()
}
}
// 转换 sqlx::Error 为 AppError
impl From<sqlx::Error> for AppError {
fn from(err: sqlx::Error) -> Self {
match err {
sqlx::Error::RowNotFound => AppError::NotFound,
_ => AppError::DatabaseError(err),
}
}
}
// 1. 创建用户
async fn create_user(
State(state): State<Arc<AppState>>,
Json(req): Json<CreateUserRequest>,
) -> Result<impl IntoResponse, AppError> {
let user = sqlx::query_as!(
User,
"INSERT INTO users (name, age, email) VALUES ($1, $2, $3) RETURNING *",
req.name,
req.age,
req.email
)
.fetch_one(&state.db_pool)
.await?;
Ok((StatusCode::CREATED, Json(user)))
}
// 2. 查询所有用户
async fn list_users(
State(state): State<Arc<AppState>>,
Query(params): Query<HashMap<String, String>>,
) -> Result<impl IntoResponse, AppError> {
let page = params.get("page").map(|p| p.parse::<i32>().unwrap_or(1)).unwrap_or(1);
let size = params.get("size").map(|s| s.parse::<i32>().unwrap_or(10)).unwrap_or(10);
let offset = (page - 1) * size;
let users = sqlx::query_as!(User, "SELECT * FROM users LIMIT $1 OFFSET $2", size, offset)
.fetch_all(&state.db_pool)
.await?;
Ok(Json(users))
}
// 3. 查询单个用户
async fn get_user(
State(state): State<Arc<AppState>>,
Path(user_id): Path<i32>,
) -> Result<impl IntoResponse, AppError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", user_id)
.fetch_one(&state.db_pool)
.await?;
Ok(Json(user))
}
// 4. 更新用户
async fn update_user(
State(state): State<Arc<AppState>>,
Path(user_id): Path<i32>,
Json(req): Json<UpdateUserRequest>,
) -> Result<impl IntoResponse, AppError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", user_id)
.fetch_one(&state.db_pool)
.await?;
let name = req.name.unwrap_or(user.name);
let age = req.age.unwrap_or(user.age);
let email = req.email.unwrap_or(user.email);
let updated_user = sqlx::query_as!(
User,
"UPDATE users SET name = $1, age = $2, email = $3 WHERE id = $4 RETURNING *",
name,
age,
email,
user_id
)
.fetch_one(&state.db_pool)
.await?;
Ok(Json(updated_user))
}
// 5. 删除用户
async fn delete_user(
State(state): State<Arc<AppState>>,
Path(user_id): Path<i32>,
) -> Result<impl IntoResponse, AppError> {
let result = sqlx::query!("DELETE FROM users WHERE id = $1", user_id)
.execute(&state.db_pool)
.await?;
if result.rows_affected() == 0 {
return Err(AppError::NotFound);
}
Ok(StatusCode::NO_CONTENT)
}
#[tokio::main]
async fn main() {
// 加载 .env 文件
dotenv().ok();
// 初始化日志
tracing_subscriber::fmt::init();
// 从环境变量获取数据库连接 URL
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL 未设置");
// 创建数据库连接池
let db_pool = PgPoolOptions::new()
.max_connections(5)
.connect(&database_url)
.await
.expect("无法连接到数据库");
// 初始化全局状态
let app_state = Arc::new(AppState { db_pool });
// 构建路由
let app = Router::new()
.route("/users", post(create_user))
.route("/users", get(list_users))
.route("/users/:id", get(get_user))
.route("/users/:id", put(update_user))
.route("/users/:id", delete(delete_user))
.with_state(app_state);
// 定义监听地址
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
println!("服务器运行在 http://{}", addr);
// 启动服务器
Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
5.4 测试 API
使用 curl 或 Postman 测试 CRUD 接口:
- 创建用户
bash
curl -X POST -H "Content-Type: application/json" -d '{"name":"张三","age":25,"email":"zhangsan@example.com"}' http://localhost:3000/users
- 查询所有用户
bash
curl http://localhost:3000/users?page=1&size=10
- 查询单个用户
bash
curl http://localhost:3000/users/1
- 更新用户
bash
curl -X PUT -H "Content-Type: application/json" -d '{"age":26}' http://localhost:3000/users/1
- 删除用户
bash
curl -X DELETE http://localhost:3000/users/1
六、进阶拓展:Axum 生态与性能优化
6.1 Axum 生态周边工具
- 身份认证 :使用
axum-login实现会话管理,jsonwebtoken实现 JWT 鉴权。 - OpenAPI 文档 :使用
utoipa+utoipa-swagger-ui自动生成 Swagger 文档。 - 缓存 :使用
tower-http::caching实现 HTTP 缓存,或redis客户端实现分布式缓存。 - 限流 :使用
tower-http::limit实现请求限流,防止服务过载。
6.2 性能优化技巧
- 使用
tokio-uring提升 I/O 性能 :对于高并发 I/O 场景,启用 Tokio 的uring特性,利用 Linux io_uring 机制减少系统调用开销。 - 合理配置连接池:数据库连接池大小不宜过大(建议等于 CPU 核心数),避免连接竞争。
- 启用响应压缩 :使用
tower-http::compression中间件压缩响应体,减少网络传输量。 - 避免阻塞异步任务 :所有阻塞操作(如同步文件读写、CPU 密集计算)必须放入
tokio::task::spawn_blocking。 - 使用
Release模式编译 :生产环境编译时添加--release标志,启用 Rust 编译器的优化。
七、Acxum 对比 Actix-web
Acxum 对比 Actix-web 最直观的区别:无宏入侵
一、"无宏入侵"的核心含义
"无宏入侵"是指 Axum 实现 Web 开发核心能力(路由定义、请求参数解析、响应处理等)时,完全基于 Rust 原生的类型系统、函数参数和 Trait 设计,不依赖自定义宏作为核心实现手段;开发者编写的代码贴近原生 Rust 语法,无"隐藏的宏魔法",逻辑直观、可调试性强。
与之相对,"宏入侵"是指框架将核心逻辑封装在自定义宏中,开发者必须通过编写宏(如 #[get]、#[post]、#[web::query] 等)才能使用框架核心功能------宏会改变代码的原生书写方式,甚至隐藏底层类型和逻辑,增加代码的"魔法感"和学习/调试成本。
是 Rust 生态中另一款主流 Web 框架,其核心功能(路由、参数提取、处理器定义)大量依赖宏实现,是理解"宏入侵"的典型案例。下面通过路由定义 和请求参数提取两个核心场景,对比 Axum(无宏)和 actix-web(宏依赖)的代码差异。
二、场景1:路由定义的对比
1. Axum:无宏,纯函数调用定义路由
Axum 的路由通过 Router 结构体的普通方法(route、nest 等)定义,完全是原生 Rust 函数调用,无任何宏:
rust
// Axum 路由定义(无宏)
use axum::{routing::{get, post}, Router};
use std::net::SocketAddr;
// 普通异步函数,无任何宏标注
async fn get_user() -> &'static str {
"获取用户信息"
}
async fn create_user() -> &'static str {
"创建用户"
}
#[tokio::main]
async fn main() {
// 纯函数调用构建路由,逻辑清晰
let app = Router::new()
.route("/user", get(get_user)) // GET 路由:函数调用
.route("/user", post(create_user)); // POST 路由:函数调用
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
2. actix-web:依赖宏定义路由
actix-web 必须通过 #[get]、#[post] 等宏标注处理函数,路由注册也依赖宏或宏生成的逻辑:
rust
// actix-web 路由定义(强依赖宏)
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
// 核心逻辑依赖 #[get] 宏标注
#[get("/user")] // 宏:绑定 GET 方法和路径
async fn get_user() -> impl Responder {
HttpResponse::Ok().body("获取用户信息")
}
// 核心逻辑依赖 #[post] 宏标注
#[post("/user")] // 宏:绑定 POST 方法和路径
async fn create_user() -> impl Responder {
HttpResponse::Ok().body("创建用户")
}
#[actix_web::main] // 宏:替代 tokio::main,初始化 actix 运行时
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(get_user) // 注册宏标注的函数
.service(create_user)
})
.bind(("0.0.0.0", 3000))?
.run()
.await
}
差异分析:
- Axum:路由是"数据结构 + 函数调用",符合 Rust 原生编程习惯,可通过普通代码逻辑动态调整路由(比如根据配置条件添加路由);
- actix-web:路由逻辑被封装在
#[get]/#[post]宏中,开发者无法直接通过原生 Rust 逻辑修改路由规则,宏的"魔法"隐藏了底层绑定逻辑(比如宏会自动生成路由注册的代码)。
三、场景2:请求参数提取的对比
请求参数解析是 Web 框架的核心能力,Axum 靠"提取器(Extractor)"(原生函数参数)实现,actix-web 则依赖宏 + 上下文对象提取。
1. Axum:无宏,通过函数参数(提取器)解析参数
Axum 直接将参数解析逻辑体现在函数参数类型中,无任何宏,类型安全且直观:
rust
// Axum 参数提取(无宏,纯类型系统)
use axum::{extract::{Path, Json}, routing::post, Router};
use serde::Deserialize;
// 普通结构体,仅派生 Deserialize(通用序列化 trait,非框架宏)
#[derive(Deserialize)]
struct User {
name: String,
age: u32,
}
// 函数参数直接声明要提取的参数类型:Path(路径参数) + Json(请求体)
// 无任何宏,参数类型即解析规则
async fn update_user(
Path(user_id): Path<u32>, // 提取路径参数 /user/:user_id
Json(user_info): Json<User> // 提取 JSON 请求体
) -> String {
format!("更新用户 {}:姓名={},年龄={}", user_id, user_info.name, user_info.age)
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/user/:user_id", post(update_user)); // 纯函数调用绑定路由
axum::Server::bind(&([0,0,0,0], 3000).into())
.serve(app.into_make_service())
.await
.unwrap();
}
2. actix-web:依赖宏 + 上下文对象提取参数
actix-web 需通过 web::Path/web::Json 结合宏(或上下文)提取参数,核心逻辑依赖宏封装:
rust
// actix-web 参数提取(依赖宏 + 上下文)
use actix_web::{post, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct User {
name: String,
age: u32,
}
// 需通过 #[post] 宏绑定路由,参数需通过 web::Path/web::Json 从上下文提取
#[post("/user/{user_id}")] // 宏:绑定路径(含参数)
async fn update_user(
// web::Path 是 actix 封装的类型,需从宏生成的上下文中提取
user_id: web::Path<u32>,
// web::Json 同理,依赖框架上下文,而非原生函数参数
user_info: web::Json<User>
) -> impl Responder {
let response = format!(
"更新用户 {}:姓名={},年龄={}",
user_id, user_info.name, user_info.age
);
HttpResponse::Ok().body(response)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(update_user) // 注册宏标注的函数
})
.bind(("0.0.0.0", 3000))?
.run()
.await
}
差异分析:
- Axum:参数提取是"原生函数参数 + 类型系统",开发者只需声明参数类型(如
Path<u32>),框架通过 Trait 自动解析,无宏介入,类型错误在编译期直接暴露,且代码可直接跳转、调试; - actix-web:参数提取依赖
web::Path/web::Json封装类型,而这些类型的解析逻辑被隐藏在#[post]宏生成的上下文里------开发者无法直接看到"路径参数如何映射到web::Path",宏成为参数解析的"黑盒"。
四、"无宏入侵"的核心价值(对比 actix-web)
| 维度 | Axum(无宏入侵) | actix-web(宏依赖) |
|---|---|---|
| 代码可读性 | 贴近原生 Rust,逻辑直观,无"魔法代码" | 宏隐藏底层逻辑,需记忆框架专属宏规则 |
| 调试/可维护性 | 可直接跳转函数、查看类型,编译错误提示清晰 | 宏展开后代码复杂,错误提示指向宏内部 |
| 灵活性 | 可通过原生 Rust 逻辑动态调整路由/参数解析 | 宏逻辑固定,动态调整需适配框架宏规则 |
| 学习成本 | 只需掌握 Rust 类型系统和异步编程 | 需额外学习框架自定义宏的使用规则 |
五、总结
Axum 的"无宏入侵"本质是将框架能力完全融入 Rust 原生语法体系,而非通过宏创造"专属语法":
- 核心逻辑(路由、参数提取)靠 Trait、函数参数、类型系统实现,无框架专属宏;
- 代码风格与原生 Rust 一致,降低学习和调试成本;
- 类型安全由 Rust 编译器直接保障,而非框架宏的额外校验。
而 actix-web 的宏虽然简化了"书写量",但也带来了"宏入侵"的问题------代码依赖框架自定义宏,脱离框架后难以复用,且宏的"黑盒特性"增加了问题定位的难度。这也是 Axum 成为 Rust Web 开发首选框架的核心原因之一:在保持高性能的同时,兼顾了代码的原生性和可维护性。
八、总结
Axum 凭借其类型安全、无宏入侵、高性能的特性,成为 Rust Web 开发的首选框架。它的核心设计理念是"用 Rust 的类型系统解决 Web 开发的常见问题",通过提取器、响应、中间件等组件,构建了一套简洁而强大的开发范式。
本文从基础入门到生产级实战,全面覆盖了 Axum 的核心功能与使用技巧。掌握 Axum 不仅能帮助开发者构建高性能的 Web 应用,更能深入理解 Rust 异步编程、类型系统的精髓。在云原生和微服务时代,Axum 无疑是 Rust 开发者的必备技能之一。