PEANUT996

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已经很好用了,如PathJson,但Json在使用时,需要使用appdata()注入包含JsonConfig配置的configure,关于ErrorAuth的内容暂时还没完善。