写点什么

手把手教你用 Rust 搭建 REST API

2020 年 5 月 12 日

手把手教你用Rust搭建REST API


这篇文章将展示如何用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 = falsefeatures = ["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


CREATE TABLE users(    id         SERIAL PRIMARY KEY,    username   VARCHAR NOT NULL,    password   VARCHAR NOT NULL,    first_name VARCHAR NOT NULL)
复制代码


down.sql


DROP TABLE users
复制代码


应用迁移的话可以用这个命令:


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)。首先先创建一个结构。


use diesel;use diesel::pg::PgConnection;use diesel::prelude::*;use super::schema::users;use super::schema::users::dsl::users as all_users;// this is to get users from the database#[derive(Serialize, Queryable)] pub struct User {    pub id: i32,    pub username: String,    pub password: String,    pub first_name: String,}
复制代码


你大概会好奇结构定义中的这些标注都是什么。他们被称作导出(derives),也就是说,这些代码会导出序列化、可查询的 traits。#[derive(Serialize)]以及 #[derive(Deserialize)] 可以用来映射数据到响应和请求上。


下面再创建两个 struct,后面都会用到。


// decode request data#[derive(Deserialize)] pub struct UserData {    pub username: String,}// this is to insert users to database#[derive(Serialize, Deserialize, Insertable)]#[table_name = "users"]pub struct NewUser {    pub username: String,    pub password: String,    pub first_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


use super::db::Conn as DbConn;use rocket_contrib::json::Json;use super::models::{User, NewUser};use serde_json::Value;use crate::models::UserData;
#[post("/users", format = "application/json")]pub fn get_all(conn: DbConn) -> Json<Value> { let users = User::get_all_users(&conn); Json(json!({ "status": 200, "result": users, }))}
#[post("/newUser", format = "application/json", data = "<new_user>")]pub fn new_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>")]pub fn find_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 应用程序到数据库。

“数据库连接池是一种数据结构,用于维护活动的数据库连接以便后续在应用程序中使用。”


use diesel::pg::PgConnection;use r2d2;use r2d2_diesel::ConnectionManager;use rocket::http::Status;use rocket::request::{self, FromRequest};use rocket::{Outcome, Request, State};use std::ops::Deref;
pub type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
pub fn init_pool(db_url: String) -> Pool { let manager = ConnectionManager::<PgConnection>::new(db_url); r2d2::Pool::new(manager).expect("db pool failure")}
pub struct Conn(pub r2d2::PooledConnection<ConnectionManager<PgConnection>>);
impl<'a, 'r> FromRequest<'a, 'r> for Conn { type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Conn, ()> { let pool = request.guard::<State<Pool>>()?; match pool.get() { Ok(conn) => Outcome::Success(Conn(conn)), Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())), } }}
impl Deref for Conn { type Target = PgConnection;
#[inline(always)] fn deref(&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]extern crate diesel;extern crate dotenv;extern crate r2d2;extern crate r2d2_diesel;#[macro_use]extern crate rocket;extern crate rocket_contrib;#[macro_use]extern crate serde_derive;#[macro_use]extern crate serde_json;use dotenv::dotenv;use std::env;use routes::*;use std::process::Command;mod db;mod models;mod routes;mod schema;fn rocket() -> rocket::Rocket {    dotenv().ok();    let database_url = env::var("DATABASE_URL").expect("set DATABASE_URL");    let pool = db::init_pool(database_url);    rocket::ignite()        .manage(pool)        .mount(            "/api/v1/",            routes![get_all, new_user, find_user],        )}fn main() {    let _output = if cfg!(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测试一下我们的服务器。



希望本文能对你有所帮助。祝好!


英文原文:


How to Build a REST API in Rust — A Step-by-Step Guide


2020 年 5 月 12 日 17:136111
用户头像

发布了 30 篇内容, 共 68708 次阅读, 收获喜欢 51 次。

关注

评论 2 条评论

发布
用户头像
Json(json!(..)) -> Json<Value>不是多此一举吗?
2020 年 05 月 18 日 12:31
回复
用户头像
想API来API,说曹操,曹操到
2020 年 05 月 13 日 10:46
回复
没有更多了
发现更多内容

OCR技术的未来发展与演进

OCR技术的未来发展与演进

手把手教你用Rust搭建REST API-InfoQ