axum-web-framework
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAxum Web Framework
Axum Web框架
A comprehensive skill for building production-ready web applications and APIs using Axum, the ergonomic and modular Rust web framework built on Tokio and Tower. Master routing, extractors, middleware, state management, error handling, and deployment patterns.
本指南全面介绍如何使用Axum构建可投入生产的Web应用和API,Axum是基于Tokio和Tower打造的符合人体工程学、模块化的Rust Web框架。你将掌握路由、提取器、中间件、状态管理、错误处理以及部署模式。
When to Use This Skill
何时使用本指南
Use this skill when:
- Building REST APIs with Rust and async/await
- Creating high-performance web services with type safety
- Developing microservices with Tokio ecosystem integration
- Implementing WebSocket servers or Server-Sent Events (SSE)
- Building GraphQL APIs with Rust backend
- Creating middleware-heavy applications requiring Tower integration
- Developing production-ready web applications requiring robust error handling
- Building systems requiring fine-grained control over HTTP request/response handling
- Implementing authentication, authorization, and security middleware
- Creating real-time web applications with async Rust
- Developing APIs requiring request validation and transformation
- Building web services with complex routing and nested routers
- Implementing rate limiting, timeout, and backpressure handling
- Creating web applications requiring custom extractors and response types
在以下场景中使用本指南:
- 使用Rust和async/await构建REST API
- 创建具备类型安全性的高性能Web服务
- 开发与Tokio生态集成的微服务
- 实现WebSocket服务器或Server-Sent Events(SSE)
- 基于Rust后端构建GraphQL API
- 开发需要与Tower集成的、重度依赖中间件的应用
- 开发具备健壮错误处理能力的可投入生产的Web应用
- 构建需要对HTTP请求/响应处理进行细粒度控制的系统
- 实现认证、授权和安全中间件
- 使用异步Rust构建实时Web应用
- 开发需要请求验证和转换的API
- 构建具备复杂路由和嵌套路由器的Web服务
- 实现速率限制、超时和背压处理
- 创建需要自定义提取器和响应类型的Web应用
Core Concepts
核心概念
Axum Architecture Philosophy
Axum架构理念
Axum is built on three fundamental pillars:
- Tower Services: Everything in Axum is built on Tower's trait, providing composability and middleware integration
Service - Type-Safe Extractors: Request data extraction is compile-time checked, eliminating runtime parsing errors
- Minimal Boilerplate: Ergonomic APIs that reduce ceremony while maintaining explicitness
Axum基于三大核心支柱构建:
- Tower Services:Axum中的所有组件都基于Tower的trait构建,提供可组合性和中间件集成能力
Service - 类型安全的提取器:请求数据提取在编译时进行检查,消除运行时解析错误
- 极简样板代码:符合人体工程学的API,在保持明确性的同时减少冗余代码
The Router
路由器(Router)
The is the central building block in Axum. It maps HTTP requests to handlers based on path and method.
RouterKey Properties:
- Routes are matched in the order they're defined
- Routers can be nested for modular organization
- Middleware can be applied at router, route, or method level
- Generic over state type for flexible state management
- Implements Tower's trait for composability
Service
Router Creation:
rust
use axum::{Router, routing::get};
let app = Router::new()
.route("/", get(handler))
.route("/users/:id", get(get_user))
.route("/posts", get(list_posts).post(create_post));Router关键特性:
- 路由按照定义顺序进行匹配
- 路由器可以嵌套,实现模块化组织
- 中间件可以应用在路由器、路由或方法级别
- 对状态类型泛型化,支持灵活的状态管理
- 实现Tower的trait,具备可组合性
Service
创建路由器:
rust
use axum::{Router, routing::get};
let app = Router::new()
.route("/", get(handler))
.route("/users/:id", get(get_user))
.route("/posts", get(list_posts).post(create_post));Handlers
处理函数(Handlers)
Handlers are async functions that process requests and return responses. Axum supports multiple handler signatures through its powerful type system.
Handler Requirements:
- Must be async functions
- Can extract data from requests using extractors
- Must return types implementing
IntoResponse - Can have up to 16 parameters (all must be extractors)
Common Handler Patterns:
rust
// Simple handler
async fn handler() -> &'static str {
"Hello, World!"
}
// Handler with path parameter
async fn get_user(Path(user_id): Path<u32>) -> String {
format!("User ID: {}", user_id)
}
// Handler with multiple extractors
async fn create_user(
State(state): State<AppState>,
Json(payload): Json<CreateUser>,
) -> Result<Json<User>, StatusCode> {
// Implementation
}处理函数是异步函数,用于处理请求并返回响应。Axum通过强大的类型系统支持多种处理函数签名。
处理函数要求:
- 必须是异步函数
- 可以使用提取器从请求中提取数据
- 必须返回实现的类型
IntoResponse - 最多可以有16个参数(所有参数都必须是提取器)
常见处理函数模式:
rust
// 简单处理函数
async fn handler() -> &'static str {
"Hello, World!"
}
// 带路径参数的处理函数
async fn get_user(Path(user_id): Path<u32>) -> String {
format!("User ID: {}", user_id)
}
// 带多个提取器的处理函数
async fn create_user(
State(state): State<AppState>,
Json(payload): Json<CreateUser>,
) -> Result<Json<User>, StatusCode> {
// 实现逻辑
}Extractors
提取器(Extractors)
Extractors are types that implement or , allowing type-safe extraction of data from requests.
FromRequestFromRequestPartsBuilt-in Extractors:
- Path - Extract path parameters
- Query - Extract query string parameters
- Json - Parse JSON request body
- Form - Parse form-encoded request body
- State - Access shared application state
- Extension - Access request extensions
- Headers - Access request headers
- Method - Get HTTP method
- Uri - Get request URI
- Request - Get full request
- Bytes - Raw request body as bytes
- String - Request body as UTF-8 string
- Multipart - Handle multipart/form-data
Extractor Ordering:
- Extractors that consume the request body must come last
- Multiple body extractors in one handler will cause compilation errors
- and other non-body extractors can be in any order
State
提取器是实现或的类型,允许以类型安全的方式从请求中提取数据。
FromRequestFromRequestParts内置提取器:
- Path - 提取路径参数
- Query - 提取查询字符串参数
- Json - 解析JSON请求体
- Form - 解析表单编码的请求体
- State - 访问共享的应用状态
- Extension - 访问请求扩展
- Headers - 访问请求头
- Method - 获取HTTP方法
- Uri - 获取请求URI
- Request - 获取完整请求
- Bytes - 以字节形式获取原始请求体
- String - 以UTF-8字符串形式获取请求体
- Multipart - 处理multipart/form-data
提取器顺序:
- 消费请求体的提取器必须放在最后
- 单个处理函数中使用多个体提取器会导致编译错误
- 和其他非体提取器可以按任意顺序排列
State
Responses
响应(Responses)
Any type implementing can be returned from handlers. Axum provides many built-in implementations.
IntoResponseBuilt-in Response Types:
- ,
String- Text responses&'static str - - JSON responses
Json<T> - - HTML responses
Html<String> - - Status-only responses
StatusCode - - Status with body
(StatusCode, T) - - Custom headers with body
(Parts, T) - - Full control over response
Response - - Error handling (where E: IntoResponse)
Result<T, E>
任何实现的类型都可以从处理函数返回。Axum提供了许多内置实现。
IntoResponse内置响应类型:
- ,
String- 文本响应&'static str - - JSON响应
Json<T> - - HTML响应
Html<String> - - 仅返回状态码的响应
StatusCode - - 带响应体的状态码
(StatusCode, T) - - 带自定义头的响应体
(Parts, T) - - 完全控制响应
Response - - 错误处理(其中E: IntoResponse)
Result<T, E>
State Management
状态管理
Axum uses the extractor to share data across handlers. State must implement and is typically wrapped in for shared ownership.
StateCloneArcState Patterns:
- Simple State:
rust
#[derive(Clone)]
struct AppState {
api_key: String,
}
let app = Router::new()
.route("/", get(handler))
.with_state(AppState {
api_key: "secret".to_string(),
});- Shared State with Arc:
rust
#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
cache: Arc<RwLock<Cache>>,
}- Multiple State Types:
rust
// Define separate state types for different router sections
let api_router: Router<ApiState> = Router::new()
.route("/api/data", get(api_handler));
let app_router: Router<AppState> = Router::new()
.route("/app", get(app_handler));
// Combine with final state
let app = Router::new()
.nest("/", app_router.with_state(app_state))
.nest("/", api_router.with_state(api_state));Axum使用提取器在处理函数之间共享数据。状态必须实现,通常用包裹以实现共享所有权。
StateCloneArc状态模式:
- 简单状态:
rust
#[derive(Clone)]
struct AppState {
api_key: String,
}
let app = Router::new()
.route("/", get(handler))
.with_state(AppState {
api_key: "secret".to_string(),
});- 使用Arc的共享状态:
rust
#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
cache: Arc<RwLock<Cache>>,
}- 多状态类型:
rust
// 为不同的路由器部分定义单独的状态类型
let api_router: Router<ApiState> = Router::new()
.route("/api/data", get(api_handler));
let app_router: Router<AppState> = Router::new()
.route("/app", get(app_handler));
// 组合最终状态
let app = Router::new()
.nest("/", app_router.with_state(app_state))
.nest("/", api_router.with_state(api_state));Middleware
中间件
Middleware in Axum comes from Tower and provides request/response transformation, logging, authentication, and more.
Middleware Categories:
- Tower Middleware - From and
towercratestower-http - Custom Middleware - Using
middleware::from_fn - Service Middleware - Implementing Tower's trait
Service - Layer Pattern - Using Tower's for composability
Layer
Middleware Application Order:
- Applied with executes bottom-to-top (wrapping previous layers)
.layer() - Applied with executes top-to-bottom (more intuitive)
ServiceBuilder - Middleware on runs after routing
Router::layer - Middleware around (using
Router) runs before routingLayer::layer
Axum中的中间件来自Tower,提供请求/响应转换、日志记录、认证等功能。
中间件类别:
- Tower中间件 - 来自和
towercratetower-http - 自定义中间件 - 使用
middleware::from_fn - 服务中间件 - 实现Tower的trait
Service - 层模式 - 使用Tower的实现可组合性
Layer
中间件应用顺序:
- 使用添加的中间件按从下到上的顺序执行(包裹之前的层)
.layer() - 使用添加的中间件按从上到下的顺序执行(更直观)
ServiceBuilder - 上的中间件在路由匹配后运行
Router::layer - 包裹的中间件(使用
Router)在路由匹配前运行Layer::layer
Error Handling
错误处理
Axum's error handling is built on the trait, allowing custom error types to be converted to HTTP responses.
IntoResponseError Handling Strategies:
- Result Types:
rust
async fn handler() -> Result<Json<Data>, StatusCode> {
// Returns 200 OK or error status code
}- Custom Error Types:
rust
enum AppError {
Database(sqlx::Error),
NotFound,
Unauthorized,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
// Convert to HTTP response
}
}- HandleErrorLayer:
rust
use axum::error_handling::HandleErrorLayer;
let app = Router::new()
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(handle_error))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
);Axum的错误处理基于 trait,允许将自定义错误类型转换为HTTP响应。
IntoResponse错误处理策略:
- Result类型:
rust
async fn handler() -> Result<Json<Data>, StatusCode> {
// 返回200 OK或错误状态码
}- 自定义错误类型:
rust
enum AppError {
Database(sqlx::Error),
NotFound,
Unauthorized,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
// 转换为HTTP响应
}
}- HandleErrorLayer:
rust
use axum::error_handling::HandleErrorLayer;
let app = Router::new()
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(handle_error))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
);Tower Integration
Tower集成
Axum is built on Tower, enabling powerful middleware composition and service abstraction.
Key Tower Concepts:
- Service Trait - Asynchronous request processing
- Layer Trait - Middleware factory pattern
- ServiceBuilder - Ergonomic middleware composition
- Timeout - Request timeout handling
- RateLimit - Request rate limiting
- LoadShed - Backpressure management
- Buffer - Request buffering
Axum基于Tower构建,支持强大的中间件组合和服务抽象。
关键Tower概念:
- Service Trait - 异步请求处理
- Layer Trait - 中间件工厂模式
- ServiceBuilder - 符合人体工程学的中间件组合
- Timeout - 请求超时处理
- RateLimit - 请求速率限制
- LoadShed - 背压管理
- Buffer - 请求缓冲
Routing
路由
Basic Routing
基础路由
Routes map HTTP methods and paths to handlers:
rust
use axum::{
Router,
routing::{get, post, put, delete, patch},
};
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("/posts/:id/comments", get(get_comments).post(add_comment));路由将HTTP方法和路径映射到处理函数:
rust
use axum::{
Router,
routing::{get, post, put, delete, patch},
};
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("/posts/:id/comments", get(get_comments).post(add_comment));Path Parameters
路径参数
Extract dynamic segments from paths:
rust
use axum::extract::Path;
use serde::Deserialize;
// Single parameter
async fn get_user(Path(user_id): Path<u32>) -> String {
format!("User {}", user_id)
}
// Multiple parameters
#[derive(Deserialize)]
struct PostPath {
user_id: u32,
post_id: u32,
}
async fn get_post(Path(params): Path<PostPath>) -> String {
format!("User {} Post {}", params.user_id, params.post_id)
}
// Using tuple for multiple params
async fn get_comment(
Path((post_id, comment_id)): Path<(u32, u32)>
) -> String {
format!("Post {} Comment {}", post_id, comment_id)
}从路径中提取动态片段:
rust
use axum::extract::Path;
use serde::Deserialize;
// 单个参数
async fn get_user(Path(user_id): Path<u32>) -> String {
format!("User {}", user_id)
}
// 多个参数
#[derive(Deserialize)]
struct PostPath {
user_id: u32,
post_id: u32,
}
async fn get_post(Path(params): Path<PostPath>) -> String {
format!("User {} Post {}", params.user_id, params.post_id)
}
// 使用元组处理多个参数
async fn get_comment(
Path((post_id, comment_id)): Path<(u32, u32)>
) -> String {
format!("Post {} Comment {}", post_id, comment_id)
}Wildcard Routes
通配符路由
Capture remaining path segments:
rust
// Captures all remaining path
async fn handler(Path(path): Path<String>) -> String {
format!("Captured path: {}", path)
}
let app = Router::new()
.route("/{*key}", get(handler));
// GET /foo/bar/baz -> path = "foo/bar/baz"Important: Nested routers strip matched prefixes, but wildcard routes retain the full URI.
捕获剩余的路径片段:
rust
// 捕获所有剩余路径
async fn handler(Path(path): Path<String>) -> String {
format!("Captured path: {}", path)
}
let app = Router::new()
.route("/{*key}", get(handler));
// GET /foo/bar/baz -> path = "foo/bar/baz"注意: 嵌套路由器会剥离匹配的前缀,但通配符路由会保留完整的URI。
Nested Routers
嵌套路由器
Organize routes into modules using router nesting:
rust
use axum::{Router, routing::get};
fn api_routes() -> Router {
Router::new()
.route("/users", get(list_users))
.route("/users/:id", get(get_user))
.route("/posts", get(list_posts))
}
fn admin_routes() -> Router {
Router::new()
.route("/dashboard", get(dashboard))
.route("/settings", get(settings))
}
let app = Router::new()
.nest("/api", api_routes())
.nest("/admin", admin_routes())
.route("/", get(root));Nesting Behavior:
- Matched prefix is stripped from URI before passing to nested router
- Handlers in nested routers only see path relative to nest point
- Fallback handlers are inherited from parent if not defined in child
- Middleware can be applied before or after nesting
使用路由器嵌套将路由组织到模块中:
rust
use axum::{Router, routing::get};
fn api_routes() -> Router {
Router::new()
.route("/users", get(list_users))
.route("/users/:id", get(get_user))
.route("/posts", get(list_posts))
}
fn admin_routes() -> Router {
Router::new()
.route("/dashboard", get(dashboard))
.route("/settings", get(settings))
}
let app = Router::new()
.nest("/api", api_routes())
.nest("/admin", admin_routes())
.route("/", get(root));嵌套行为:
- 匹配的前缀会从URI中剥离后再传递给嵌套路由器
- 嵌套路由器中的处理函数只能看到相对于嵌套点的路径
- 如果子路由器未定义回退处理函数,则会继承父路由器的
- 中间件可以在嵌套之前或之后应用
Fallback Handlers
回退处理函数
Handle unmatched routes:
rust
use axum::{http::StatusCode, handler::Handler};
async fn fallback() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "Not Found")
}
let app = Router::new()
.route("/", get(handler))
.fallback(fallback);Fallback Inheritance:
rust
async fn api_fallback() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "API endpoint not found")
}
let api = Router::new()
.route("/users", get(list_users))
.fallback(api_fallback);
let app = Router::new()
.nest("/api", api)
.fallback(fallback); // Used for non-/api routes处理未匹配的路由:
rust
use axum::{http::StatusCode, handler::Handler};
async fn fallback() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "Not Found")
}
let app = Router::new()
.route("/", get(handler))
.fallback(fallback);回退继承:
rust
async fn api_fallback() -> (StatusCode, &'static str) {
(StatusCode::NOT_FOUND, "API endpoint not found")
}
let api = Router::new()
.route("/users", get(list_users))
.fallback(api_fallback);
let app = Router::new()
.nest("/api", api)
.fallback(fallback); // 用于非/api路由Method Routing
方法路由
Handle multiple HTTP methods on the same route:
rust
use axum::routing::{get, post, MethodRouter};
// Multiple methods on one route
let app = Router::new()
.route("/users", get(list_users).post(create_user));
// Different handlers per method
let app = Router::new()
.route("/resource",
get(get_resource)
.post(create_resource)
.put(update_resource)
.delete(delete_resource)
.patch(patch_resource)
);在同一路由上处理多个HTTP方法:
rust
use axum::routing::{get, post, MethodRouter};
// 单个路由上的多个方法
let app = Router::new()
.route("/users", get(list_users).post(create_user));
// 每个方法对应不同的处理函数
let app = Router::new()
.route("/resource",
get(get_resource)
.post(create_resource)
.put(update_resource)
.delete(delete_resource)
.patch(patch_resource)
);Extractors Deep Dive
提取器深入解析
Path Extractor
Path提取器
Extract typed path parameters:
rust
use axum::extract::Path;
use serde::Deserialize;
// Simple extraction
async fn user_by_id(Path(id): Path<u32>) -> String {
format!("User {}", id)
}
// Complex extraction
#[derive(Deserialize)]
struct Params {
org: String,
repo: String,
issue: u32,
}
async fn github_issue(Path(params): Path<Params>) -> String {
format!("{}/{} issue #{}", params.org, params.repo, params.issue)
}
let app = Router::new()
.route("/users/:id", get(user_by_id))
.route("/repos/:org/:repo/issues/:issue", get(github_issue));提取类型化的路径参数:
rust
use axum::extract::Path;
use serde::Deserialize;
// 简单提取
async fn user_by_id(Path(id): Path<u32>) -> String {
format!("User {}", id)
}
// 复杂提取
#[derive(Deserialize)]
struct Params {
org: String,
repo: String,
issue: u32,
}
async fn github_issue(Path(params): Path<Params>) -> String {
format!("{}/{} issue #{}", params.org, params.repo, params.issue)
}
let app = Router::new()
.route("/users/:id", get(user_by_id))
.route("/repos/:org/:repo/issues/:issue", get(github_issue));Query Extractor
Query提取器
Extract query string parameters:
rust
use axum::extract::Query;
use serde::Deserialize;
#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
per_page: Option<u32>,
}
async fn list_users(Query(pagination): Query<Pagination>) -> String {
let page = pagination.page.unwrap_or(1);
let per_page = pagination.per_page.unwrap_or(20);
format!("Page {} with {} items", page, per_page)
}
// GET /users?page=2&per_page=50提取查询字符串参数:
rust
use axum::extract::Query;
use serde::Deserialize;
#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
per_page: Option<u32>,
}
async fn list_users(Query(pagination): Query<Pagination>) -> String {
let page = pagination.page.unwrap_or(1);
let per_page = pagination.per_page.unwrap_or(20);
format!("Page {} with {} items", page, per_page)
}
// GET /users?page=2&per_page=50Json Extractor
Json提取器
Parse JSON request bodies:
rust
use axum::extract::Json;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
username: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: u32,
username: String,
email: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
let user = User {
id: 123,
username: payload.username,
email: payload.email,
};
Json(user)
}JSON Error Handling:
rust
use axum::extract::rejection::JsonRejection;
use axum::http::StatusCode;
async fn create_user(
payload: Result<Json<CreateUser>, JsonRejection>
) -> Result<Json<User>, (StatusCode, String)> {
match payload {
Ok(Json(create_user)) => {
// Valid JSON
Ok(Json(User { /* ... */ }))
}
Err(JsonRejection::MissingJsonContentType(_)) => {
Err((
StatusCode::BAD_REQUEST,
"Missing `Content-Type: application/json`".to_string(),
))
}
Err(JsonRejection::JsonDataError(err)) => {
Err((
StatusCode::BAD_REQUEST,
format!("Invalid JSON: {}", err),
))
}
Err(JsonRejection::JsonSyntaxError(err)) => {
Err((
StatusCode::BAD_REQUEST,
format!("JSON syntax error: {}", err),
))
}
Err(_) => {
Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Unknown error".to_string(),
))
}
}
}解析JSON请求体:
rust
use axum::extract::Json;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateUser {
username: String,
email: String,
}
#[derive(Serialize)]
struct User {
id: u32,
username: String,
email: String,
}
async fn create_user(Json(payload): Json<CreateUser>) -> Json<User> {
let user = User {
id: 123,
username: payload.username,
email: payload.email,
};
Json(user)
}JSON错误处理:
rust
use axum::extract::rejection::JsonRejection;
use axum::http::StatusCode;
async fn create_user(
payload: Result<Json<CreateUser>, JsonRejection>
) -> Result<Json<User>, (StatusCode, String)> {
match payload {
Ok(Json(create_user)) => {
// 有效的JSON
Ok(Json(User { /* ... */ }))
}
Err(JsonRejection::MissingJsonContentType(_)) => {
Err((
StatusCode::BAD_REQUEST,
"Missing `Content-Type: application/json`".to_string(),
))
}
Err(JsonRejection::JsonDataError(err)) => {
Err((
StatusCode::BAD_REQUEST,
format!("Invalid JSON: {}", err),
))
}
Err(JsonRejection::JsonSyntaxError(err)) => {
Err((
StatusCode::BAD_REQUEST,
format!("JSON syntax error: {}", err),
))
}
Err(_) => {
Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Unknown error".to_string(),
))
}
}
}State Extractor
State提取器
Access shared application state:
rust
use axum::extract::State;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
api_key: String,
}
async fn handler(State(state): State<AppState>) -> String {
format!("API Key: {}", state.api_key)
}
let state = AppState {
db_pool: Arc::new(DatabasePool::new()),
api_key: "secret".to_string(),
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);访问共享的应用状态:
rust
use axum::extract::State;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
api_key: String,
}
async fn handler(State(state): State<AppState>) -> String {
format!("API Key: {}", state.api_key)
}
let state = AppState {
db_pool: Arc::new(DatabasePool::new()),
api_key: "secret".to_string(),
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);Extension Extractor
Extension提取器
Access request extensions (useful for middleware):
rust
use axum::extract::Extension;
#[derive(Clone)]
struct CurrentUser {
id: u32,
username: String,
}
async fn handler(Extension(user): Extension<CurrentUser>) -> String {
format!("Hello, {}", user.username)
}
// Set by middleware:
async fn auth_middleware(
mut req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let user = CurrentUser {
id: 1,
username: "alice".to_string(),
};
req.extensions_mut().insert(user);
Ok(next.run(req).await)
}访问请求扩展(对中间件有用):
rust
use axum::extract::Extension;
#[derive(Clone)]
struct CurrentUser {
id: u32,
username: String,
}
async fn handler(Extension(user): Extension<CurrentUser>) -> String {
format!("Hello, {}", user.username)
}
// 由中间件设置:
async fn auth_middleware(
mut req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let user = CurrentUser {
id: 1,
username: "alice".to_string(),
};
req.extensions_mut().insert(user);
Ok(next.run(req).await)
}Form Extractor
Form提取器
Parse form-encoded request bodies:
rust
use axum::extract::Form;
use serde::Deserialize;
#[derive(Deserialize)]
struct LoginForm {
username: String,
password: String,
}
async fn login(Form(form): Form<LoginForm>) -> String {
format!("Logging in user: {}", form.username)
}解析表单编码的请求体:
rust
use axum::extract::Form;
use serde::Deserialize;
#[derive(Deserialize)]
struct LoginForm {
username: String,
password: String,
}
async fn login(Form(form): Form<LoginForm>) -> String {
format!("Logging in user: {}", form.username)
}Headers Extractor
Headers提取器
Access request headers:
rust
use axum::http::HeaderMap;
async fn handler(headers: HeaderMap) -> String {
let user_agent = headers
.get("user-agent")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
format!("User-Agent: {}", user_agent)
}访问请求头:
rust
use axum::http::HeaderMap;
async fn handler(headers: HeaderMap) -> String {
let user_agent = headers
.get("user-agent")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
format!("User-Agent: {}", user_agent)
}Custom Extractors
自定义提取器
Create custom extractors by implementing or :
FromRequestFromRequestPartsrust
use axum::{
extract::{FromRequest, Request},
response::{IntoResponse, Response},
http::StatusCode,
async_trait,
};
struct AuthenticatedUser {
id: u32,
username: String,
}
#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(
parts: &mut Parts,
state: &S,
) -> Result<Self, Self::Rejection> {
// Extract and validate auth token
let auth_header = parts
.headers
.get("authorization")
.and_then(|v| v.to_str().ok())
.ok_or_else(|| {
(StatusCode::UNAUTHORIZED, "Missing authorization header")
.into_response()
})?;
// Validate token and return user
Ok(AuthenticatedUser {
id: 1,
username: "alice".to_string(),
})
}
}
async fn protected_route(user: AuthenticatedUser) -> String {
format!("Hello, {}", user.username)
}通过实现或创建自定义提取器:
FromRequestFromRequestPartsrust
use axum::{
extract::{FromRequest, Request},
response::{IntoResponse, Response},
http::StatusCode,
async_trait,
};
struct AuthenticatedUser {
id: u32,
username: String,
}
#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(
parts: &mut Parts,
state: &S,
) -> Result<Self, Self::Rejection> {
// 提取并验证认证令牌
let auth_header = parts
.headers
.get("authorization")
.and_then(|v| v.to_str().ok())
.ok_or_else(|| {
(StatusCode::UNAUTHORIZED, "Missing authorization header")
.into_response()
})?;
// 验证令牌并返回用户
Ok(AuthenticatedUser {
id: 1,
username: "alice".to_string(),
})
}
}
async fn protected_route(user: AuthenticatedUser) -> String {
format!("Hello, {}", user.username)
}Middleware
中间件
Applying Middleware
应用中间件
Three Ways to Apply Middleware:
- Router-level - Affects all routes
- Route-level - Affects specific routes
- Method-level - Affects specific methods on a route
rust
use axum::{Router, routing::get, middleware};
use tower_http::trace::TraceLayer;
// 1. Router-level
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());
// 2. Route-level
let app = Router::new()
.route("/protected", get(handler))
.route_layer(middleware::from_fn(auth_middleware));
// 3. Method-level
let app = Router::new()
.route("/resource",
get(handler)
.route_layer(middleware::from_fn(read_only_auth))
.post(create_handler)
.route_layer(middleware::from_fn(write_auth))
);三种应用中间件的方式:
- 路由器级别 - 影响所有路由
- 路由级别 - 影响特定路由
- 方法级别 - 影响路由上的特定方法
rust
use axum::{Router, routing::get, middleware};
use tower_http::trace::TraceLayer;
// 1. 路由器级别
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());
// 2. 路由级别
let app = Router::new()
.route("/protected", get(handler))
.route_layer(middleware::from_fn(auth_middleware));
// 3. 方法级别
let app = Router::new()
.route("/resource",
get(handler)
.route_layer(middleware::from_fn(read_only_auth))
.post(create_handler)
.route_layer(middleware::from_fn(write_auth))
);Middleware Execution Order
中间件执行顺序
With sequential calls (bottom-to-top):
.layer()rust
let app = Router::new()
.route("/", get(handler))
.layer(layer_three) // Executes third
.layer(layer_two) // Executes second
.layer(layer_one); // Executes firstWith (top-to-bottom):
ServiceBuilderrust
use tower::ServiceBuilder;
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(layer_one) // Executes first
.layer(layer_two) // Executes second
.layer(layer_three) // Executes third
);使用连续的调用(从下到上):
.layer()rust
let app = Router::new()
.route("/", get(handler))
.layer(layer_three) // 第三个执行
.layer(layer_two) // 第二个执行
.layer(layer_one); // 第一个执行使用(从上到下):
ServiceBuilderrust
use tower::ServiceBuilder;
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(layer_one) // 第一个执行
.layer(layer_two) // 第二个执行
.layer(layer_three) // 第三个执行
);Common Tower Middleware
常见Tower中间件
TraceLayer - HTTP request tracing:
rust
use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());CompressionLayer - Response compression:
rust
use tower_http::compression::CompressionLayer;
let app = Router::new()
.route("/", get(handler))
.layer(CompressionLayer::new());CorsLayer - CORS handling:
rust
use tower_http::cors::{CorsLayer, Any};
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
let app = Router::new()
.route("/api/data", get(handler))
.layer(cors);TimeoutLayer - Request timeouts:
rust
use tower::timeout::TimeoutLayer;
use std::time::Duration;
let app = Router::new()
.route("/", get(handler))
.layer(TimeoutLayer::new(Duration::from_secs(30)));TraceLayer - HTTP请求追踪:
rust
use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());CompressionLayer - 响应压缩:
rust
use tower_http::compression::CompressionLayer;
let app = Router::new()
.route("/", get(handler))
.layer(CompressionLayer::new());CorsLayer - CORS处理:
rust
use tower_http::cors::{CorsLayer, Any};
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
let app = Router::new()
.route("/api/data", get(handler))
.layer(cors);TimeoutLayer - 请求超时:
rust
use tower::timeout::TimeoutLayer;
use std::time::Duration;
let app = Router::new()
.route("/", get(handler))
.layer(TimeoutLayer::new(Duration::from_secs(30)));HandleErrorLayer
HandleErrorLayer
Convert middleware errors to HTTP responses:
rust
use axum::{
error_handling::HandleErrorLayer,
http::{StatusCode, Method, Uri},
BoxError,
};
use tower::ServiceBuilder;
use std::time::Duration;
async fn handle_timeout_error(
method: Method,
uri: Uri,
err: BoxError,
) -> (StatusCode, String) {
if err.is::<tower::timeout::error::Elapsed>() {
(
StatusCode::REQUEST_TIMEOUT,
format!("`{} {}` request timed out", method, uri),
)
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("`{} {}` failed with {}", method, uri, err),
)
}
}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(handle_timeout_error))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
);将中间件错误转换为HTTP响应:
rust
use axum::{
error_handling::HandleErrorLayer,
http::{StatusCode, Method, Uri},
BoxError,
};
use tower::ServiceBuilder;
use std::time::Duration;
async fn handle_timeout_error(
method: Method,
uri: Uri,
err: BoxError,
) -> (StatusCode, String) {
if err.is::<tower::timeout::error::Elapsed>() {
(
StatusCode::REQUEST_TIMEOUT,
format!("`{} {}` request timed out", method, uri),
)
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("`{} {}` failed with {}", method, uri, err),
)
}
}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(handle_timeout_error))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
);Custom Middleware with from_fn
使用from_fn创建自定义中间件
Create custom middleware using async functions:
rust
use axum::{
middleware::{self, Next},
extract::Request,
response::Response,
http::StatusCode,
};
async fn auth_middleware(
req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let auth_header = req.headers()
.get("authorization")
.and_then(|h| h.to_str().ok());
if let Some(auth_header) = auth_header {
if validate_token(auth_header).await {
Ok(next.run(req).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
let app = Router::new()
.route("/protected", get(handler))
.layer(middleware::from_fn(auth_middleware));Passing data from middleware to handler:
rust
use axum::extract::Extension;
#[derive(Clone)]
struct CurrentUser {
id: u32,
username: String,
}
async fn auth_middleware(
mut req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let auth_header = req.headers()
.get("authorization")
.and_then(|h| h.to_str().ok())
.ok_or(StatusCode::UNAUTHORIZED)?;
if let Some(user) = authorize_user(auth_header).await {
req.extensions_mut().insert(user);
Ok(next.run(req).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
async fn handler(Extension(user): Extension<CurrentUser>) -> String {
format!("Hello, {}", user.username)
}
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(auth_middleware));使用异步函数创建自定义中间件:
rust
use axum::{
middleware::{self, Next},
extract::Request,
response::Response,
http::StatusCode,
};
async fn auth_middleware(
req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let auth_header = req.headers()
.get("authorization")
.and_then(|h| h.to_str().ok());
if let Some(auth_header) = auth_header {
if validate_token(auth_header).await {
Ok(next.run(req).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
let app = Router::new()
.route("/protected", get(handler))
.layer(middleware::from_fn(auth_middleware));从中间件向处理函数传递数据:
rust
use axum::extract::Extension;
#[derive(Clone)]
struct CurrentUser {
id: u32,
username: String,
}
async fn auth_middleware(
mut req: Request,
next: Next,
) -> Result<Response, StatusCode> {
let auth_header = req.headers()
.get("authorization")
.and_then(|h| h.to_str().ok())
.ok_or(StatusCode::UNAUTHORIZED)?;
if let Some(user) = authorize_user(auth_header).await {
req.extensions_mut().insert(user);
Ok(next.run(req).await)
} else {
Err(StatusCode::UNAUTHORIZED)
}
}
async fn handler(Extension(user): Extension<CurrentUser>) -> String {
format!("Hello, {}", user.username)
}
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(auth_middleware));Custom Tower Middleware
自定义Tower中间件
Implement Tower's and traits for full control:
ServiceLayerrust
use tower::{Service, Layer};
use axum::{response::Response, extract::Request};
use std::task::{Context, Poll};
use futures_core::future::BoxFuture;
#[derive(Clone)]
struct MyLayer;
impl<S> Layer<S> for MyLayer {
type Service = MyMiddleware<S>;
fn layer(&self, inner: S) -> Self::Service {
MyMiddleware { inner }
}
}
#[derive(Clone)]
struct MyMiddleware<S> {
inner: S,
}
impl<S> Service<Request> for MyMiddleware<S>
where
S: Service<Request, Response = Response> + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: Request) -> Self::Future {
// Process request
let future = self.inner.call(request);
Box::pin(async move {
let response: Response = future.await?;
// Process response
Ok(response)
})
}
}
let app = Router::new()
.route("/", get(handler))
.layer(MyLayer);实现Tower的和 trait以获得完全控制权:
ServiceLayerrust
use tower::{Service, Layer};
use axum::{response::Response, extract::Request};
use std::task::{Context, Poll};
use futures_core::future::BoxFuture;
#[derive(Clone)]
struct MyLayer;
impl<S> Layer<S> for MyLayer {
type Service = MyMiddleware<S>;
fn layer(&self, inner: S) -> Self::Service {
MyMiddleware { inner }
}
}
#[derive(Clone)]
struct MyMiddleware<S> {
inner: S,
}
impl<S> Service<Request> for MyMiddleware<S>
where
S: Service<Request, Response = Response> + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: Request) -> Self::Future {
// 处理请求
let future = self.inner.call(request);
Box::pin(async move {
let response: Response = future.await?;
// 处理响应
Ok(response)
})
}
}
let app = Router::new()
.route("/", get(handler))
.layer(MyLayer);Middleware with State Access
访问状态的中间件
Create middleware that accesses application state:
rust
use axum::extract::State;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db: Arc<Database>,
}
#[derive(Clone)]
struct MyLayer {
state: AppState,
}
impl<S> Layer<S> for MyLayer {
type Service = MyService<S>;
fn layer(&self, inner: S) -> Self::Service {
MyService {
inner,
state: self.state.clone(),
}
}
}
#[derive(Clone)]
struct MyService<S> {
inner: S,
state: AppState,
}
impl<S> Service<Request> for MyService<S>
where
S: Service<Request>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
// Use self.state here
self.inner.call(req)
}
}
let state = AppState {
db: Arc::new(Database::new()),
};
let app = Router::new()
.route("/", get(handler))
.layer(MyLayer { state: state.clone() })
.with_state(state);创建可访问应用状态的中间件:
rust
use axum::extract::State;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db: Arc<Database>,
}
#[derive(Clone)]
struct MyLayer {
state: AppState,
}
impl<S> Layer<S> for MyLayer {
type Service = MyService<S>;
fn layer(&self, inner: S) -> Self::Service {
MyService {
inner,
state: self.state.clone(),
}
}
}
#[derive(Clone)]
struct MyService<S> {
inner: S,
state: AppState,
}
impl<S> Service<Request> for MyService<S>
where
S: Service<Request>,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
// 在此处使用self.state
self.inner.call(req)
}
}
let state = AppState {
db: Arc::new(Database::new()),
};
let app = Router::new()
.route("/", get(handler))
.layer(MyLayer { state: state.clone() })
.with_state(state);Middleware Before Routing
路由前的中间件
Apply middleware before routing (e.g., for URI rewriting):
rust
use tower::Layer;
use axum::ServiceExt;
fn rewrite_request_uri(req: Request) -> Request {
// Modify request URI
req
}
let middleware = tower::util::MapRequestLayer::new(rewrite_request_uri);
let app = Router::new()
.route("/", get(handler));
// Apply layer around entire router
let app_with_middleware = middleware.layer(app);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app_with_middleware.into_make_service()).await?;在路由匹配前应用中间件(例如URI重写):
rust
use tower::Layer;
use axum::ServiceExt;
fn rewrite_request_uri(req: Request) -> Request {
// 修改请求URI
req
}
let middleware = tower::util::MapRequestLayer::new(rewrite_request_uri);
let app = Router::new()
.route("/", get(handler));
// 将层应用于整个路由器
let app_with_middleware = middleware.layer(app);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app_with_middleware.into_make_service()).await?;State Management
状态管理
Basic State
基础状态
Simple state with cloneable types:
rust
use axum::extract::State;
#[derive(Clone)]
struct AppState {
config: Config,
api_key: String,
}
async fn handler(State(state): State<AppState>) -> String {
format!("Config: {:?}, Key: {}", state.config, state.api_key)
}
let state = AppState {
config: Config::default(),
api_key: "secret".to_string(),
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);使用可克隆类型的简单状态:
rust
use axum::extract::State;
#[derive(Clone)]
struct AppState {
config: Config,
api_key: String,
}
async fn handler(State(state): State<AppState>) -> String {
format!("Config: {:?}, Key: {}", state.config, state.api_key)
}
let state = AppState {
config: Config::default(),
api_key: "secret".to_string(),
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);Shared State with Arc
使用Arc的共享状态
Use for shared ownership of expensive-to-clone types:
Arcrust
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
cache: Arc<RwLock<HashMap<String, String>>>,
config: Config, // Cheap to clone
}
async fn handler(State(state): State<AppState>) -> Result<String, StatusCode> {
// Access database pool
let conn = state.db_pool.get().await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// Access cache (read)
let cache = state.cache.read().await;
let value = cache.get("key");
// Access cache (write)
drop(cache); // Release read lock
let mut cache = state.cache.write().await;
cache.insert("new_key".to_string(), "value".to_string());
Ok("Success".to_string())
}使用实现对克隆成本高的类型的共享所有权:
Arcrust
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
cache: Arc<RwLock<HashMap<String, String>>>,
config: Config, // 克隆成本低
}
async fn handler(State(state): State<AppState>) -> Result<String, StatusCode> {
// 访问数据库连接池
let conn = state.db_pool.get().await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// 访问缓存(读)
let cache = state.cache.read().await;
let value = cache.get("key");
// 访问缓存(写)
drop(cache); // 释放读锁
let mut cache = state.cache.write().await;
cache.insert("new_key".to_string(), "value".to_string());
Ok("Success".to_string())
}Multiple State Types
多状态类型
Use different state types for different router sections:
rust
#[derive(Clone)]
struct ApiState {
api_key: String,
}
#[derive(Clone)]
struct AppState {
db: Arc<Database>,
}
fn api_routes() -> Router<ApiState> {
Router::new()
.route("/data", get(|State(state): State<ApiState>| async move {
format!("API Key: {}", state.api_key)
}))
}
fn app_routes() -> Router<AppState> {
Router::new()
.route("/users", get(|State(state): State<AppState>| async move {
"Users".to_string()
}))
}
let api_state = ApiState { api_key: "secret".to_string() };
let app_state = AppState { db: Arc::new(Database::new()) };
let app = Router::new()
.nest("/api", api_routes().with_state(api_state))
.nest("/app", app_routes().with_state(app_state));为不同的路由器部分使用不同的状态类型:
rust
#[derive(Clone)]
struct ApiState {
api_key: String,
}
#[derive(Clone)]
struct AppState {
db: Arc<Database>,
}
fn api_routes() -> Router<ApiState> {
Router::new()
.route("/data", get(|State(state): State<ApiState>| async move {
format!("API Key: {}", state.api_key)
}))
}
fn app_routes() -> Router<AppState> {
Router::new()
.route("/users", get(|State(state): State<AppState>| async move {
"Users".to_string()
}))
}
let api_state = ApiState { api_key: "secret".to_string() };
let app_state = AppState { db: Arc::new(Database::new()) };
let app = Router::new()
.nest("/api", api_routes().with_state(api_state))
.nest("/app", app_routes().with_state(app_state));Generic State in Functions
函数中的泛型状态
Return routers with generic state for flexibility:
rust
fn routes<S>() -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
Router::new()
.route("/health", get(|| async { "OK" }))
.route("/version", get(|| async { "1.0.0" }))
}
// Can be combined with any state type
let app = Router::new()
.merge(routes())
.route("/", get(handler))
.with_state(AppState { /* ... */ });返回带有泛型状态的路由器以提高灵活性:
rust
fn routes<S>() -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
Router::new()
.route("/health", get(|| async { "OK" }))
.route("/version", get(|| async { "1.0.0" }))
}
// 可与任何状态类型组合
let app = Router::new()
.merge(routes())
.route("/", get(handler))
.with_state(AppState { /* ... */ });State Transitions
状态转换
Chain routers with different state requirements:
rust
#[derive(Clone)]
struct StateA {
data_a: String,
}
#[derive(Clone)]
struct StateB {
data_b: String,
}
let router_a: Router<StateA> = Router::new()
.route("/a", get(|State(s): State<StateA>| async move { s.data_a }));
// Provide StateA, next missing state is StateB
let router_b: Router<StateB> = router_a.with_state(StateA {
data_a: "A".to_string(),
});
// Add routes needing StateB
let router_b = router_b
.route("/b", get(|State(s): State<StateB>| async move { s.data_b }));
// Provide StateB, now we have Router<()>
let app: Router<()> = router_b.with_state(StateB {
data_b: "B".to_string(),
});链接具有不同状态要求的路由器:
rust
#[derive(Clone)]
struct StateA {
data_a: String,
}
#[derive(Clone)]
struct StateB {
data_b: String,
}
let router_a: Router<StateA> = Router::new()
.route("/a", get(|State(s): State<StateA>| async move { s.data_a }));
// 提供StateA,接下来缺少的状态是StateB
let router_b: Router<StateB> = router_a.with_state(StateA {
data_a: "A".to_string(),
});
// 添加需要StateB的路由
let router_b = router_b
.route("/b", get(|State(s): State<StateB>| async move { s.data_b }));
// 提供StateB,现在得到Router<()>
let app: Router<()> = router_b.with_state(StateB {
data_b: "B".to_string(),
});Error Handling
错误处理
Basic Error Handling with Result
使用Result的基础错误处理
Use to handle errors in handlers:
Resultrust
use axum::http::StatusCode;
async fn handler() -> Result<String, StatusCode> {
let result = some_operation().await;
match result {
Ok(data) => Ok(format!("Success: {}", data)),
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
}
}在处理函数中使用处理错误:
Resultrust
use axum::http::StatusCode;
async fn handler() -> Result<String, StatusCode> {
let result = some_operation().await;
match result {
Ok(data) => Ok(format!("Success: {}", data)),
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
}
}Custom Error Types
自定义错误类型
Implement for custom errors:
IntoResponserust
use axum::{
response::{IntoResponse, Response},
http::StatusCode,
Json,
};
use serde::Serialize;
#[derive(Debug)]
enum AppError {
Database(sqlx::Error),
NotFound,
Unauthorized,
ValidationError(String),
}
#[derive(Serialize)]
struct ErrorResponse {
error: String,
message: String,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, error_message) = match self {
AppError::Database(e) => {
tracing::error!("Database error: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
}
AppError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, &msg),
};
let body = Json(ErrorResponse {
error: status.to_string(),
message: error_message.to_string(),
});
(status, body).into_response()
}
}
// Use in handler
async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, AppError> {
let user = db.get_user(id).await.map_err(AppError::Database)?;
user.ok_or(AppError::NotFound).map(Json)
}为自定义错误实现:
IntoResponserust
use axum::{
response::{IntoResponse, Response},
http::StatusCode,
Json,
};
use serde::Serialize;
#[derive(Debug)]
enum AppError {
Database(sqlx::Error),
NotFound,
Unauthorized,
ValidationError(String),
}
#[derive(Serialize)]
struct ErrorResponse {
error: String,
message: String,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, error_message) = match self {
AppError::Database(e) => {
tracing::error!("Database error: {}", e);
(StatusCode::INTERNAL_SERVER_ERROR, "Database error")
}
AppError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized"),
AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, &msg),
};
let body = Json(ErrorResponse {
error: status.to_string(),
message: error_message.to_string(),
});
(status, body).into_response()
}
}
// 在处理函数中使用
async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, AppError> {
let user = db.get_user(id).await.map_err(AppError::Database)?;
user.ok_or(AppError::NotFound).map(Json)
}Extractor Rejection Handling
提取器拒绝处理
Handle extractor rejections for better error messages:
rust
use axum::extract::rejection::JsonRejection;
async fn create_user(
payload: Result<Json<CreateUser>, JsonRejection>,
) -> Result<Json<User>, AppError> {
let Json(create_user) = payload.map_err(|err| match err {
JsonRejection::MissingJsonContentType(_) => {
AppError::ValidationError("Content-Type must be application/json".to_string())
}
JsonRejection::JsonDataError(e) => {
AppError::ValidationError(format!("Invalid JSON: {}", e))
}
JsonRejection::JsonSyntaxError(e) => {
AppError::ValidationError(format!("JSON syntax error: {}", e))
}
_ => AppError::ValidationError("Invalid request body".to_string()),
})?;
// Process create_user
Ok(Json(user))
}处理提取器拒绝以提供更好的错误消息:
rust
use axum::extract::rejection::JsonRejection;
async fn create_user(
payload: Result<Json<CreateUser>, JsonRejection>,
) -> Result<Json<User>, AppError> {
let Json(create_user) = payload.map_err(|err| match err {
JsonRejection::MissingJsonContentType(_) => {
AppError::ValidationError("Content-Type必须是application/json".to_string())
}
JsonRejection::JsonDataError(e) => {
AppError::ValidationError(format!("Invalid JSON: {}", e))
}
JsonRejection::JsonSyntaxError(e) => {
AppError::ValidationError(format!("JSON语法错误: {}", e))
}
_ => AppError::ValidationError("Invalid request body".to_string()),
})?;
// 处理create_user
Ok(Json(user))
}Custom Extractors with Error Handling
带错误处理的自定义提取器
Create extractors with custom rejection types:
rust
use axum::{
extract::{FromRequest, Request},
response::{IntoResponse, Response},
async_trait,
};
struct ValidatedJson<T>(T);
#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
T: serde::de::DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(data) = Json::<T>::from_request(req, state)
.await
.map_err(|err| {
(
StatusCode::BAD_REQUEST,
format!("Invalid JSON: {}", err),
).into_response()
})?;
data.validate().map_err(|err| {
(
StatusCode::BAD_REQUEST,
format!("Validation error: {}", err),
).into_response()
})?;
Ok(ValidatedJson(data))
}
}
async fn handler(ValidatedJson(data): ValidatedJson<MyData>) -> String {
// data is validated
format!("Received: {:?}", data)
}创建带有自定义拒绝类型的提取器:
rust
use axum::{
extract::{FromRequest, Request},
response::{IntoResponse, Response},
async_trait,
};
struct ValidatedJson<T>(T);
#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
T: serde::de::DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(data) = Json::<T>::from_request(req, state)
.await
.map_err(|err| {
(
StatusCode::BAD_REQUEST,
format!("Invalid JSON: {}", err),
).into_response()
})?;
data.validate().map_err(|err| {
(
StatusCode::BAD_REQUEST,
format!("Validation error: {}", err),
).into_response()
})?;
Ok(ValidatedJson(data))
}
}
async fn handler(ValidatedJson(data): ValidatedJson<MyData>) -> String {
// data已验证
format!("Received: {:?}", data)
}Middleware Error Handling
中间件错误处理
Handle errors from fallible middleware:
rust
use axum::error_handling::HandleErrorLayer;
use tower::ServiceBuilder;
async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
if err.is::<tower::timeout::error::Elapsed>() {
(
StatusCode::REQUEST_TIMEOUT,
"Request took too long".to_string(),
)
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled error: {}", err),
)
}
}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(handle_timeout_error))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
);处理可能失败的中间件的错误:
rust
use axum::error_handling::HandleErrorLayer;
use tower::ServiceBuilder;
async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
if err.is::<tower::timeout::error::Elapsed>() {
(
StatusCode::REQUEST_TIMEOUT,
"Request took too long".to_string(),
)
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled error: {}", err),
)
}
}
let app = Router::new()
.route("/", get(handler))
.layer(
ServiceBuilder::new()
.layer(HandleErrorLayer::new(handle_timeout_error))
.layer(TimeoutLayer::new(Duration::from_secs(30)))
);Fallible Services
可能失败的服务
Route to services that can fail:
rust
use axum::error_handling::HandleError;
async fn fallible_operation() -> Result<(), anyhow::Error> {
// Operation that might fail
Ok(())
}
let fallible_service = tower::service_fn(|_req| async {
fallible_operation().await?;
Ok::<_, anyhow::Error>(Response::new(Body::empty()))
});
async fn handle_error(err: anyhow::Error) -> (StatusCode, String) {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
)
}
let app = Router::new().route_service(
"/",
HandleError::new(fallible_service, handle_error),
);路由到可能失败的服务:
rust
use axum::error_handling::HandleError;
async fn fallible_operation() -> Result<(), anyhow::Error> {
// 可能失败的操作
Ok(())
}
let fallible_service = tower::service_fn(|_req| async {
fallible_operation().await?;
Ok::<_, anyhow::Error>(Response::new(Body::empty()))
});
async fn handle_error(err: anyhow::Error) -> (StatusCode, String) {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
)
}
let app = Router::new().route_service(
"/",
HandleError::new(fallible_service, handle_error),
);Response Building
响应构建
JSON Responses
JSON响应
Return JSON with type safety:
rust
use axum::Json;
use serde::Serialize;
#[derive(Serialize)]
struct ApiResponse {
success: bool,
data: String,
}
async fn handler() -> Json<ApiResponse> {
Json(ApiResponse {
success: true,
data: "Hello".to_string(),
})
}返回类型安全的JSON:
rust
use axum::Json;
use serde::Serialize;
#[derive(Serialize)]
struct ApiResponse {
success: bool,
data: String,
}
async fn handler() -> Json<ApiResponse> {
Json(ApiResponse {
success: true,
data: "Hello".to_string(),
})
}HTML Responses
HTML响应
Serve HTML content:
rust
use axum::response::Html;
async fn handler() -> Html<&'static str> {
Html("<h1>Hello, World!</h1>")
}
async fn dynamic_html(Path(name): Path<String>) -> Html<String> {
Html(format!("<h1>Hello, {}</h1>", name))
}提供HTML内容:
rust
use axum::response::Html;
async fn handler() -> Html<&'static str> {
Html("<h1>Hello, World!</h1>")
}
async fn dynamic_html(Path(name): Path<String>) -> Html<String> {
Html(format!("<h1>Hello, {}</h1>", name))
}Status Code Responses
状态码响应
Return different status codes:
rust
use axum::http::StatusCode;
async fn handler() -> StatusCode {
StatusCode::NO_CONTENT
}
async fn with_body() -> (StatusCode, String) {
(StatusCode::CREATED, "Resource created".to_string())
}
async fn json_with_status() -> (StatusCode, Json<ApiResponse>) {
(StatusCode::CREATED, Json(ApiResponse { /* ... */ }))
}返回不同的状态码:
rust
use axum::http::StatusCode;
async fn handler() -> StatusCode {
StatusCode::NO_CONTENT
}
async fn with_body() -> (StatusCode, String) {
(StatusCode::CREATED, "Resource created".to_string())
}
async fn json_with_status() -> (StatusCode, Json<ApiResponse>) {
(StatusCode::CREATED, Json(ApiResponse { /* ... */ }))
}Custom Headers
自定义头
Add custom headers to responses:
rust
use axum::http::{HeaderMap, header};
async fn handler() -> (HeaderMap, String) {
let mut headers = HeaderMap::new();
headers.insert(header::CACHE_CONTROL, "max-age=3600".parse().unwrap());
headers.insert("X-Custom-Header", "value".parse().unwrap());
(headers, "Response body".to_string())
}向响应添加自定义头:
rust
use axum::http::{HeaderMap, header};
async fn handler() -> (HeaderMap, String) {
let mut headers = HeaderMap::new();
headers.insert(header::CACHE_CONTROL, "max-age=3600".parse().unwrap());
headers.insert("X-Custom-Header", "value".parse().unwrap());
(headers, "Response body".to_string())
}Full Response Control
完全控制响应
Build complete responses:
rust
use axum::{
response::Response,
http::{StatusCode, header},
body::Body,
};
async fn handler() -> Response {
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/json")
.header("X-Custom", "value")
.body(Body::from(r#"{"status":"ok"}"#))
.unwrap()
}构建完整的响应:
rust
use axum::{
response::Response,
http::{StatusCode, header},
body::Body,
};
async fn handler() -> Response {
Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/json")
.header("X-Custom", "value")
.body(Body::from(r#"{"status":"ok"}"#))
.unwrap()
}Streaming Responses
流式响应
Stream data to clients:
rust
use axum::response::sse::{Event, Sse};
use futures::stream::{self, Stream};
use std::convert::Infallible;
async fn sse_handler() -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
let stream = stream::iter(0..10).map(|i| {
Ok(Event::default().data(format!("Event {}", i)))
});
Sse::new(stream)
}向客户端流式传输数据:
rust
use axum::response::sse::{Event, Sse};
use futures::stream::{self, Stream};
use std::convert::Infallible;
async fn sse_handler() -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
let stream = stream::iter(0..10).map(|i| {
Ok(Event::default().data(format!("Event {}", i)))
});
Sse::new(stream)
}File Downloads
文件下载
Serve files for download:
rust
use axum::{
response::{Response, IntoResponse},
http::{header, StatusCode},
body::Body,
};
use tokio::fs::File;
async fn download_file() -> Result<Response, StatusCode> {
let file = File::open("path/to/file.pdf")
.await
.map_err(|_| StatusCode::NOT_FOUND)?;
let body = Body::from_stream(tokio_util::io::ReaderStream::new(file));
Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/pdf")
.header(
header::CONTENT_DISPOSITION,
"attachment; filename=\"file.pdf\"",
)
.body(body)
.unwrap())
}提供文件下载:
rust
use axum::{
response::{Response, IntoResponse},
http::{header, StatusCode},
body::Body,
};
use tokio::fs::File;
async fn download_file() -> Result<Response, StatusCode> {
let file = File::open("path/to/file.pdf")
.await
.map_err(|_| StatusCode::NOT_FOUND)?;
let body = Body::from_stream(tokio_util::io::ReaderStream::new(file));
Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/pdf")
.header(
header::CONTENT_DISPOSITION,
"attachment; filename=\"file.pdf\"",
)
.body(body)
.unwrap())
}Production Patterns
生产模式
Database Integration
数据库集成
Integrate with database pools:
rust
use sqlx::{PgPool, postgres::PgPoolOptions};
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db: PgPool,
}
async fn get_user(
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<User>, AppError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_optional(&state.db)
.await
.map_err(AppError::Database)?
.ok_or(AppError::NotFound)?;
Ok(Json(user))
}
#[tokio::main]
async fn main() {
let db = PgPoolOptions::new()
.max_connections(5)
.connect(&env::var("DATABASE_URL").unwrap())
.await
.unwrap();
let state = AppState { db };
let app = Router::new()
.route("/users/:id", get(get_user))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}与数据库连接池集成:
rust
use sqlx::{PgPool, postgres::PgPoolOptions};
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db: PgPool,
}
async fn get_user(
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<User>, AppError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_optional(&state.db)
.await
.map_err(AppError::Database)?
.ok_or(AppError::NotFound)?;
Ok(Json(user))
}
#[tokio::main]
async fn main() {
let db = PgPoolOptions::new()
.max_connections(5)
.connect(&env::var("DATABASE_URL").unwrap())
.await
.unwrap();
let state = AppState { db };
let app = Router::new()
.route("/users/:id", get(get_user))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}Configuration Management
配置管理
Use environment variables and configuration:
rust
use serde::Deserialize;
use config::{Config, ConfigError, Environment};
#[derive(Debug, Deserialize, Clone)]
struct Settings {
database_url: String,
redis_url: String,
jwt_secret: String,
port: u16,
}
impl Settings {
fn new() -> Result<Self, ConfigError> {
Config::builder()
.add_source(Environment::default())
.build()?
.try_deserialize()
}
}
#[derive(Clone)]
struct AppState {
settings: Settings,
db: PgPool,
}
#[tokio::main]
async fn main() {
let settings = Settings::new().expect("Failed to load configuration");
let db = PgPoolOptions::new()
.connect(&settings.database_url)
.await
.expect("Failed to connect to database");
let state = AppState {
settings: settings.clone(),
db,
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);
let addr = format!("0.0.0.0:{}", settings.port);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}使用环境变量和配置:
rust
use serde::Deserialize;
use config::{Config, ConfigError, Environment};
#[derive(Debug, Deserialize, Clone)]
struct Settings {
database_url: String,
redis_url: String,
jwt_secret: String,
port: u16,
}
impl Settings {
fn new() -> Result<Self, ConfigError> {
Config::builder()
.add_source(Environment::default())
.build()?
.try_deserialize()
}
}
#[derive(Clone)]
struct AppState {
settings: Settings,
db: PgPool,
}
#[tokio::main]
async fn main() {
let settings = Settings::new().expect("Failed to load configuration");
let db = PgPoolOptions::new()
.connect(&settings.database_url)
.await
.expect("Failed to connect to database");
let state = AppState {
settings: settings.clone(),
db,
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);
let addr = format!("0.0.0.0:{}", settings.port);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}Structured Logging
结构化日志
Implement comprehensive logging:
rust
use tracing::{info, error, debug, instrument};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "info".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
info!("Starting server");
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
info!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}
#[instrument]
async fn handler(Path(id): Path<u32>) -> Result<String, AppError> {
debug!("Handling request for user {}", id);
match get_user_from_db(id).await {
Ok(user) => {
info!("Successfully retrieved user {}", id);
Ok(format!("User: {}", user))
}
Err(e) => {
error!("Failed to get user {}: {}", id, e);
Err(AppError::Database(e))
}
}
}实现全面的日志记录:
rust
use tracing::{info, error, debug, instrument};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "info".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
info!("Starting server");
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
info!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}
#[instrument]
async fn handler(Path(id): Path<u32>) -> Result<String, AppError> {
debug!("Handling request for user {}", id);
match get_user_from_db(id).await {
Ok(user) => {
info!("Successfully retrieved user {}", id);
Ok(format!("User: {}", user))
}
Err(e) => {
error!("Failed to get user {}: {}", id, e);
Err(AppError::Database(e))
}
}
}Graceful Shutdown
优雅关闭
Implement graceful shutdown handling:
rust
use tokio::signal;
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
println!("Signal received, starting graceful shutdown");
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(handler));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap();
}实现优雅关闭处理:
rust
use tokio::signal;
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
println!("Signal received, starting graceful shutdown");
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(handler));
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap();
}Health Checks
健康检查
Implement health check endpoints:
rust
use serde::Serialize;
#[derive(Serialize)]
struct HealthResponse {
status: String,
database: String,
cache: String,
}
async fn health_check(State(state): State<AppState>) -> Json<HealthResponse> {
let db_status = match sqlx::query("SELECT 1").fetch_one(&state.db).await {
Ok(_) => "healthy",
Err(_) => "unhealthy",
};
let cache_status = match state.redis.ping().await {
Ok(_) => "healthy",
Err(_) => "unhealthy",
};
Json(HealthResponse {
status: if db_status == "healthy" && cache_status == "healthy" {
"healthy".to_string()
} else {
"degraded".to_string()
},
database: db_status.to_string(),
cache: cache_status.to_string(),
})
}
let app = Router::new()
.route("/health", get(health_check))
.route("/ready", get(readiness_check))
.with_state(state);实现健康检查端点:
rust
use serde::Serialize;
#[derive(Serialize)]
struct HealthResponse {
status: String,
database: String,
cache: String,
}
async fn health_check(State(state): State<AppState>) -> Json<HealthResponse> {
let db_status = match sqlx::query("SELECT 1").fetch_one(&state.db).await {
Ok(_) => "healthy",
Err(_) => "unhealthy",
};
let cache_status = match state.redis.ping().await {
Ok(_) => "healthy",
Err(_) => "unhealthy",
};
Json(HealthResponse {
status: if db_status == "healthy" && cache_status == "healthy" {
"healthy".to_string()
} else {
"degraded".to_string()
},
database: db_status.to_string(),
cache: cache_status.to_string(),
})
}
let app = Router::new()
.route("/health", get(health_check))
.route("/ready", get(readiness_check))
.with_state(state);Rate Limiting
速率限制
Implement rate limiting:
rust
use tower::limit::RateLimitLayer;
use std::time::Duration;
let app = Router::new()
.route("/api/data", get(handler))
.layer(RateLimitLayer::new(
100, // max requests
Duration::from_secs(60), // per minute
));实现速率限制:
rust
use tower::limit::RateLimitLayer;
use std::time::Duration;
let app = Router::new()
.route("/api/data", get(handler))
.layer(RateLimitLayer::new(
100, // 最大请求数
Duration::from_secs(60), // 每分钟
));Request Validation
请求验证
Validate requests with custom extractors:
rust
use validator::{Validate, ValidationError};
#[derive(Debug, Deserialize, Validate)]
struct CreateUserRequest {
#[validate(length(min = 3, max = 50))]
username: String,
#[validate(email)]
email: String,
#[validate(length(min = 8))]
password: String,
}
struct ValidatedJson<T>(T);
#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = (StatusCode, String);
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(data) = Json::<T>::from_request(req, state)
.await
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
data.validate()
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
Ok(ValidatedJson(data))
}
}
async fn create_user(
ValidatedJson(user): ValidatedJson<CreateUserRequest>,
) -> Result<Json<User>, AppError> {
// user is validated
Ok(Json(User { /* ... */ }))
}使用自定义提取器验证请求:
rust
use validator::{Validate, ValidationError};
#[derive(Debug, Deserialize, Validate)]
struct CreateUserRequest {
#[validate(length(min = 3, max = 50))]
username: String,
#[validate(email)]
email: String,
#[validate(length(min = 8))]
password: String,
}
struct ValidatedJson<T>(T);
#[async_trait]
impl<S, T> FromRequest<S> for ValidatedJson<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = (StatusCode, String);
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(data) = Json::<T>::from_request(req, state)
.await
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
data.validate()
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
Ok(ValidatedJson(data))
}
}
async fn create_user(
ValidatedJson(user): ValidatedJson<CreateUserRequest>,
) -> Result<Json<User>, AppError> {
// user已验证
Ok(Json(User { /* ... */ }))
}Testing
测试
Unit Testing Handlers
处理函数单元测试
Test handlers in isolation:
rust
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use tower::ServiceExt;
#[tokio::test]
async fn test_handler() {
let app = Router::new().route("/", get(handler));
let response = app
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
}孤立测试处理函数:
rust
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use tower::ServiceExt;
#[tokio::test]
async fn test_handler() {
let app = Router::new().route("/", get(handler));
let response = app
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
}Integration Testing
集成测试
Test full application:
rust
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode, Method};
use tower::ServiceExt;
use serde_json::json;
#[tokio::test]
async fn test_create_user() {
let state = AppState::test();
let app = create_app(state);
let request_body = json!({
"username": "testuser",
"email": "test@example.com"
});
let response = app
.oneshot(
Request::builder()
.method(Method::POST)
.uri("/users")
.header("content-type", "application/json")
.body(Body::from(request_body.to_string()))
.unwrap()
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
}
}测试完整应用:
rust
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode, Method};
use tower::ServiceExt;
use serde_json::json;
#[tokio::test]
async fn test_create_user() {
let state = AppState::test();
let app = create_app(state);
let request_body = json!({
"username": "testuser",
"email": "test@example.com"
});
let response = app
.oneshot(
Request::builder()
.method(Method::POST)
.uri("/users")
.header("content-type", "application/json")
.body(Body::from(request_body.to_string()))
.unwrap()
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
}
}Best Practices
最佳实践
Handler Organization
处理函数组织
- Keep handlers thin - Move business logic to service layer
- Use extractors - Let type system handle extraction
- Return Result types - Use custom error types
- Instrument handlers - Add tracing for observability
- 保持处理函数简洁 - 将业务逻辑移至服务层
- 使用提取器 - 让类型系统处理数据提取
- 返回Result类型 - 使用自定义错误类型
- 为处理函数添加 instrumentation - 添加追踪以提高可观察性
State Management
状态管理
- Use Arc for expensive types - Database pools, caches
- Keep state minimal - Only what's truly shared
- Implement Clone - Required for State extractor
- Avoid mutation - Use interior mutability (RwLock, Mutex) when needed
- 对成本高的类型使用Arc - 数据库连接池、缓存
- 保持状态最小化 - 仅保留真正需要共享的数据
- 实现Clone - State提取器要求
- 避免突变 - 需要时使用内部可变性(RwLock、Mutex)
Error Handling
错误处理
- Create custom error types - Implement IntoResponse
- Provide context - Include helpful error messages
- Log errors appropriately - Use tracing
- Return proper status codes - Match HTTP semantics
- 创建自定义错误类型 - 实现IntoResponse
- 提供上下文 - 包含有用的错误消息
- 适当记录错误 - 使用tracing
- 返回正确的状态码 - 符合HTTP语义
Middleware
中间件
- Use ServiceBuilder - More intuitive ordering
- Apply at right level - Router vs route vs method
- Handle errors - Use HandleErrorLayer for fallible middleware
- Keep middleware focused - Single responsibility
- 使用ServiceBuilder - 顺序更直观
- 在正确的级别应用 - 路由器 vs 路由 vs 方法
- 处理错误 - 对可能失败的中间件使用HandleErrorLayer
- 保持中间件聚焦 - 单一职责
Performance
性能
- Use connection pooling - For databases and external services
- Enable compression - CompressionLayer for responses
- Implement caching - Reduce redundant operations
- Use backpressure - LoadShedLayer, RateLimitLayer
- Optimize serialization - Use efficient JSON libraries
- 使用连接池 - 针对数据库和外部服务
- 启用压缩 - 对响应使用CompressionLayer
- 实现缓存 - 减少冗余操作
- 使用背压 - LoadShedLayer、RateLimitLayer
- 优化序列化 - 使用高效的JSON库
Security
安全
- Validate inputs - Use custom extractors with validation
- Implement authentication - Use middleware for auth
- Use HTTPS - In production environments
- Set security headers - CORS, CSP, etc.
- Rate limit - Prevent abuse
- 验证输入 - 使用带验证的自定义提取器
- 实现认证 - 使用中间件处理认证
- 使用HTTPS - 在生产环境中
- 设置安全头 - CORS、CSP等
- 速率限制 - 防止滥用
Common Patterns
常见模式
Repository Pattern
仓库模式
rust
trait UserRepository {
async fn get(&self, id: u32) -> Result<User, AppError>;
async fn create(&self, user: CreateUser) -> Result<User, AppError>;
async fn update(&self, id: u32, user: UpdateUser) -> Result<User, AppError>;
async fn delete(&self, id: u32) -> Result<(), AppError>;
}
struct PostgresUserRepository {
pool: PgPool,
}
impl UserRepository for PostgresUserRepository {
async fn get(&self, id: u32) -> Result<User, AppError> {
// Implementation
}
}
#[derive(Clone)]
struct AppState {
user_repo: Arc<dyn UserRepository + Send + Sync>,
}
async fn get_user(
State(state): State<AppState>,
Path(id): Path<u32>,
) -> Result<Json<User>, AppError> {
let user = state.user_repo.get(id).await?;
Ok(Json(user))
}rust
trait UserRepository {
async fn get(&self, id: u32) -> Result<User, AppError>;
async fn create(&self, user: CreateUser) -> Result<User, AppError>;
async fn update(&self, id: u32, user: UpdateUser) -> Result<User, AppError>;
async fn delete(&self, id: u32) -> Result<(), AppError>;
}
struct PostgresUserRepository {
pool: PgPool,
}
impl UserRepository for PostgresUserRepository {
async fn get(&self, id: u32) -> Result<User, AppError> {
// 实现
}
}
#[derive(Clone)]
struct AppState {
user_repo: Arc<dyn UserRepository + Send + Sync>,
}
async fn get_user(
State(state): State<AppState>,
Path(id): Path<u32>,
) -> Result<Json<User>, AppError> {
let user = state.user_repo.get(id).await?;
Ok(Json(user))
}Service Layer Pattern
服务层模式
rust
struct UserService {
repo: Arc<dyn UserRepository + Send + Sync>,
email_service: Arc<EmailService>,
}
impl UserService {
async fn create_user(&self, data: CreateUser) -> Result<User, AppError> {
let user = self.repo.create(data).await?;
self.email_service.send_welcome_email(&user).await?;
Ok(user)
}
}
#[derive(Clone)]
struct AppState {
user_service: Arc<UserService>,
}rust
struct UserService {
repo: Arc<dyn UserRepository + Send + Sync>,
email_service: Arc<EmailService>,
}
impl UserService {
async fn create_user(&self, data: CreateUser) -> Result<User, AppError> {
let user = self.repo.create(data).await?;
self.email_service.send_welcome_email(&user).await?;
Ok(user)
}
}
#[derive(Clone)]
struct AppState {
user_service: Arc<UserService>,
}Versioned APIs
版本化API
rust
fn v1_routes() -> Router<AppState> {
Router::new()
.route("/users", get(v1::list_users))
.route("/users/:id", get(v1::get_user))
}
fn v2_routes() -> Router<AppState> {
Router::new()
.route("/users", get(v2::list_users))
.route("/users/:id", get(v2::get_user))
}
let app = Router::new()
.nest("/api/v1", v1_routes())
.nest("/api/v2", v2_routes())
.with_state(state);Skill Version: 1.0.0
Last Updated: October 2025
Skill Category: Web Development, REST APIs, Rust, Async Programming
Compatible With: Axum 0.7+, Tokio 1.0+, Tower 0.4+
rust
fn v1_routes() -> Router<AppState> {
Router::new()
.route("/users", get(v1::list_users))
.route("/users/:id", get(v1::get_user))
}
fn v2_routes() -> Router<AppState> {
Router::new()
.route("/users", get(v2::list_users))
.route("/users/:id", get(v2::get_user))
}
let app = Router::new()
.nest("/api/v1", v1_routes())
.nest("/api/v2", v2_routes())
.with_state(state);指南版本: 1.0.0
最后更新: 2025年10月
指南分类: Web开发、REST API、Rust、异步编程
兼容版本: Axum 0.7+、Tokio 1.0+、Tower 0.4+