从零到一:构建你的第一个 Rust 异步 Web 服务

4 阅读1分钟

在当今高并发的网络环境下,异步编程已成为构建高性能服务的标配。Rust 语言凭借其出色的性能表现和内存安全性,结合强大的异步生态,正在成为构建下一代 Web 服务的理想选择。本文将带你从零开始,使用 Rust 的异步栈构建一个完整的 Web API 服务。

为什么选择 Rust 异步?

在深入代码之前,我们先理解 Rust 异步编程的核心优势:

  1. 零成本抽象:Rust 的异步/await 语法在编译时展开,运行时几乎没有额外开销
  2. 无畏并发:借用检查器保证异步代码的内存安全,避免数据竞争
  3. 高性能:基于事件驱动的执行器,单机可轻松处理数十万并发连接
  4. 现代生态:Tokio 运行时提供了完整的异步 I/O 支持

项目搭建与环境准备

首先确保你已安装 Rust 工具链(1.75+ 版本):

# 创建新项目
cargo new async-web-service
cd async-web-service

# 添加依赖
cargo add tokio --features full
cargo add axum
cargo add serde --features derive
cargo add serde_json
cargo add tracing
cargo add tracing-subscriber

编辑 Cargo.toml 文件,确保配置正确:

[package]
name = "async-web-service"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7"
tokio = { version = "1.37", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

核心架构设计

我们的 Web 服务将采用分层架构:

┌─────────────────┐
│   HTTP 层       │  ← Axum 路由和处理程序
├─────────────────┤
│  业务逻辑层     │  ← 异步业务处理
├─────────────────┤
│  数据访问层     │  ← 数据库/外部API调用
└─────────────────┘

实现步骤

1. 基础服务器搭建

创建 src/main.rs 文件:

use axum::{
    routing::{get, post},
    Router,
    Json,
    extract::State,
};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::net::TcpListener;
use tracing_subscriber;

// 应用状态结构
#[derive(Clone)]
struct AppState {
    app_name: String,
    version: String,
}

// API 响应结构
#[derive(Serialize)]
struct ApiResponse<T> {
    success: bool,
    data: Option<T>,
    message: String,
}

impl<T> ApiResponse<T> {
    fn success(data: T) -> Self {
        Self {
            success: true,
            data: Some(data),
            message: "Success".to_string(),
        }
    }
    
    fn error(message: &str) -> Self {
        Self {
            success: false,
            data: None,
            message: message.to_string(),
        }
    }
}

// 健康检查端点
async fn health_check() -> Json<ApiResponse<String>> {
    Json(ApiResponse::success("Service is healthy".to_string()))
}

// 用户信息结构
#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: u64,
    username: String,
    email: String,
}

// 用户创建请求
#[derive(Debug, Deserialize)]
struct CreateUserRequest {
    username: String,
    email: String,
}

// 创建用户端点
async fn create_user(
    State(state): State<Arc<AppState>>,
    Json(payload): Json<CreateUserRequest>,
) -> Json<ApiResponse<User>> {
    tracing::info!(
        "Creating user in app: {}, version: {}",
        state.app_name,
        state.version
    );
    
    // 模拟异步操作(实际中可能是数据库插入)
    let user = User {
        id: 1,
        username: payload.username,
        email: payload.email,
    };
    
    Json(ApiResponse::success(user))
}

// 获取用户列表
async fn get_users() -> Json<ApiResponse<Vec<User>>> {
    // 模拟异步数据获取
    let users = vec![
        User {
            id: 1,
            username: "alice".to_string(),
            email: "alice@example.com".to_string(),
        },
        User {
            id: 2,
            username: "bob".to_string(),
            email: "bob@example.com".to_string(),
        },
    ];
    
    Json(ApiResponse::success(users))
}

#[tokio::main]
async fn main() {
    // 初始化日志
    tracing_subscriber::fmt()
        .with_env_filter("info")
        .init();
    
    // 创建应用状态
    let state = Arc::new(AppState {
        app_name: "Async Web Service".to_string(),
        version: "1.0.0".to_string(),
    });
    
    // 构建路由
    let app = Router::new()
        .route("/health", get(health_check))
        .route("/users", get(get_users))
        .route("/users", post(create_user))
        .with_state(state);
    
    // 启动服务器
    let listener = TcpListener::bind("127.0.0.1:3000")
        .await
        .expect("Failed to bind port");
    
    tracing::info!("Server listening on http://{}", listener.local_addr().unwrap());
    
    axum::serve(listener, app)
        .await
        .expect("Server failed");
}

2. 添加中间件和错误处理

创建 src/middleware.rs

use axum::{
    http::{Request, StatusCode},
    middleware::Next,
    response::Response,
    Json,
};
use std::time::Instant;
use tracing::info;

// 日志中间件
pub async fn logging_middleware<B>(
    req: Request<B>,
    next: Next<B>,
) -> Result<Response, (StatusCode, String)> {
    let start = Instant::now();
    let method = req.method().clone();
    let uri = req.uri().clone();
    
    let response = next.run(req).await;
    let duration = start.elapsed();
    
    info!(
        "{} {} - {}ms - Status: {}",
        method,
        uri,
        duration.as_millis(),
        response.status()
    );
    
    Ok(response)
}

// 认证中间件
pub async fn auth_middleware<B>(
    req: Request<B>,
    next: Next<B>,
) -> Result<Response, (StatusCode, Json<serde_json::Value>)> {
    // 检查认证头
    let auth_header = req.headers()
        .get("Authorization")
        .and_then(|header| header.to_str().ok());
    
    match auth_header {
        Some(token) if token.starts_with("Bearer ") => {
            // 验证 token(这里简化处理)
            if token.len() > 7 {
                Ok(next.run(req).await)
            } else {
                let error_response = serde_json::json!({
                    "error": "Invalid token",
                    "code": "AUTH_ERROR"
                });
                Err((StatusCode::UNAUTHORIZED, Json(error_response)))
            }
        }
        _ => {
            let error_response = serde_json::json!({
                "error": "Missing authorization header",
                "code": "AUTH_REQUIRED"
            });
            Err((StatusCode::UNAUTHORIZED, Json(error_response)))
        }
    }
}

3. 实现异步数据库操作

创建 src/database.rs

use sqlx::{postgres::PgPoolOptions, Pool, Postgres};
use tracing::error;
use std::time::Duration;

pub struct Database {
    pool: Pool<Postgres>,
}

impl Database {
    pub async fn new(database_url: &str) -> Result<Self, sqlx::Error> {
        let pool = PgPoolOptions::new()
            .max_connections(10)
            .acquire_timeout(Duration::from_secs(3))
            .connect(database_url)
            .await?;
        
        // 运行迁移(实际项目中应该使用专门的迁移工具)
        Self::run_migrations(&pool).await?;
        
        Ok(Self { pool })
    }
    
    async fn run_migrations(pool: &Pool<Postgres>) -> Result<(), sqlx::Error> {
        sqlx::query(
            r#"
            CREATE TABLE IF NOT EXISTS users (
                id BIGSERIAL PRIMARY KEY,
                username VARCHAR(50) UNIQUE NOT NULL,
                email VARCHAR(100) UNIQUE NOT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
            "#
        )
        .execute(pool)
        .await?;
        
        Ok(())