Overview
在本教程中,您可以学习;了解如何使用 Rocket Web框架创建Rust Web应用程序。Rust驱动程序允许您利用内存管理、生命周期和数据库连接监控等功能来提高应用程序的性能。
完成本教程后,您将拥有一个 Web应用程序,其中包含用于执行增删改查操作的路由。
先决条件
确保您的开发环境中安装了Rust 1.74 或更高版本,以及Rust包管理器 Cargo。
有关如何安装Rust和 Cargo 的信息,请参阅有关下载和安装Rust的Rust官方指南。
您还必须设立一个MongoDB Atlas 群集。要学习;了解如何创建集群,请参阅快速入门指南的创建MongoDB部署步骤。将连接字符串保存在安全位置,以便稍后在本教程中使用。
步骤
选择异步运行时。
使用Rust驱动程序时,必须选择同步或异步运行时。本教程使用异步运行时,更适合构建 API。
默认情况下,驱动程序使用异步 tokio 运行时运行。
要学习;了解有关可用运行时的更多信息,请参阅 异步和同步 API指南。
插入示例数据。
选择 INSERT DOCUMENT 按钮,然后将 bread_data.json 文件的内容粘贴到示例应用中。
插入数据后,您可以在 recipes集合中查看示例文档。
安装Rocket。
打开 IDE 并进入项目目录。从项目根目录运行以下命令以安装 Rocket Web框架:
cargo add -F json rocket
验证 Cargo.toml文件中的依赖项列表是否包含 rocket 的条目。
您还必须添加一个由 Rocket 开发的包,允许您使用包装器来管理MongoDB客户端建立的异步连接的集合池。此包允许您参数化MongoDB数据库和集合,并让每个应用函数接收自己的连接以供使用。
运行以下命令以添加 rocket_db_pools 包:
cargo add -F mongodb rocket_db_pools
验证 Cargo.toml文件中的依赖项列表是否包含 rocket_db_pools 的条目,而该条目包含 mongodb 的功能标志。
配置Rocket。
要将Rocket 配置为使用 bread数据库,请在项目根目录中创建一个名为 Rocket.toml 的文件。Rocket 会查找此文件以读取配置设置。您还可以将MongoDB连接字符串存储在此文件中。
将以下配置粘贴到 Rocket.toml文件中:
[default.databases.db] url = "<connection string>"
要学习;了解有关配置Rocket的更多信息,请参阅Rocket文档中的配置。
了解应用结构。
在开始编写API之前,学习;了解简单的 Rocket应用的结构,并在应用程序中创建相应的文件。
下图展示了您的Rocket应用必须具有的文件结构,并解释了每个文件的功能:
. ├── Cargo.lock # Dependency info ├── Cargo.toml # Project and dependency info ├── Rocket.toml # Rocket configuration └── src # Directory for all app code ├── db.rs # Establishes database connection ├── main.rs # Starts the web app ├── models.rs # Organizes data └── routes.rs # Stores API routes
根据上图,创建 src目录及其包含的文件。点,文件可以为空。
设置数据库连接。
将以下代码粘贴到db.rs文件:
use rocket_db_pools::{mongodb::Client, Database}; pub struct MainDatabase(Client);
您还必须将数据库结构附加到您的Rocket实例。将以下模板代码复制到您的 main.rs文件中:
mod db; mod models; mod routes; use rocket::{launch, routes}; use rocket_db_pools::Database; fn rocket() -> _ { rocket::build() .attach(db::MainDatabase::init()) // Paste mount() call below }
IDE 可能会引发函数体不完整的错误。您可以忽略此错误,因为您将在后续步骤中添加 mount() 调用。
创建数据模型。
定义一致且有用的结构体来表示数据对于维护类型安全和减少运行时错误非常重要。
在您的 models.rs文件中,定义一个 Recipe 结构体来表示烘焙面包的配方:
use mongodb::bson::oid::ObjectId; use rocket::serde::{Deserialize, Serialize}; pub struct Recipe { pub id: Option<ObjectId>, pub title: String, pub ingredients: Vec<String>, pub temperature: u32, pub bake_time: u32, }
设置API路由。
路由允许程序将请求定向到适当的端点以发送或接收数据。文件routes.rs 存储API中定义的所有路由。
将以下模板代码复制到您的 routes.rs文件中:
use crate::db::MainDatabase; use crate::models::Recipe; use mongodb::bson::doc; use mongodb::bson::oid::ObjectId; use rocket::{ delete, futures::TryStreamExt, get, http::Status, post, put, response::status, serde::json::Json, }; use rocket_db_pools::Connection; use serde_json::{json, Value}; // Paste index route below // Paste get_recipes route below // Paste get_recipe route below // Paste update_recipe route below // Paste delete_recipe route below // Paste create_recipe route below
将以下 index 路由粘贴到 // Paste index route below 注释下:
pub fn index() -> Json<Value> { Json(json!({"status": "It is time to make some bread!!!"})) }
在写入其余路由之前,请将路由添加到Rocket 的主启动函数中。
在 main.rs文件中,将以下代码粘贴到 // Paste mount() call below 注释下:
.mount( "/", routes![ routes::index, routes::get_recipes, routes::create_recipe, routes::get_recipe, routes::update_recipe, routes::delete_recipe, ], )
写入增删改查操作路由。
创建
当您尝试在MongoDB中创建数据时,有两种可能的结果:
文档已成功创建,因此您的应用会返回
HTTP 201。插入期间发生错误,因此您的应用会返回
HTTP 400。
将以下 create_recipe() 路由粘贴到 routes.rs文件中的 // Paste create_recipe route below 注释下:
pub async fn create_recipe( db: Connection<MainDatabase>, data: Json<Recipe>, ) -> status::Custom<Json<Value>> { if let Ok(res) = db .database("bread") .collection::<Recipe>("recipes") .insert_one(data.into_inner(), None) .await { if let Some(id) = res.inserted_id.as_object_id() { return status::Custom( Status::Created, Json( json!({"status": "success", "message": format!("Recipe ({}) created successfully", id.to_string())}), ), ); } } status::Custom( Status::BadRequest, Json(json!({"status": "error", "message":"Recipe could not be created"})), ) }
读取
当您尝试从MongoDB读取数据时,有两种可能的结果:
返回匹配文档的向量。
返回一个空向量,因为没有匹配的文档或发生错误。
由于这些预期结果,请将以下 get_recipes() 路由粘贴到 routes.rs文件中的 // Paste get_recipes route below 注释下:
pub async fn get_recipes(db: Connection<MainDatabase>) -> Json<Vec<Recipe>> { let recipes = db .database("bread") .collection("recipes") .find(None, None) .await; if let Ok(r) = recipes { if let Ok(collected) = r.try_collect::<Vec<Recipe>>().await { return Json(collected); } } return Json(vec![]); }
按ID检索
将以下 get_recipe() 路由粘贴到 // Paste get_recipe route below 注释下:
pub async fn get_recipe( db: Connection<MainDatabase>, id: &str, ) -> status::Custom<Json<Value>> { let b_id = ObjectId::parse_str(id); if b_id.is_err() { return status::Custom( Status::BadRequest, Json(json!({"status": "error", "message":"Recipe ID is invalid"})), ); } if let Ok(Some(recipe)) = db .database("bread") .collection::<Recipe>("recipes") .find_one(doc! {"_id": b_id.unwrap()}, None) .await { return status::Custom( Status::Ok, Json(json!({"status": "success", "data": recipe})), ); } return status::Custom( Status::NotFound, Json(json!({"status": "success", "message": "Recipe not found"})), ); }
此路由通过 _id 值检索单个文档。
Update
将以下 update_recipe() 路由粘贴到 // Paste update_recipe route below 注释下:
pub async fn update_recipe( db: Connection<MainDatabase>, data: Json<Recipe>, id: &str, ) -> status::Custom<Json<Value>> { let b_id = ObjectId::parse_str(id); if b_id.is_err() { return status::Custom( Status::BadRequest, Json(json!({"status": "error", "message":"Recipe ID is invalid"})), ); } if let Ok(_) = db .database("bread") .collection::<Recipe>("recipes") .update_one( doc! {"_id": b_id.as_ref().unwrap()}, doc! {"$set": mongodb::bson::to_document(&data.into_inner()).unwrap()}, None, ) .await { return status::Custom( Status::Created, Json( json!({"status": "success", "message": format!("Recipe ({}) updated successfully", b_id.unwrap())}), ), ); }; status::Custom( Status::BadRequest, Json( json!({"status": "success", "message": format!("Recipe ({}) could not be updated successfully", b_id.unwrap())}), ), ) }
该路由按 _id 值更新单个文档。
删除
将以下 delete_recipe() 路由粘贴到 // Paste delete_recipe route below 注释之后:
pub async fn delete_recipe( db: Connection<MainDatabase>, id: &str, ) -> status::Custom<Json<Value>> { let b_id = ObjectId::parse_str(id); if b_id.is_err() { return status::Custom( Status::BadRequest, Json(json!({"status": "error", "message":"Recipe ID is invalid"})), ); } if db .database("bread") .collection::<Recipe>("recipes") .delete_one(doc! {"_id": b_id.as_ref().unwrap()}, None) .await .is_err() { return status::Custom( Status::BadRequest, Json( json!({"status": "error", "message":format!("Recipe ({}) could not be deleted", b_id.unwrap())}), ), ); }; status::Custom( Status::Accepted, Json( json!({"status": "", "message": format!("Recipe ({}) successfully deleted", b_id.unwrap())}), ), ) }
此路由通过其 _id 值删除单个文档。
测试执行增删改查操作的路由。
通过在终端中运行以下命令来启动应用程序:
cargo run
在另一个终端窗口中,运行以下命令以测试 create_recipe() 路由:
curl -v --header "Content-Type: application/json" --request POST --data '{"title":"simple bread recipe","ingredients":["water, flour"], "temperature": 250, "bake_time": 120}' http://127.0.0.1:8000/recipes
{"status":"success","message":"Recipe (684c4245f5a3ca09efa92593) created successfully"}
运行以下命令以测试 get_recipes() 路由:
curl -v --header "Content-Type: application/json" --header "Accept: application/json" http://127.0.0.1:8000/recipes/
[{"_id":...,"title":"artisan","ingredients":["salt","flour","water","yeast"],"temperature":404,"bake_time":5}, {"_id":...,"title":"rye","ingredients":["salt"],"temperature":481,"bake_time":28},...]
运行以下命令以测试 delete_recipe() 路由。将 <id> 占位符替换为集合中已知的 _id 值,该值可能类似于 68484d020f561e78c03c7800:
curl -v --header "Content-Type: application/json" --header "Accept: application/json" --request DELETE http://127.0.0.1:8000/recipes/<id>
{"status":"","message":"Recipe (68484d020f561e78c03c7800) successfully deleted"}
结论
在本教程中,您学习了如何使用 Rocket构建简单的 Web应用程序来执行增删改查操作。
资源
要学习;了解有关增删改查操作的更多信息,请参阅以下指南: