Actix-Web+Diesel+MySQL构建简单的RESTful API | Peanuts' Blog

Actix-Web+Diesel+MySQL构建简单的RESTful API

第一个Rust项目,边写边学习。

Requirements

  • Linux
  • Actix-Web
  • Diesel
  • MySQL

Diesel

由于事先配置好了数据库,这里选择DB-First模式,配置好数据库连接后(环境变量或者dotenv),diesel setup后使用diesel print-schema > src/schema.rs,可直接生成schema.rs文件。至于model.rs文件,还是需要自己手动完成编写的,在gitter上面问了一下为什么不支持自动生成model,说是因为不知道你所需要编写的具体业务代码。(还是不够自动化呗。
下面给一些Diesel简单的CRUD语句样例供参考:
使用的是MySQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Example

// Insert
let new_user = NewUser{
username: String::from("Rust_Test"),
password: String::from("origin_password"),
};
diesel::insert_into(user)
.values(&new_user)
.execute(&connection)
.expect("Insert Failed");


// Query
let result = user
.filter(username.eq("Rust_Test"))
.first::<User>(&connection)
.expect("Error loading User.");
println!("{:?}",result);

// Update
let mut old_user = user.filter(username.eq("Rust_Test"))
.first::<User>(&connection)
.expect("Find Failed.");
old_user.password = String::from("new_password");

diesel::update(user)
.filter(username.eq(old_user.username.clone()))
.set(&old_user)
.execute(&connection)
.expect("Update Failed.");


// Delete
diesel::delete(user)
.filter(username.eq("Rust_Test"))
.execute(&connection)
.expect("Delete Failed.");

这里是可能有用的环境配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// schema.rs
table! {
user (id) {
id -> Bigint,
username -> Varchar,
password -> Varchar,
}
}

// model.rs
#[derive(Queryable,Debug,AsChangeset)]
#[table_name="user"]
pub struct User{
pub id: i64,
pub username: String,
pub password: String,
}

#[derive(Insertable)]
#[table_name="user"]
pub struct NewUser{
pub username: String,
pub password: String,
}

Actix-Web

作为性能屠榜的框架,刚写完的时候发现速度没有那么快,最后发现是diesel没有使用r2d2连接池,不过用了以后发现速度还是赶不上Gin+GORM的速度(平均15~25ms),Actix-Web+Diesel则是30~40ms,不过比SpringBoot(130ms+)开了连接池以后还快了不少…

配置diesel-r2d2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use diesel::{
r2d2::{ConnectionManager, Pool, PoolError, PooledConnection},
};

// 自定义类型
type MysqlPool = Pool<ConnectionManager<MysqlConnection>>;
type MysqlPooledConnection = PooledConnection<ConnectionManager<MysqlConnection>>;

// get a connection pool
pub fn get_connection_pool() -> Result<MysqlPool, PoolError> {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let manager = ConnectionManager::<MysqlConnection>::new(&database_url);
Pool::builder().max_size(15).build(manager)
}

// 使用pool.get()返回一个connection
}

Log配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use actix_web::middleware::Logger;
use env_logger::Env;

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
// env_logger配置日志级别
env_logger::from_env(Env::default().default_filter_or("info")).init();

HttpServer::new(|| {
App::new()
.wrap(Logger::default())
.wrap(Logger::new("%a %{User-Agent}i"))
})
.bind("127.0.0.1:8088")?
.run()
.await
}

Handler配置

handler.rs

1
2
3
4
5
6
7
8
9
10
11
pub async fn get_all_user(pool: Data<MysqlPool>) -> Result<HttpResponse> {
let connection = pool
.get()
.map_err(|e| HttpResponse::InternalServerError().json(e.to_string()))
.unwrap();

let users = user::get_all(&connection).unwrap();
Ok(HttpResponse::Ok().json(users))
}

// --snip--

model.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

pub struct User{
...
}

pub struct NewUser{
...
}



use diesel::{prelude::*, result::Error};
pub fn get_all(connection: &MysqlPooledConnection) -> Result<Vec<User>, Error> {
use crate::schema::user::dsl::*;

let users = user.load::<User>(connection)?;
Ok(users.into())
}
// --snip--

配置HttpServer(包含Logger和JsonConfig配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pub async fn server_setup() -> std::io::Result<()> {
env_logger::from_env(Env::default().default_filter_or("info")).init();

let pool = get_connection_pool().unwrap_or_else(|_| panic!("Get poor error"));

HttpServer::new(move || {
App::new()
.data(pool.clone())
.wrap(Logger::default())
.service(
web::scope("/user")
.app_data(web::Json::<User>::configure(|cfg| {
cfg.error_handler(|err, _req| {
error::InternalError::from_response(
err,
HttpResponse::Conflict().finish(),
)
.into()
})
}))
.route("/", web::get().to(user::get_all_user)),
// --snip--
)
})
.bind("127.0.0.1:8088")?
.run()
.await
}

需要注意的是,Actix-Web中的Extrator已经很好用了,如PathJson,但Json在使用时,需要使用appdata()注入包含JsonConfig配置的configure,关于ErrorAuth的内容暂时还没完善。