ABOUT ME

-

Total
-
  • Rust 백엔드: REST API (Rocket + MySQL)
    컴퓨터/Rust 2021. 7. 14. 14:11
    728x90
    반응형

    Rocket

    Rust 언어용 웹 프레임워크

     

    Rocket - Simple, Fast, Type-Safe Web Framework for Rust

    Forms? Check! Handling forms is simple and easy.

    rocket.rs

     

    Rust Rocket 기본 설정

    프로젝트 시작

    cargo new rustweb --bin
    cd rustweb

     

    기본 src/main.rs

    #[macro_use] extern crate rocket;
    
    #[get("/")]
    fn index() -> &'static str {
        "Hello, world!"
    }
    
    #[launch]
    fn rocket() -> _ {
        rocket::build().mount("/", routes![index])
    }
    cargo build
    cargo run

    GET localhost:8000/ 해보면 "Hello, world!"를 볼 수 있다.

     

    Cargo.toml 수정

    [dependencies]
    rocket = "0.4.10"
    rocket_contrib = "0.4"
    serde = { version = "1.0.126", features = ["derive"] }
    serde_json = "1.0.64"
    serde_derive = "1.0.126"
    reqwest = { version = "0.11", features = ["json"] }
    tokio = { version = "1", features = ["full"] }
    diesel = { version = "1.4.7", features = ["mysql"] }
    dotenv = "0.15.0"
    r2d2 = "*"
    r2d2-diesel = "*"

     

    완성할 프로젝트 구조

    (db 따로, route 따로, test 따로)

    lib.rs에는 프로젝트 공통으로 쓰일 것들 모임

    │  .env
    │  .gitignore
    │  Cargo.lock
    │  Cargo.toml
    │  disel.toml
    │  Makefile
    │  README.md
    │  Rocket.toml
    │  
    ├─src
    │  │  lib.rs
    │  │  main.rs
    │  │  
    │  ├─db
    │  │      connection.rs
    │  │      mod.rs
    │  │      models.rs
    │  │      query.rs
    │  │      schema.rs
    │  │      
    │  └─routes
    │          mod.rs
    │          notice.rs
    │          
    └─tests
            common.rs
            notice.rs
            parse.rs

     

    MySQL 설정하기

    Python, Go언어로 REST API를 만들 때보다 시간이 많이 걸렸다.

     

    서버 주소는 숨기기 위해 Rocket.toml이나 .env에 저장해서 사용하면 된다.

     

    Rocket.toml

    [global.databases.my_db]
    url = "mysql://id:password@server/자신db"

     

     

    .env

    DATABASE_URL=mysql://id:password@server/자신db

     

    lib.rs

    #![feature(proc_macro_hygiene, decl_macro)]
    
    #[macro_use]
    extern crate rocket;
    #[macro_use]
    extern crate rocket_contrib;
    
    #[macro_use]
    extern crate diesel;
    // #[macro_use]
    // extern crate diesel_codegen;
    extern crate r2d2;
    extern crate r2d2_diesel;
    
    #[macro_use]
    extern crate serde_derive;
    
    mod db;
    mod routes;
    
    pub fn rocket() -> rocket::Rocket {
        rocket::ignite().manage(db::connection::init_pool()).mount(
            "/api",
            routes![routes::notice::hello, routes::notice::db_test],
        )
    }

     

    src/routes/notice.rs

    /hello는 struct를 JSON으로 보내는 방법이고

    /db는 db 데이터를 잘 받는지 테스트하는 endpoint이다.

    use crate::db::connection::Conn;
    use crate::db::models::Schedule;
    use crate::db::query;
    use diesel;
    use diesel::result::Error;
    use rocket::http::Status;
    use rocket_contrib::json::Json;
    use serde::{Deserialize, Serialize};
    
    #[derive(Serialize, Deserialize, Debug)]
    pub struct Notice {
        id: u64,
        title: String,
        date: String,
        link: String,
        writer: String,
    }
    
    #[get("/hello")]
    pub fn hello() -> Json<Notice> {
        let notice = Notice {
            id: 12345,
            title: "공지1".to_string(),
            date: "2021-07-09".to_string(),
            link: "https://".to_string(),
            writer: "CSW".to_string(),
        };
        Json(notice)
    }
    
    #[get("/db")]
    pub fn db_test(conn: Conn) -> Result<Json<Vec<Schedule>>, Status> {
        let result = query::show_scheds(&conn)
            .map(|sched| Json(sched))
            .map_err(|error| error_status(error));
    
        for row in query::show_scheds(&conn).unwrap() {
            println!("id: {}, content: {}", row.id, row.content);
        }
    
        result
    }
    
    fn error_status(error: Error) -> Status {
        match error {
            Error::NotFound => Status::Ok, // 챗봇은 무조건 200
            _ => Status::InternalServerError,
        }
    }

     

    src/db/schema.rs

    diesel::table! {
        ajou_sched (id) {
            id -> Integer,
            start_date -> Varchar,
            end_date -> Varchar,
            content -> Varchar,
        }
    }

     

    src/db/query.rs

    #![allow(proc_macro_derive_resolution_fallback)]
    
    use diesel;
    use diesel::prelude::*;
    
    use crate::db::models::Schedule;
    
    use crate::db::schema::ajou_sched::dsl::*;
    
    // SELECT * from ajou_sched limit 5
    pub fn show_scheds(connection: &MysqlConnection) -> QueryResult<Vec<Schedule>> {
        //posts.filter(published.eq(true))
        ajou_sched.limit(5).load::<Schedule>(&*connection)
    }

     

    src/db/models.rs

    #![allow(proc_macro_derive_resolution_fallback)]
    
    use crate::db::schema::ajou_sched;
    use diesel::prelude::*;
    
    #[derive(Queryable, AsChangeset, Serialize, Deserialize, Debug)]
    #[table_name = "ajou_sched"]
    pub struct Schedule {
        pub id: i32,
        pub content: String,
        pub start_date: String,
        pub end_date: String,
    }
    
    // impl Schedule {
    //     pub fn read(conn: &MysqlConnection) -> Result<Vec<Schedule>, Error> {
    //         ajou_sched::table
    //             .order(ajou_sched::id.asc())
    //             .load::<Schedule>(conn)
    //     }
    // }

     

    src/db/connection.rs

    MySQL pool을 이용해서 연결을 생성하는 방법이다.

    (.env 파일에서 데이터 베이스 주소 읽어왔음)

    use std::ops::Deref;
    
    use r2d2;
    use r2d2::PooledConnection;
    //use diesel::mysql::MysqlConnection;
    use diesel::mysql::*;
    use r2d2_diesel::ConnectionManager;
    
    use dotenv::dotenv;
    use rocket::http::Status;
    use rocket::request::{self, FromRequest};
    use rocket::{Outcome, Request, State};
    use std::env;
    pub type Pool = r2d2::Pool<ConnectionManager<MysqlConnection>>;
    
    // pub const DATABASE_FILE: &'static str = env!("DATABASE_URL");
    
    pub fn init_pool() -> Pool {
        dotenv().ok(); // Grabbing ENV vars
    
        // Pull DATABASE_URL env var
        let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    
        // let config = Config::default();
        let manager = ConnectionManager::<MysqlConnection>::new(database_url);
        Pool::new(manager).expect("db pool")
    }
    
    // #[derive(ConnectionPool)]
    pub struct Conn(pub PooledConnection<ConnectionManager<MysqlConnection>>);
    
    impl Deref for Conn {
        type Target = MysqlConnection;
    
        // #[inline(always)]
        fn deref(&self) -> &Self::Target {
            &self.0
        }
    }
    
    impl<'a, 'r> FromRequest<'a, 'r> for Conn {
        type Error = ();
    
        fn from_request(request: &'a Request<'r>) -> request::Outcome<Conn, Self::Error> {
            let pool = request.guard::<State<Pool>>()?;
            match pool.get() {
                Ok(conn) => Outcome::Success(Conn(conn)),
                Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
            }
        }
    }

     

    결과

    Postman GET /api/db 결과

     

    참고

    아래 개인 프로젝트나 MySQL 연동 commit 참고

     

    MySQL implementation [2/2] · Alfex4936/Rust-Backend@a446f93

    학사 일정 저장된 것 불러오기 성공 GET /api/db 결과: [ { "id": 1, "content": "07.06 (화)", "start_date": "07.09 (금)", "end...

    github.com

     

    728x90

    댓글