摘要:本文系统讲解使用 Rust 构建 Web 应用的完整流程,涵盖 Axum 框架入门、路由与处理器、数据库集成(SQLx)、JWT 认证、中间件与错误处理、部署与性能优化等核心内容。每个知识点配有完整代码示例、对比表格、实战场景及常见问题解答,帮助开发者掌握 Rust Web 开发。
关键词:Rust、Web开发、Axum、SQLx、JWT认证、中间件、RESTful API、后端开发、部署
适合人群:已掌握 Rust 基础的开发者、想学习 Web 开发的程序员、想构建高性能后端的开发者
阅读时间:约 65 分钟
版本信息:Rust 1.70+ | Axum 0.7+ | SQLx 0.7+ | 兼容 Windows/macOS/Linux
文章目录
- [一、Axum 框架入门](#一、Axum 框架入门)
-
- [1.1 为什么选择 Axum?](#1.1 为什么选择 Axum?)
- [1.2 项目初始化](#1.2 项目初始化)
- [1.3 第一个 Axum 应用](#1.3 第一个 Axum 应用)
- 二、路由与处理器
-
- [2.1 路由配置](#2.1 路由配置)
- [2.2 路径参数](#2.2 路径参数)
- [2.3 查询参数](#2.3 查询参数)
- [2.4 JSON 请求与响应](#2.4 JSON 请求与响应)
- 三、数据库集成
-
- [3.1 SQLx 基础](#3.1 SQLx 基础)
- [3.2 数据库连接](#3.2 数据库连接)
- [3.3 应用状态](#3.3 应用状态)
- [3.4 数据库操作](#3.4 数据库操作)
- [四、JWT 认证](#四、JWT 认证)
-
- [4.1 JWT 基础](#4.1 JWT 基础)
- [4.2 JWT 工具函数](#4.2 JWT 工具函数)
- [4.3 认证中间件](#4.3 认证中间件)
- 五、中间件与错误处理
-
- [5.1 常用中间件](#5.1 常用中间件)
- [5.2 错误处理](#5.2 错误处理)
- 六、部署与性能优化
-
- [6.1 Docker 部署](#6.1 Docker 部署)
- [6.2 性能优化](#6.2 性能优化)
- [6.3 健康检查](#6.3 健康检查)
- [💡 综合实战案例](#💡 综合实战案例)
-
- [实战:用户管理 API](#实战:用户管理 API)
- [❓ 常见问题 FAQ](#❓ 常见问题 FAQ)
- [📝 学习资源与建议](#📝 学习资源与建议)
- [📚 参考资料](#📚 参考资料)
一、Axum 框架入门
1.1 为什么选择 Axum?
Axum 是 Tokio 团队开发的 Web 框架,基于 tower 和 hyper 构建。
主流 Web 框架对比:
| 框架 | 特点 | 性能 | 生态 | 学习曲线 |
|---|---|---|---|---|
| Axum | Tokio 官方推荐 | ⭐⭐⭐⭐⭐ | 良好 | 中等 |
| Actix-web | 成熟稳定 | ⭐⭐⭐⭐⭐ | 成熟 | 中等 |
| Rocket | 易用性好 | ⭐⭐⭐⭐ | 良好 | 低 |
| Warp | 函数式风格 | ⭐⭐⭐⭐ | 一般 | 较高 |
1.2 项目初始化
bash
cargo new my_web_app
cd my_web_app
Cargo.toml 依赖:
toml
[dependencies]
axum = "0.7"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
1.3 第一个 Axum 应用
rust
use axum::{
routing::get,
Router,
};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(root));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("Server running on http://0.0.0.0:3000");
axum::serve(listener, app).await.unwrap();
}
async fn root() -> &'static str {
"Hello, Axum!"
}
Axum 核心概念:
| 概念 | 说明 | 示例 |
|---|---|---|
| Router | 路由配置 | Router::new() |
| Handler | 请求处理器 | async fn handler() |
| Layer | 中间件 | tower_http::trace::TraceLayer |
| Extractor | 参数提取 | Json<T>、Path<T> |
| Response | 响应类型 | Json<T>、String |
二、路由与处理器
2.1 路由配置
rust
use axum::{
routing::{get, post, put, delete},
Router,
};
let app = Router::new()
.route("/", get(root))
.route("/users", get(list_users).post(create_user))
.route("/users/:id", get(get_user).put(update_user).delete(delete_user))
.route("/health", get(health_check));
路由方法对比:
| 方法 | HTTP 方法 | 用途 | 示例 |
|---|---|---|---|
get() |
GET | 获取资源 | 获取用户列表 |
post() |
POST | 创建资源 | 创建新用户 |
put() |
PUT | 更新资源 | 更新用户信息 |
delete() |
DELETE | 删除资源 | 删除用户 |
patch() |
PATCH | 部分更新 | 更新部分字段 |
2.2 路径参数
rust
use axum::{
extract::Path,
routing::get,
Router,
};
async fn get_user(Path(id): Path<i32>) -> String {
format!("Getting user with id: {}", id)
}
async fn get_user_and_post(Path((user_id, post_id)): Path<(i32, i32)>) -> String {
format!("User: {}, Post: {}", user_id, post_id)
}
2.3 查询参数
rust
use axum::{
extract::Query,
routing::get,
Router,
};
use serde::Deserialize;
#[derive(Deserialize)]
struct Pagination {
page: i32,
limit: i32,
}
async fn list_users(Query(params): Query<Pagination>) -> String {
format!("Page: {}, Limit: {}", params.page, params.limit)
}
2.4 JSON 请求与响应
rust
use axum::{
extract::Json,
routing::post,
Router,
};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(Serialize)]
struct UserResponse {
id: i32,
name: String,
email: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<UserResponse> {
let user = UserResponse {
id: 1,
name: payload.name,
email: payload.email,
};
Json(user)
}
Extractor 类型对比:
| Extractor | 说明 | 示例 |
|---|---|---|
Path<T> |
路径参数 | Path(id): Path<i32> |
Query<T> |
查询参数 | Query(params): Query<Pagination> |
Json<T> |
JSON 请求体 | Json(payload): Json<CreateUser> |
State<T> |
应用状态 | State(state): State<AppState> |
Extension<T> |
扩展数据 | Extension(user): Extension<User> |
三、数据库集成
3.1 SQLx 基础
SQLx 是异步 SQL 工具包,支持编译期 SQL 检查。
Cargo.toml 依赖:
toml
[dependencies]
sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "chrono"] }
chrono = { version = "0.4", features = ["serde"] }
3.2 数据库连接
rust
use sqlx::PgPool;
async fn create_pool(database_url: &str) -> PgPool {
PgPool::connect(database_url)
.await
.expect("Failed to create pool")
}
3.3 应用状态
rust
use axum::{
extract::State,
routing::get,
Router,
};
use sqlx::PgPool;
#[derive(Clone)]
struct AppState {
db: PgPool,
}
let app_state = AppState {
db: create_pool("postgres://localhost/mydb").await,
};
let app = Router::new()
.route("/users", get(list_users))
.with_state(app_state);
async fn list_users(State(state): State<AppState>) -> String {
format!("Connected to database")
}
3.4 数据库操作
rust
use sqlx::FromRow;
use chrono::NaiveDateTime;
#[derive(FromRow, Serialize)]
struct User {
id: i32,
name: String,
email: String,
created_at: NaiveDateTime,
}
async fn list_users(State(state): State<AppState>) -> Json<Vec<User>> {
let users = sqlx::query_as::<_, User>(
"SELECT id, name, email, created_at FROM users"
)
.fetch_all(&state.db)
.await
.unwrap_or_default();
Json(users)
}
async fn create_user(
State(state): State<AppState>,
Json(payload): Json<CreateUser>,
) -> Json<User> {
let user = sqlx::query_as::<_, User>(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at"
)
.bind(&payload.name)
.bind(&payload.email)
.fetch_one(&state.db)
.await
.unwrap();
Json(user)
}
数据库操作对比:
| 方法 | 说明 | 返回值 | 示例 |
|---|---|---|---|
query_as |
查询并映射到结构体 | Vec<T> |
query_as::<_, User> |
query |
执行查询 | Affected Rows |
query("DELETE ...") |
execute |
执行语句 | Affected Rows |
execute().await |
fetch_one |
获取单条记录 | T |
fetch_one(&pool).await |
fetch_all |
获取所有记录 | Vec<T> |
fetch_all(&pool).await |
四、JWT 认证
4.1 JWT 基础
JWT(JSON Web Token)用于用户认证。
Cargo.toml 依赖:
toml
[dependencies]
jsonwebtoken = "9"
bcrypt = "0.15"
4.2 JWT 工具函数
rust
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
use serde::{Serialize, Deserialize};
use chrono::{Duration, Utc};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
exp: usize,
}
fn create_token(user_id: &str, secret: &str) -> Result<String, jsonwebtoken::errors::Error> {
let expiration = Utc::now()
.checked_add_signed(Duration::hours(24))
.expect("Invalid timestamp")
.timestamp() as usize;
let claims = Claims {
sub: user_id.to_owned(),
exp: expiration,
};
encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret.as_bytes()),
)
}
fn verify_token(token: &str, secret: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
decode::<Claims>(
token,
&DecodingKey::from_secret(secret.as_bytes()),
&Validation::default(),
)
.map(|data| data.claims)
}
4.3 认证中间件
rust
use axum::{
extract::Request,
http::StatusCode,
middleware::Next,
response::Response,
};
async fn auth_middleware(
request: Request,
next: Next,
) -> Result<Response, StatusCode> {
let auth_header = request
.headers()
.get("Authorization")
.and_then(|h| h.to_str().ok());
if let Some(auth_header) = auth_header {
if let Some(token) = auth_header.strip_prefix("Bearer ") {
if verify_token(token, "secret").is_ok() {
return Ok(next.run(request).await);
}
}
}
Err(StatusCode::UNAUTHORIZED)
}
JWT 认证流程:
| 步骤 | 说明 | 示例 |
|---|---|---|
| 用户登录 | 验证用户名密码 | POST /login |
| 生成 Token | 创建 JWT | create_token(user_id, secret) |
| 返回 Token | 返回给客户端 | Json(LoginResponse { token }) |
| 携带 Token | 客户端发送请求 | Authorization: Bearer <token> |
| 验证 Token | 服务端验证 | verify_token(token, secret) |
五、中间件与错误处理
5.1 常用中间件
rust
use tower_http::{
trace::TraceLayer,
cors::CorsLayer,
compression::CompressionLayer,
};
let app = Router::new()
.route("/", get(root))
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::permissive())
.layer(CompressionLayer::new());
中间件对比:
| 中间件 | 说明 | 用途 |
|---|---|---|
TraceLayer |
请求追踪 | 日志记录 |
CorsLayer |
CORS 配置 | 跨域请求 |
CompressionLayer |
响应压缩 | 减少带宽 |
TimeoutLayer |
超时控制 | 防止慢请求 |
RateLimitLayer |
限流 | 防止滥用 |
5.2 错误处理
rust
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
#[derive(Debug)]
enum AppError {
NotFound(String),
BadRequest(String),
InternalError(String),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
AppError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
};
let body = Json(json!({
"error": message,
}));
(status, body).into_response()
}
}
错误处理最佳实践:
| 实践 | 说明 | 示例 |
|---|---|---|
| 统一错误类型 | 定义 AppError 枚举 |
enum AppError |
| 实现 IntoResponse | 自动转换为 HTTP 响应 | impl IntoResponse |
| 使用 ? 运算符 | 简化错误传播 | handler()? |
| 记录错误日志 | 使用 tracing | tracing::error! |
| 返回友好消息 | 不暴露内部细节 | Json({ "error": "..." }) |
六、部署与性能优化
6.1 Docker 部署
dockerfile
FROM rust:1.70 as builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
COPY --from=builder /app/target/release/my_web_app /usr/local/bin/
EXPOSE 3000
CMD ["my_web_app"]
6.2 性能优化
优化技巧:
| 优化项 | 说明 | 配置 |
|---|---|---|
| Release 构建 | 最大优化 | cargo build --release |
| LTO | 链接时优化 | lto = true |
| Codegen Units | 减少并行编译 | codegen-units = 1 |
| 连接池 | 数据库连接复用 | PgPool::connect() |
| 缓存 | 减少数据库查询 | Redis/Memcached |
| 压缩 | 减少响应大小 | CompressionLayer |
Cargo.toml 优化配置:
toml
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
6.3 健康检查
rust
async fn health_check() -> Json<serde_json::Value> {
Json(serde_json::json!({
"status": "ok",
"timestamp": chrono::Utc::now().to_rfc3339(),
}))
}
💡 综合实战案例
实战:用户管理 API
rust
use axum::{
extract::{State, Json, Path},
routing::{get, post, put, delete},
Router,
};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use chrono::NaiveDateTime;
#[derive(Clone)]
struct AppState {
db: PgPool,
}
#[derive(Deserialize)]
struct CreateUser {
name: String,
email: String,
}
#[derive(FromRow, Serialize)]
struct User {
id: i32,
name: String,
email: String,
created_at: NaiveDateTime,
}
#[tokio::main]
async fn main() {
let db = PgPool::connect("postgres://localhost/mydb")
.await
.expect("Failed to connect to database");
let app_state = AppState { db };
let app = Router::new()
.route("/users", get(list_users).post(create_user))
.route("/users/:id", get(get_user).put(update_user).delete(delete_user))
.route("/health", get(health_check))
.with_state(app_state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
println!("Server running on http://0.0.0.0:3000");
axum::serve(listener, app).await.unwrap();
}
async fn list_users(State(state): State<AppState>) -> Json<Vec<User>> {
let users = sqlx::query_as::<_, User>(
"SELECT id, name, email, created_at FROM users"
)
.fetch_all(&state.db)
.await
.unwrap_or_default();
Json(users)
}
async fn create_user(
State(state): State<AppState>,
Json(payload): Json<CreateUser>,
) -> Json<User> {
let user = sqlx::query_as::<_, User>(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at"
)
.bind(&payload.name)
.bind(&payload.email)
.fetch_one(&state.db)
.await
.unwrap();
Json(user)
}
async fn get_user(
State(state): State<AppState>,
Path(id): Path<i32>,
) -> Json<Option<User>> {
let user = sqlx::query_as::<_, User>(
"SELECT id, name, email, created_at FROM users WHERE id = $1"
)
.bind(id)
.fetch_optional(&state.db)
.await
.unwrap();
Json(user)
}
async fn update_user(
State(state): State<AppState>,
Path(id): Path<i32>,
Json(payload): Json<CreateUser>,
) -> Json<Option<User>> {
let user = sqlx::query_as::<_, User>(
"UPDATE users SET name = $1, email = $2 WHERE id = $3 RETURNING id, name, email, created_at"
)
.bind(&payload.name)
.bind(&payload.email)
.bind(id)
.fetch_optional(&state.db)
.await
.unwrap();
Json(user)
}
async fn delete_user(
State(state): State<AppState>,
Path(id): Path<i32>,
) -> StatusCode {
sqlx::query("DELETE FROM users WHERE id = $1")
.bind(id)
.execute(&state.db)
.await
.map(|_| StatusCode::NO_CONTENT)
.unwrap_or(StatusCode::NOT_FOUND)
}
async fn health_check() -> Json<serde_json::Value> {
Json(serde_json::json!({
"status": "ok",
"timestamp": chrono::Utc::now().to_rfc3339(),
}))
}
项目知识点:
- Axum 路由与处理器
- SQLx 数据库操作
- 应用状态管理
- RESTful API 设计
- CRUD 操作实现
❓ 常见问题 FAQ
Q1:Axum 和 Actix-web 有什么区别?
A:主要区别:
- Axum 是 Tokio 官方推荐,与 Tokio 生态集成更好
- Actix-web 更成熟,文档更完善
- Axum 基于 tower,中间件系统更强大
- Actix-web 性能略优,但差距不大
Q2:如何处理数据库连接池?
A:使用 PgPool 或 MySqlPool:
- 创建连接池:
PgPool::connect().await - 通过 State 传递:
.with_state(AppState { db }) - 在处理器中使用:
State(state): State<AppState> - 连接池自动管理连接
Q3:JWT Token 如何刷新?
A:常见方案:
- 双 Token 机制:Access Token + Refresh Token
- Access Token 短期有效(15 分钟)
- Refresh Token 长期有效(7 天)
- 使用 Refresh Token 获取新的 Access Token
Q4:如何部署 Rust Web 应用?
A:部署方式:
- Docker 容器化部署
- 直接二进制部署
- 使用 systemd 管理服务
- 使用 Nginx 反向代理
Q5:如何优化 Rust Web 应用性能?
A:优化技巧:
- 使用
--release构建 - 启用 LTO 优化
- 使用连接池
- 添加缓存层(Redis)
- 启用响应压缩
📝 学习资源与建议
学习建议
1. 理解异步编程 :Web 开发离不开 async/await
2. 掌握数据库操作 :SQLx 是 Rust Web 开发的核心
3. 设计 RESTful API :遵循 REST 规范设计接口
4. 重视错误处理 :统一的错误处理提升用户体验
5. 关注性能优化:Release 构建、连接池、缓存