15.5 配置管理、错误处理与中间件

一个生产级 Web 服务不仅需要正确实现业务逻辑,还需具备良好的配置灵活性统一的错误处理机制可扩展的中间件支持。本节将围绕这三个方面,完善我们在前几节搭建的 API 项目,使其更健壮、可观测且易于运维。

配置管理

应用配置应通过环境变量或配置文件注入,避免硬编码。我们使用 config crate 结合 serde 实现分层配置加载(默认值 → 环境变量覆盖)。

首先定义配置结构体(src/config.rs):

use serde::Deserialize;

#[derive(Deserialize, Debug)]
pub struct AppConfig {
    #[serde(default = "default_server_port")]
    pub server_port: u16,
    #[serde(default)]
    pub database_url: String,
}

fn default_server_port() -> u16 {
    3000
}

impl AppConfig {
    pub fn from_env() -> Result<Self, config::ConfigError> {
        let mut cfg = config::Config::new();
        cfg.merge(config::Environment::new())?;
        cfg.try_into()
    }
}

main.rs 中加载:

let config = AppConfig::from_env().expect("Failed to load config");
let listener = TcpListener::bind(("0.0.0.0", config.server_port)).await?;

现在可通过 DATABASE_URL=postgres://... SERVER_PORT=8080 cargo run 动态配置。

统一错误处理

Rust 的 Result 类型天然支持错误传递,但 Web 框架需要将内部错误转换为 HTTP 响应。我们定义统一的 AppError 类型,并实现 IntoResponse(Axum)或 ResponseError(Actix-web)。

src/error.rs 中:

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};
use serde_json::json;
use sqlx::Error as SqlxError;

#[derive(Debug)]
pub enum AppError {
    Sqlx(SqlxError),
    NotFound,
    ValidationError(String),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::Sqlx(e) => {
                tracing::error!(error = %e, "Database error");
                (StatusCode::INTERNAL_SERVER_ERROR, "Database error")
            }
            AppError::NotFound => (StatusCode::NOT_FOUND, "Resource not found"),
            AppError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg.as_str()),
        };

        (status, Json(json!({ "error": message }))).into_response()
    }
}

// 为常见错误类型实现 From,简化 ? 操作符使用
impl From<SqlxError> for AppError {
    fn from(err: SqlxError) -> Self {
        match &err {
            SqlxError::RowNotFound => AppError::NotFound,
            _ => AppError::Sqlx(err),
        }
    }
}

这样,在 handler 中只需返回 Result<..., AppError>,任何 sqlx::Error 都会自动转为合适的 HTTP 响应。

中间件扩展

中间件用于横切关注点,如请求日志、CORS、认证等。Axum 基于 tower,可直接使用其生态中间件。

自定义请求日志中间件

虽然 tower-http 提供了 TraceLayer,但我们可以实现更定制化的日志:

use std::task::{Context, Poll};
use axum::{
    body::Body,
    http::{Request, Response},
    middleware::Next,
};
use futures::FutureExt;

pub async fn log_requests<B>(
    req: Request<B>,
    next: Next<B>,
) -> Response {
    let start = std::time::Instant::now();
    let method = req.method().clone();
    let uri = req.uri().clone();

    let response = next.run(req).await;

    let duration = start.elapsed();
    tracing::info!(
        method = %method,
        uri = %uri,
        status = response.status().as_u16(),
        duration_ms = duration.as_millis(),
        "request completed"
    );

    response
}

注册中间件:

use axum::middleware;

let app = Router::new()
    .route("/tasks", post(create_task))
    .layer(middleware::from_fn(log_requests));

启用 CORS

对于前端调用,通常需启用 CORS:

[dependencies]
tower-http = { version = "0.5", features = ["cors"] }
use tower_http::cors::CorsLayer;

let cors = CorsLayer::new()
    .allow_origin(tower_http::cors::Any)
    .allow_methods([axum::http::Method::GET, axum::http::Method::POST]);

let app = Router::new()
    .route(...)
    .layer(cors)
    .layer(middleware::from_fn(log_requests));

小结

通过引入配置管理,应用变得可部署于不同环境;统一错误处理提升了 API 的一致性和可调试性;中间件机制则让横切逻辑模块化、可复用。这三者共同构成了生产就绪服务的基础骨架。至此,我们已构建出一个结构清晰、可观测、可配置且具备完整 CRUD 能力的 Rust Web API 服务。后续可根据需求进一步集成认证、限流、健康检查等功能,但核心工程实践已在本章体现。

#Rust 入门教程 分享于 5 天前

内容由 AI 创作和分享,仅供参考