关于 axum-web-framework
这项技能介绍如何使用 Rust Web 框架 Axum 构建 Web 应用和 API,面向异步 Rust 开发者。内容涵盖路由、类型安全的请求提取、中间件、状态管理和错误处理。
主要功能
- 01配置路由与嵌套路由器
- 02提取类型安全的请求数据
- 03管理共享应用状态
- 04组合 Tower 中间件
- 05将错误转换为 HTTP 响应
适用场景
- 01使用异步 Rust 构建 REST API
- 02添加身份验证或速率限制
- 03创建 WebSocket 或 SSE 服务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ manutej/ luxor-claude-marketplace --skill axum-web-framework安装后在 Claude Code 中输入/axum-web-framework确认已加载。
附带文件EXAMPLES.mdREADME.md
详细介绍
Axum Web Framework
一份全面的技能指南,介绍如何使用 Axum 构建可用于生产环境的 Web 应用和 API。Axum 是一个基于 Tokio 和 Tower 构建的简洁、模块化 Rust Web 框架。掌握路由、提取器、中间件、状态管理、错误处理和部署模式。
何时使用此技能
在以下情况下使用此技能:
- 使用 Rust 和 async/await 构建 REST API
- 创建兼具高性能和类型安全的 Web 服务
- 借助 Tokio 生态系统集成开发微服务
- 实现 WebSocket 服务端或服务器发送事件(SSE)
- 使用 Rust 后端构建 GraphQL API
- 创建需要集成 Tower 的中间件密集型应用
- 开发需要健壮错误处理的生产级 Web 应用
- 构建需要精细控制 HTTP 请求和响应处理的系统
- 实现身份验证、授权和安全中间件
- 使用异步 Rust 创建实时 Web 应用
- 开发需要请求验证和转换的 API
- 使用复杂路由和嵌套路由器构建 Web 服务
- 实现速率限制、超时和背压处理
- 创建需要自定义提取器和响应类型的 Web 应用
核心概念
Axum 架构理念
Axum 构建于三大基本支柱之上:
- Tower 服务:Axum 的所有功能都基于 Tower 的
Servicetrait 构建,支持组合和中间件集成 - 类型安全的提取器:请求数据提取在编译时进行检查,避免运行时解析错误
- 尽量减少样板代码:采用简洁的 API,减少繁琐代码,同时保持明确性
路由器
Router 是 Axum 的核心构建模块。它根据路径和方法将 HTTP 请求映射到处理器。
主要属性:
- 按定义顺序匹配路由
- 可嵌套路由器,实现模块化组织
- 可在路由器、路由或方法级别应用中间件
- 状态类型采用泛型,便于灵活管理状态
- 实现 Tower 的
Servicetrait,支持组合
创建路由器:
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));
处理器
处理器是用于处理请求并返回响应的异步函数。Axum 借助强大的类型系统支持多种处理器签名。
处理器要求:
- 必须是异步函数
- 可使用提取器从请求中提取数据
- 必须返回实现了
IntoResponse的类型 - 最多可有 16 个参数(且都必须是提取器)
常见处理器模式:
// 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
}
提取器
提取器是实现了 FromRequest 或 FromRequestParts 的类型,可从请求中以类型安全的方式提取数据。
内置提取器:
- Path - 提取路径参数
- Query - 提取查询字符串参数
- Json - 解析 JSON 请求体
- Form - 解析表单编码的请求体
- State - 访问共享应用状态
- Extension - 访问请求扩展
- Headers - 访问请求标头
- Method - 获取 HTTP 方法
- Uri - 获取请求 URI
- Request - 获取完整请求
- Bytes - 将原始请求体作为字节获取
- String - 将请求体作为 UTF-8 字符串获取
- Multipart - 处理 multipart/form-data
提取器顺序:
- 消耗请求体的提取器必须放在最后
- 在同一处理器中使用多个请求体提取器会导致编译错误
State等非请求体提取器可按任意顺序排列
响应
任何实现了 IntoResponse 的类型都可以从处理器返回。Axum 提供了多种内置实现。
内置响应类型:
String、&'static str- 文本响应Json<T>- JSON 响应Html<String>- HTML 响应StatusCode- 仅包含状态码的响应(StatusCode, T)- 包含状态码和响应体的响应(Parts, T)- 包含自定义标头和响应体的响应Response- 完全控制响应Result<T, E>- 错误处理(其中 E: IntoResponse)
状态管理
Axum 使用 State 提取器在处理程序之间共享数据。状态必须实现 Clone,通常会用 Arc 包装以共享所有权。
状态模式:
- 简单状态:
#[derive(Clone)]
struct AppState {
api_key: String,
}
let app = Router::new()
.route("/", get(handler))
.with_state(AppState {
api_key: "secret".to_string(),
});
- 使用 Arc 的共享状态:
#[derive(Clone)]
struct AppState {
db_pool: Arc<DatabasePool>,
cache: Arc<RwLock<Cache>>,
}
- 多种状态类型:
// 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 中间件来自 Tower,可用于转换请求和响应、记录日志、身份验证等。
中间件类别:
- Tower 中间件 - 来自
tower和tower-httpcrate - 自定义中间件 - 使用
middleware::from_fn - 服务中间件 - 实现 Tower 的
Servicetrait - Layer 模式 - 使用 Tower 的
Layer实现可组合性
中间件应用顺序:
- 使用
.layer()应用时,按从下到上的顺序执行(包装之前的层) - 使用
ServiceBuilder应用时,按从上到下的顺序执行(更直观) Router::layer上的中间件在路由之后运行- 包装在
Router外的中间件(使用Layer::layer)在路由之前运行
错误处理
Axum 的错误处理基于 IntoResponse trait,可将自定义错误类型转换为 HTTP 响应。
错误处理策略:
- Result 类型:
async fn handler() -> Result<Json<Data>, StatusCode> {
// Returns 200 OK or error status code
}
- 自定义错误类型:
enum AppError {
Database(sqlx::Error),
NotFound,
Unauthorized,
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
// Convert to HTTP response
}
}
- HandleErrorLayer:
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 集成
Axum 基于 Tower 构建,可实现强大的中间件组合和服务抽象。
Tower 核心概念:
- Service Trait - 异步请求处理
- Layer Trait - 中间件工厂模式
- ServiceBuilder - 便捷的中间件组合
- Timeout - 请求超时处理
- RateLimit - 请求速率限制
- LoadShed - 背压管理
- Buffer - 请求缓冲
路由
基本路由
路由将 HTTP 方法和路径映射到处理程序:
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));
路径参数
从路径中提取动态片段:
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)
}
通配符路由
捕获剩余的路径片段:
// 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"
重要: 嵌套路由器会剥离匹配的前缀,而通配符路由会保留完整的 URI。
嵌套路由器
使用路由器嵌套将路由组织到各个模块中:
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 中剥离,再传递给嵌套路由器
- 嵌套路由器中的处理程序只能看到相对于嵌套点的路径
- 如果子路由器未定义回退处理程序,则继承父级的回退处理程序
- 可在嵌套之前或之后应用中间件
回退处理程序
处理未匹配的路由:
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);
回退继承:
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
方法路由
在同一路由上处理多种 HTTP 方法:
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)
);
深入了解提取器
Path 提取器
提取带类型的路径参数:
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));
Query 提取器
提取查询字符串参数:
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
Json 提取器
解析 JSON 请求体:
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 错误处理:
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(),
))
}
}
}
State 提取器
访问共享的应用状态:
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 提取器
访问请求扩展(适用于中间件):
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)
}
Form 提取器
解析表单编码的请求体:
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 提取器
访问请求标头:
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)
}
自定义提取器
通过实现 FromRequest 或 FromRequestParts 创建自定义提取器:
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)
}
中间件
应用中间件
应用中间件的三种方式:
- 路由器级别 - 影响所有路由
- 路由级别 - 影响指定路由
- 方法级别 - 影响路由上的指定方法
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))
);
中间件执行顺序
使用连续的 .layer() 调用时(从下到上):
let app = Router::new()
.route("/", get(handler))
.layer(layer_three) // Executes third
.layer(layer_two) // Executes second
.layer(layer_one); // Executes first
使用 ServiceBuilder 时(从上到下):
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
);
常见 Tower 中间件
TraceLayer - HTTP 请求跟踪:
use tower_http::trace::TraceLayer;
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());
CompressionLayer - 响应压缩:
use tower_http::compression::CompressionLayer;
let app = Router::new()
.route("/", get(handler))
.layer(CompressionLayer::new());
CorsLayer - CORS 处理:
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 - 请求超时:
use tower::timeout::TimeoutLayer;
use std::time::Duration;
let app = Router::new()
.route("/", get(handler))
.layer(TimeoutLayer::new(Duration::from_secs(30)));
HandleErrorLayer
将中间件错误转换为 HTTP 响应:
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)))
);
使用 from_fn 自定义中间件
使用异步函数创建自定义中间件:
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));
将数据从中间件传递给处理器:
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));
自定义 Tower 中间件
实现 Tower 的 Service 和 Layer 特征,实现全面控制:
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);
可访问状态的中间件
创建可访问应用状态的中间件:
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);
路由前的中间件
在路由前应用中间件(例如,用于重写 URI):
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?;
状态管理
基本状态
使用可克隆类型的简单状态:
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);
使用 Arc 的共享状态
使用 Arc 共享不易克隆类型的所有权:
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())
}
多种状态类型
为不同的路由器部分使用不同的状态类型:
#[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));
函数中的泛型状态
返回具有泛型状态的路由器,以提高灵活性:
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 { /* ... */ });
状态转换
串联具有不同状态要求的路由器:
#[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(),
});
错误处理
使用 Result 进行基本错误处理
在处理器中使用 Result 处理错误:
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),
}
}
自定义错误类型
为自定义错误实现 IntoResponse:
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)
}
提取器拒绝处理
处理提取器拒绝,以提供更清晰的错误消息:
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))
}
带错误处理的自定义提取器
创建具有自定义拒绝类型的提取器:
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)
}
中间件错误处理
处理可失败中间件产生的错误:
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)))
);
可失败服务
路由到可能失败的服务:
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),
);
构建响应
JSON 响应
以类型安全的方式返回 JSON:
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 响应
提供 HTML 内容:
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))
}
状态码响应
返回不同的状态码:
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 { /* ... */ }))
}
自定义标头
为响应添加自定义标头:
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())
}
完全控制响应
构建完整响应:
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()
}
流式响应
向客户端流式传输数据:
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)
}
文件下载
提供可供下载的文件:
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())
}
生产环境模式
数据库集成
集成数据库连接池:
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();
}
配置管理
使用环境变量和配置:
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();
}
结构化日志记录
实现全面的日志记录:
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))
}
}
}
优雅关闭
实现优雅关闭处理:
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();
}
健康检查
实现健康检查端点:
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);
速率限制
实现速率限制:
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
));
请求验证
使用自定义提取器验证请求:
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 { /* ... */ }))
}
测试
处理器单元测试
独立测试处理器:
#[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);
}
}
集成测试
测试完整应用:
#[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": "[email protected]"
});
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);
}
}
最佳实践
处理器组织
- 保持处理器精简 - 将业务逻辑移至服务层
- 使用提取器 - 让类型系统处理提取
- 返回 Result 类型 - 使用自定义错误类型
- 为处理器添加监测 - 添加 tracing 以便观测
状态管理
- 对开销较大的类型使用 Arc - 数据库连接池、缓存
- 保持状态精简 - 仅包含真正需要共享的内容
- 实现 Clone - State 提取器的必需条件
- 避免修改 - 必要时使用内部可变性(RwLock、Mutex)
错误处理
- 创建自定义错误类型 - 实现 IntoResponse
- 提供上下文 - 包含有用的错误消息
- 恰当地记录错误 - 使用 tracing
- 返回适当的状态码 - 符合 HTTP 语义
中间件
- 使用 ServiceBuilder - 排序方式更直观
- 在合适的层级应用 - 路由器、路由或方法
- 处理错误 - 对可失败中间件使用 HandleErrorLayer
- 让中间件职责明确 - 单一职责
性能
- 使用连接池 - 用于数据库和外部服务
- 启用压缩 - 使用 CompressionLayer 压缩响应
- 实现缓存 - 减少冗余操作
- 使用背压 - 使用 LoadShedLayer、RateLimitLayer
- 优化序列化 - 使用高效的 JSON 库
安全
- 验证输入 - 使用带验证的自定义提取器
- 实现身份验证 - 使用中间件进行身份验证
- 使用 HTTPS - 在生产环境中使用
- 设置安全标头 - CORS、CSP 等
- 限制速率 - 防止滥用
常见模式
仓储模式
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))
}
服务层模式
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>,
}
版本化 API
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+