用 Rust 构建 API 系列教程:第二部分
2021-09-13 19:12:00 Author: mp.weixin.qq.com(查看原文) 阅读量:75 收藏

今天继续使用 Rust 构建 API,第二部分。

这部分我将解释如何将 API 连接到 MongoDB。我将创建两个端点,一个用于在数据库中添加数据,另一个用于从数据库中检索数据。

我将把所有代码放在同一个文件 (src/main.rs) 中。当然,在实际情况中,你应该将代码拆分为多个文件/目录,并将控制器和服务之间的逻辑分离。

为了能够跟着本文实际动手,你需要安装 Rust 和 MongoDB,同时启动 MongoDB。如果你有使用 Docker,以下方式可能是最简单的:

docker run -d -p 27017:27017 --name mongo mongo

搭建环境

我们需要创建一个 .env 文件,安全地存储 MongoDB 连接字符串。如果你使用 Git,请确保添加 .env.gitignore 中。永远不要把 .env 文件提交了。

打开本系列第 1 部分的项目并创建 .env 文件,并添加如下内容:

MONGODB_URI=mongodb://localhost:27017

根据运行 MongoDB 的方式,确保将连接字符串替换为正确的连接字符串。

最好的做法是有一个 .env.example 文件(这个文件应该被提交到代码仓库) ,这样当某人第一次拉取项目时,他们就有一个关于如何设置 .env 文件的模板。这也是目前的最佳实践!

创建一个 .env.example 文件,添加如下内容:

MONGODB_URI= ** MongoDB URI (e.g. mongodb://localhost:27017)

添加新的依赖项

我们需要添加 2 个新的依赖项:dotenvmongodb

dotenv 是一个 crate,允许我们使用 .env 文件,它将文件中的变量添加到环境变量中。

mongodb 是官方的 MongoDB Rust 驱动程序。它允许我们与数据库交互。

更新 Cargo.toml 文件:

[dependencies]
tide = "0.16"
async-std = { version = "1", features = ["attributes"] }
serde = { version = "1", features = ["derive"] }
dotenv = "0.15"
mongodb = { version = "1", features = ["async-std-runtime"], default-features = false }

因为我们使用了 async-std 运行时,所以我们需要禁用 mongodb crate 的缺省特性。因为默认情况下它使用 tokio。然后我们需要添加 async-std-runtime 特性来使用 async-std 运行时。

开始编码

我们需要对代码进行一些修改:

use async_std::stream::StreamExt;
use dotenv::dotenv;
use mongodb::bson::doc;
use serde::{Deserialize, Serialize};
use std::env;
use tide::{Body, Request, Response, StatusCode};

这些都是我们将使用的所有模块项。

还记得上次我们设置了一个空的 Tide State 吗?这次我们将使用 State将数据库连接传递给控制器。我们需要像这样更新 State结构

#[derive(Clone)]
struct State {
  db: mongodb::Database,
}

现在我们需要更新 main 函数。必须创建数据库连接,并在创建 Tide 应用程序之前添加它的状态。

下面是更新后的 main 函数:

#[async_std::main]
async fn main() -> tide::Result<()> {
  // Use the dotenv crate to read the .env file and add the environment variables
  dotenv().ok();

  // Create the MongoDB client options with the connection string from the environment variables
  let mongodb_client_options =
    mongodb::options::ClientOptions::parse(&env::var("MONGODB_URI").unwrap()).await?;

  // Instantiate the MongoDB client
  let mongodb_client = mongodb::Client::with_options(mongodb_client_options)?;

  // Get a handle to the "rust-api-example" database
  let db = mongodb_client.database("rust-api-example");

  // Create the Tide state with the database connection
  let state = State { db };

  let mut app = tide::with_state(state);

  app.at("/hello").get(hello);

  app.listen("127.0.0.1:8080").await?;

  return Ok(());
}

我在代码中添加了注释,以帮助你理解发生了什么。

首先调用了 dotenv().ok() 方法从 .env 文件读取并添加到环境变量。然后创建了一个到数据库的连接。最后,我把这个连接传递给了 Tide State,这样我们的控制器就可以访问它了。

现在我们将创建两个控制器,一个用于在数据库中创建文档,另一个用于检索这些文档。

下面是第一个控制器的代码:

#[derive(Debug, Serialize, Deserialize)]
// The request's body structure
pub struct Item {
  pub name: String,
  pub price: f32,
}

async fn post_item(mut req: Request<State>) -> tide::Result {
  // Read the request's body and transform it into a struct
  let item = req.body_json::<Item>().await?;

  // Recover the database connection handle from the Tide state
  let db = &req.state().db;

  // Get a handle to the "items" collection
  let items_collection = db.collection_with_type::<Item>("items");

  // Insert a new Item in the "items" collection using values
  // from the request's body
  items_collection
    .insert_one(
      Item {
        name: item.name,
        price: item.price,
      },
      None,
    )
    .await?;

  // Return 200 if everything went fine
  return Ok(Response::new(StatusCode::Ok));
}

首先,我定义了一个 Item 结构体,它表示请求的主体。主体应该是一个 JSON 对象,其属性 name 的类型为 string,属性 price 的类型为 number

在函数内部,我首先尝试读取请求体。我没有做任何验证。实际项目中,不要像我一样,应该总是在使用请求体之前验证它。

我从 Tide State 恢复数据库连接。然后我得到一个 items 集合的句柄。

最后,我尝试使用请求体的值在集合中插入一个新文档(Mongo 的 Document)。

如果一切顺利的话,会返回 HTTP 状态码 200 的响应。

现在,让我们创建第二个控制器来从 items 集合中检索文档,下面是代码:

async fn get_items(req: Request<State>) -> tide::Result<tide::Body> {
  // Recover the database connection handle from the Tide state
  let db = &req.state().db;

  // Get a handle to the "items" collection
  let items_collection = db.collection_with_type::<Item>("items");

  // Find all the documents from the "items" collection
  let mut cursor = items_collection.find(NoneNone).await?;

  // Create a new empty Vector of Item
  let mut data = Vec::<Item>::new();

  // Loop through the results of the find query
  while let Some(result) = cursor.next().await {
    // If the result is ok, add the Item in the Vector
    if let Ok(item) = result {
      data.push(item);
    }
  }

  // Send the response with the list of items
  return Body::from_json(&data);
}

在函数内部,我从 Tide State 检索到数据库连接的句柄,然后获得 items 集合的句柄。

我使用 find 函数检索 items 集合中的所有文档。然后我创建一个新的空的 Vector Item。

我循环 find 查询结果,并在 Vector 中插入每个 item

最后,我将响应与主体中的 items 列表一起发送。

控制器已经准备好了,但是我们仍然需要将它们添加到 main 函数中。在 main 函数的 hello 路由之后添加以下内容:

app.at("/items").get(get_items);
app.at("/items").post(post_item);

好了,让我们测试一下,用下面的命令启动服务器:

cargo run

你现在应该能够发送一个 POST 请求给 /items (http://localhost:8080/items)。下面是 Body 的一个示例(确保将 Content-Type 头设置为 application/json)。

{
  "name""coffee",
  "price"2.5
}

你应该得到一个状态码为 200 的空响应。

然后尝试检索你刚刚创建的文档,通过发送一个 GET 请求给 /items  ( http://localhost:8080/items ) 获得。

你应该接收到一个数组,其中一个条目就是我们刚刚创建的项。

[
  {
    "name""coffee",
    "price"2.5
  }
]

结尾

这就是该系列的第二部分,希望它对你有所帮助。


往期推荐

我是 polarisxu,北大硕士毕业,曾在 360 等知名互联网公司工作,10多年技术研发与架构经验!2012 年接触 Go 语言并创建了 Go 语言中文网!著有《Go语言编程之旅》、开源图书《Go语言标准库》等。

坚持输出技术(包括 Go、Rust 等技术)、职场心得和创业感悟!欢迎关注「polarisxu」一起成长!也欢迎加我微信好友交流:gopherstudio


文章来源: http://mp.weixin.qq.com/s?__biz=MzAxNzY0NDE3NA==&mid=2247488122&idx=2&sn=d79170e445117228e96d173b538de82d&chksm=9be33d9bac94b48d9445f4a4831b18a7336650796f839c1735e8312ef9039a5d96df1ff5fec2#rd
如有侵权请联系:admin#unsafe.sh