集成 PostgreSQL 构建 Rust Web 服务器

科技   2024-09-14 23:59   广东  

本文将带领你踏上构建一个 Rust 驱动的 Web 服务器之旅,并通过集成 PostgreSQL 数据库来赋予它强大的数据管理能力。我们将利用 warp 框架来构建 Web 服务器,并使用 sqlx 库与 PostgreSQL 进行交互。

数据库准备:PostgreSQL 的安装与配置

首先,我们需要安装并配置 PostgreSQL 数据库。如果你还没有安装 PostgreSQL,可以参考以下步骤:

  1. 下载并安装 PostgreSQL: 访问 Postgres.app[1] 网站,下载并安装适用于你操作系统的 PostgreSQL。
  2. 创建数据库: 打开终端,运行以下命令登录到 PostgreSQL:
psql postgres
然后,创建一个名为 `rustedtasks` 的数据库:
CREATE DATABASE rustedtasks;
  1. 创建任务表: 切换到新创建的数据库:
psql rustedtasks
然后,创建名为 `tasks` 的表,用于存储任务信息:
CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,  -- 自动递增的整数
    title TEXT NOT NULL,
    description TEXT NOT NULL,
    completed BOOLEAN NOT NULL DEFAULT FALSE
);
运行 `\dt` 命令可以查看当前数据库中的所有表。

Rust 代码:构建 Web 服务器和数据交互

现在,让我们开始编写 Rust 代码,构建 Web 服务器并实现与数据库的交互。

1.  定义依赖项

在你的 Cargo.toml 文件中添加以下依赖项:

[dependencies]
warp = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }
sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-native-tls"] }

2.  导入必要模块

在你的代码中,导入以下模块:

use serde::{Serialize, Deserialize};
use warp::{Filter, http::{Method, StatusCode}, reply::{json, Json}, Rejection};
use sqlx::{Pool, Postgres, Row};

3.  定义数据结构

定义 Task 结构体,表示一个任务,以及 CreateTask 结构体,用于接收创建任务的请求数据:

#[derive(Deserialize, Serialize, Debug)]
struct Task {
    id: i32,
    title: String,
    description: String,
    completed: bool,
}

#[derive(Deserialize, Serialize, Debug)]
struct CreateTask {
    title: String,
    description: String,
}

4.  定义数据库连接池

创建一个类型别名 DbPool 来简化代码,并定义一个 connect_db 函数来建立与 PostgreSQL 数据库的连接:

type DbPool = Pool<Postgres>;

async fn connect_db() -> Result<DbPool, sqlx::Error> {
    let pool = Pool::<Postgres>::connect("postgres://127.0.0.1/rustedtasks").await?;
    Ok(pool)
}

5.  定义获取所有任务的处理函数

定义一个名为 get_all_tasks 的异步函数,用于获取数据库中所有任务的信息:

async fn get_all_tasks(pool: DbPool) -> Result<Json, Rejection> {
    let rows = sqlx::query("SELECT id, title, description, completed FROM tasks")
        .fetch_all(&pool)
        .await
        .map_err(|_| warp::reject())?;

    let tasks: Vec<Task> = rows.into_iter().map(|row| {
        Task {
            id: row.get("id"),
            title: row.get("title"),
            description: row.get("description"),
            completed: row.get("completed"),
        }
    }).collect();

    Ok(json(&tasks))
}

6.  定义创建任务的处理函数

定义一个名为 create_task 的异步函数,用于接收创建任务的请求数据并将其存储到数据库中:

async fn create_task(task: CreateTask, pool: DbPool) -> Result<StatusCode, Rejection> {
    sqlx::query("INSERT INTO tasks (title, description, completed) VALUES ($1, $2, $3)")
        .bind(&task.title)
        .bind(&task.description)
        .bind(false)
        .execute(&pool)
        .await
        .map_err(|_| warp::reject())?;

    Ok(StatusCode::CREATED)
}

7.  设置并运行服务器

最后,定义 main 函数,设置并运行 Web 服务器:

#[tokio::main]
async fn main() {
    let db_pool = connect_db().await.expect("Failed to connect to the database");
    let pool_filter = warp::any().map(move || db_pool.clone());

    let get_all_tasks_route = warp::path("tasks")
        .and(warp::get())
        .and(pool_filter.clone())
        .and_then(get_all_tasks);

    let add_task_route = warp::path("add")
        .and(warp::post())
        .and(warp::body::json())
        .and(pool_filter.clone())
        .and_then(create_task);

    let cors = warp::cors()
        .allow_any_origin()
        .allow_header("content-type")
        .allow_methods(&[Method::GET, Method::POST]);

    let routes = get_all_tasks_route
        .or(add_task_route)
        .with(cors);

    warp::serve(routes).run(([127001], 3030)).await;
}

完整的代码示例

use serde::{Serialize, Deserialize};
use warp::{Filter, http::{Method, StatusCode}, reply::{json, Json}, Rejection};
use sqlx::{Pool, Postgres, Row};

#[derive(Deserialize, Serialize, Debug)]
struct Task {
    id: i32,
    title: String,
    description: String,
    completed: bool,
}

#[derive(Deserialize, Serialize, Debug)]
struct CreateTask {
    title: String,
    description: String,
}

type DbPool = Pool<Postgres>;

async fn connect_db() -> Result<DbPool, sqlx::Error> {
    let pool = Pool::<Postgres>::connect("postgres://127.0.0.1/rustedtasks").await?;
    Ok(pool)
}

async fn get_all_tasks(pool: DbPool) -> Result<Json, Rejection> {
    let rows = sqlx::query("SELECT id, title, description, completed FROM tasks")
        .fetch_all(&pool)
        .await
        .map_err(|_| warp::reject())?;

    let tasks: Vec<Task> = rows.into_iter().map(|row| {
        Task {
            id: row.get("id"),
            title: row.get("title"),
            description: row.get("description"),
            completed: row.get("completed"),
        }
    }).collect();

    Ok(json(&tasks))
}

async fn create_task(task: CreateTask, pool: DbPool) -> Result<StatusCode, Rejection> {
    sqlx::query("INSERT INTO tasks (title, description, completed) VALUES ($1, $2, $3)")
        .bind(&task.title)
        .bind(&task.description)
        .bind(false)
        .execute(&pool)
        .await
        .map_err(|_| warp::reject())?;

    Ok(StatusCode::CREATED)
}

#[tokio::main]
async fn main() {
    let db_pool = connect_db().await.expect("Failed to connect to the database");
    let pool_filter = warp::any().map(move || db_pool.clone());

    let get_all_tasks_route = warp::path("tasks")
        .and(warp::get())
        .and(pool_filter.clone())
        .and_then(get_all_tasks);

    let add_task_route = warp::path("add")
        .and(warp::post())
        .and(warp::body::json())
        .and(pool_filter.clone())
        .and_then(create_task);

    let cors = warp::cors()
        .allow_any_origin()
        .allow_header("content-type")
        .allow_methods(&[Method::GET, Method::POST]);

    let routes = get_all_tasks_route
        .or(add_task_route)
        .with(cors);

    warp::serve(routes).run(([127001], 3030)).await;
}

测试

  1. 添加任务: 运行以下命令,使用 curl 向服务器发送 POST 请求,创建一个新任务:
curl -X POST http://localhost:3030/add \
    -H "Content-Type: application/json" \
    -d '{
        "title": "测试任务",
        "description": "这是一个测试任务"
    }'

  1. 获取所有任务: 运行以下命令,使用 curl 向服务器发送 GET 请求,获取所有任务的信息:
curl -X GET http://localhost:3030/tasks

总结

本文演示了如何使用 Rust 构建一个 Web 服务器,并通过集成 PostgreSQL 数据库来实现强大的数据存储和管理功能。warpsqlx 库的结合为我们提供了构建高效、可靠的 Web 应用程序的强大工具。

希望本文能帮助你更好地理解 Rust 在 Web 开发中的应用,并激发你探索更多可能性。

参考资料
[1]

Postgres.app: https://postgresapp.com/

文章精选

Tailspin:用 Rust 打造的炫彩日志查看器

Rust: 重塑系统编程的安全壁垒

Youki:用Rust编写的容器运行时,性能超越runc

使用C2Rust将C代码迁移到Rust

Rust语言中如何优雅地应对错误


Rust编程笔记
与你一起在Rust的世界里探索、学习、成长!
 最新文章