手把手教你用 Rust 搭建 REST API
source link: https://www.infoq.cn/article/GITBBOtMoo0o3H2Q6rXT
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
我是 Asel,今天我将展示如何用 Rust 搭建一个简单的 REST API。
教程中使用的是 Rocket 框架 编写 API,借助 Diesel ORM 框架处理持久特征。这个框架覆盖了以下所有的点,让我们可以更容易地从最基础开始搭建:
- 启动网页服务器并打开一个端口。
- 监听端口上的请求。
- 如果有请求接入,查看 HTTP header 中的路径。
- 根据路径将请求路由到处理器(
handler
) - 提取请求中的信息
- 打包由用户生成的数据(
data
),并生成响应(response
) - 将响应(
response
)发回给发送者
安装 Nightly Rust
因为 Rocket 大量使用了 Rust 语法扩展及其他高级、不稳定的特性,所以我们必须要安装 nightly
版。
复制代码
rustup default nightly
如果只想将 nightly
安装到项目文件夹,那可以使用以下命令:
复制代码
rustup override set nightly
依赖
复制代码
[dependencies] rocket = "0.4.4" rocket_codegen = "0.4.4" diesel = { version = "1.4.0", features = ["postgres"] } dotenv = "0.9.0" r2d2-diesel = "1.0" r2d2 = "0.8" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" custom_derive ="0.1.7" [dependencies.rocket_contrib] version = "*" default-features = false features = ["json"]
在后面的应用部分,我会解释具体该怎么写。
安装 Diesel
下一步要做的就是安装 Diesel 。Diesel 有自己的 CLI(命令行界面),这是我们第一步要做的(假设您使用的是 PostgreSQL )。
复制代码
cargo install diesel_cli — no-default-features — features postgre
然后我们需要告诉 Diesel 该在哪里找到我们的数据库,以下命令将生成一个 .env
文件。
复制代码
echo DATABASE_URL=postgres://username:password@localhost:port/diesel_demo > .env
然后执行以下命令:
复制代码
diesel setup
这样可以搭建一个数据库(如果没有的话),并创建一个空的迁移目录,我们可以用该目录来管理我们的构架(更详细的会在后面讲到)。
运行代码的时候可能会出现以下错误信息:
复制代码
= note: LINK : fatal error LNK1181: cannot open input file ‘libpq.lib’
把 PG lib folder
路径添加到环境变量中就可以轻易解决。
复制代码
setx PQ_LIB_DIR “[path to pg lib folder]”
神奇的是 Diesel 文档 中竟然没有提及这种错误信息。
强烈建议在 CMD 或者 Powershell 中执行这些命令。如果你用的是 IDE 终端,那么你会看不到这个错误信息,最终把时间浪费在找错误上。
若要解决这个问题,可以把 PG 的 bin 文件路径添加到 Path 变量。
下面我们创建一个用户表并为此创建一个迁移:
复制代码
diesel migration generate users
执行完这个命令后,你会看到迁移文件夹中出现两个文件。
下一步是为迁移编写 SQL 命令:
up.sql
复制代码
CREATETABLEusers ( idSERIALPRIMARYKEY, usernameVARCHARNOTNULL, passwordVARCHARNOTNULL, first_nameVARCHARNOTNULL )
down.sql
复制代码
DROPTABLEusers
应用迁移的话可以用这个命令:
复制代码
diesel migration run
最好先回滚之后再重新迁移,以确保 down.sql
准确无误。
复制代码
diesel migration redo
你可以看到 DB.right 出现了用户表。
差点忘了提,在运行 Diesel 安装命令的时候会生成一个文件 schema.rs
。应该是这样的:
复制代码
table! { users (id) { id -> Int4, username -> Varchar, password -> Varchar, first_name -> Varchar, } }
下面是 Rust 部分
因为要使用 ORM,所以需要先将用户表映射到 Rust 中。Java 中用的是 Class 来映射表格,这种方式被称作 Beans 。Rust 中我们要用的是结构( struct
)。首先先创建一个结构。
复制代码
usediesel; usediesel::pg::PgConnection; usediesel::prelude::*; usesuper::schema::users; usesuper::schema::users::dsl::usersasall_users; // this is to get users from the database #[derive(Serialize, Queryable)] pubstructUser{ pubid:i32, pubusername:String, pubpassword:String, pubfirst_name:String, }
你大概会好奇结构定义中的这些标注都是什么。他们被称作导出(derives),也就是说,这些代码会导出序列化、可查询的 traits。 #[derive(Serialize)]
以及 #[derive(Deserialize)]
可以用来映射数据到响应和请求上。
下面再创建两个 struct,后面都会用到。
复制代码
// decode request data #[derive(Deserialize)] pubstructUserData{ pubusername:String, } // this is to insert users to database #[derive(Serialize, Deserialize, Insertable)] #[table_name ="users"] pubstructNewUser{ pubusername:String, pubpassword:String, pubfirst_name:String, }
下面要做的是应用 User
。这样就可以对数据库进行操作了。
这里可以看到,我们将连接传递到方法,返回用户向量(Vector of User)。我们获取了用户表中的所有行,然后将其映射到用户结构上。
出错可能在所难免,如果担心的话可以把错误信息打印出来。
复制代码
impl User { pub fn get_all_users(conn: &PgConnection) -> Vec<User> { all_users .order(users::id.desc()) .load::<User>(conn) .expect("error!") } pub fn insert_user(user: NewUser, conn: &PgConnection) -> bool { diesel::insert_into(users::table) .values(&user) .execute(conn) .is_ok() } pub fn get_user_by_username(user: UserData, conn: &PgConnection) -> Vec<User> { all_users .filter(users::username.eq(user.username)) .load::<User>(conn) .expect("error!") } }
现在有了表和映射到表的结构,接下来就需要创建使用它的方法。首先,我们要建一个 route
文件,通常称之为 handler 。
复制代码
usesuper::db::ConnasDbConn; userocket_contrib::json::Json; usesuper::models::{User, NewUser}; useserde_json::Value; usecrate::models::UserData; #[post("/users", format ="application/json")] pubfnget_all(conn: DbConn) -> Json<Value> { letusers = User::get_all_users(&conn); Json(json!({ "status":200, "result": users, })) } #[post("/newUser", format ="application/json", data ="<new_user>")] pubfnnew_user(conn: DbConn, new_user: Json<NewUser>) -> Json<Value> { Json(json!({ "status": User::insert_user(new_user.into_inner(), &conn), "result": User::get_all_users(&conn).first(), })) } #[post("/getUser", format ="application/json", data ="<user_data>")] pubfnfind_user(conn: DbConn, user_data: Json<UserData>) -> Json<Value> { Json(json!({ "status":200, "result": User::get_user_by_username(user_data.into_inner(), &conn), })) }
现在要做的就只剩下设置连接池了。以下是从 Rocket 文档 中摘抄的关于连接池的简介。
“Rocket 内建了对 ORM 无关数据库的支持,Rocket 提供了一个过程宏,使您可以通过连接池轻松连接 Rocket 应用程序到数据库。 “数据库连接池是一种数据结构,用于维护活动的数据库连接以便后续在应用程序中使用。”
复制代码
usediesel::pg::PgConnection; user2d2; user2d2_diesel::ConnectionManager; userocket::http::Status; userocket::request::{self, FromRequest}; userocket::{Outcome, Request, State}; usestd::ops::Deref; pubtypePool= r2d2::Pool<ConnectionManager<PgConnection>>; pubfninit_pool(db_url:String) -> Pool { letmanager = ConnectionManager::<PgConnection>::new(db_url); r2d2::Pool::new(manager).expect("db pool failure") } pubstructConn(pubr2d2::PooledConnection<ConnectionManager<PgConnection>>); impl<'a,'r> FromRequest<'a,'r>forConn { typeError= (); fnfrom_request(request: &'aRequest<'r>) -> request::Outcome<Conn, ()> { letpool = request.guard::<State<Pool>>()?; matchpool.get() { Ok(conn) => Outcome::Success(Conn(conn)), Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())), } } } implDerefforConn { typeTarget= PgConnection; #[inline(always)] fnderef(&self) -> &Self::Target { &self.0 } }
最后,我们需要在 main 文件中启动服务器。
复制代码
#![feature(plugin, const_fn, decl_macro, proc_macro_hygiene)] #![allow(proc_macro_derive_resolution_fallback, unused_attributes)] #[macro_use] externcratediesel; externcratedotenv; externcrater2d2; externcrater2d2_diesel; #[macro_use] externcraterocket; externcraterocket_contrib; #[macro_use] externcrateserde_derive; #[macro_use] externcrateserde_json; usedotenv::dotenv; usestd::env; useroutes::*; usestd::process::Command; moddb; modmodels; modroutes; modschema; fnrocket() -> rocket::Rocket { dotenv().ok(); letdatabase_url = env::var("DATABASE_URL").expect("set DATABASE_URL"); letpool = db::init_pool(database_url); rocket::ignite() .manage(pool) .mount( "/api/v1/", routes![get_all, new_user, find_user], ) } fnmain() { let_output =ifcfg!(target_os ="windows") { Command::new("cmd") .args(&["/C","cd ui && npm start"]) .spawn() .expect("Failed to start UI Application") }else{ Command::new("sh") .arg("-c") .arg("cd ui && npm start") .spawn() .expect("Failed to start UI Application") }; rocket().launch(); }
在我的项目中,我还添加了 Angular 前端,但用的还是我们的 Rust 后端来支持。
运行程序使用: cargo run
。
启动服务器
下面用 Insomnia 测试一下我们的服务器。
希望本文能对你有所帮助。祝好!
英文原文:
Recommend
-
54
手把手教你用R语言分析歌词(附代码)
-
122
前言 其实大四实习那会就有搭建个人技术博客的想法了,然后还是懒,搁置了很久。前阵子在掘金发了几篇之前写过的文章,收获了不少赞和阅读量,有点小小的成就感,所以这点动力驱使自己动手了~有些人会觉得搭建个人博客需要购买域名,需要花钱,其实不是的,Github...
-
57
帕累托图(Pareto chart),以意大利经济学家V.Pareto的名字而命名,在反映质量问题、展现质量改进项目等领域有广泛应用。它是按照发生频率大小顺序绘制的直方图,表示有多少结果是由已确认类型或范畴的原因所造成。...
-
110
作者:ANKIT CHOUDHARY;翻译:王雨桐;校对:丁楠雅; 本文约3000字,建议阅读12分钟。...
-
73
很多人想学Python程序设计或者已经了解过一点Python程序设计基础,却没办法开发出一个项目。 今天,通过演示一个简单的控制台小游戏制作,手把手教你如何用Python编写...
-
20
code小生 一个专注大前端领域的技术平台 公众号回复 Android 加入安卓技术群 本文出处:码匠笔记公众号 Pandownload 下线大家心里都很苦,不过我...
-
25
昨晚搭建环境都花了好一会时间,主要在浪费在了安装 openoffice 这个依赖环境上(_Mac 需要手动安装_)。 然后,又一步一步功能演示,记录,调试项目,并且简单研究了一下核心代码之后才把这篇文章写完。 另外,这篇文章...
-
6
底下评论说是标题党,或者是光扔个github地址上来的同学我就不说什么了。你们有看看仓库的提交记录么?我还没有吃撑到开个仓库去骗star.我的出发点就是每天更新一部分代码,教大家用我所提到的技术栈搭建一个blog
-
7
本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战! 事情是这样的,前段时间外包工头老杨又来找我了,说某汽车大品牌要开发一个网页展厅...
-
7
本文首发:《Vue 搭建带预览的「上传图片」...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK