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,说是因为不知道你所需要编写的具体业务代码。(还是不够自动化呗。 业务Model意指需要自己拼接的DTO。
下面给一些Diesel
简单的CRUD语句样例供参考:
使用的是MySQL
// 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.");
// 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.");
这里是可能有用的环境配置:
// 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
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配置
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
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
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配置)
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
已经很好用了,如Path
、Json
,但Json
在使用时,需要使用appdata()
注入包含JsonConfig
配置的configure
,关于Error
和Auth
的内容暂时还没完善。