Rocket ๐Ÿš€์„ ์‚ฌ์šฉํ•˜์—ฌ ์ตœ์†Œํ•œ์˜ RESTful ๋…ธ๋ž˜ ์š”์ฒญ API ๋งŒ๋“ค๊ธฐ

12856 ๋‹จ์–ด apirocketrust

์‹ ์ฒญ๊ณก ์‹œ์Šคํ…œ์ด๋ž€?



YouTube ๋ฐ Twitch์™€ ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์ŠคํŠธ๋ฆฌ๋จธ๋ฅผ ์‹œ์ฒญํ•˜๋Š” ๊ฒฝ์šฐ ๋…ธ๋ž˜ ์š”์ฒญ ์‹œ์Šคํ…œ์„ ์ ‘ํ–ˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋…ธ๋ž˜ ์š”์ฒญ ์‹œ์Šคํ…œ์„ ํ†ตํ•ด ์‹œ์ฒญ์ž๋Š” ๋…ธ๋ž˜ ๋Œ€๊ธฐ์—ด์— ๋…ธ๋ž˜๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋…ธ๋ž˜๊ฐ€ ๋Œ€๊ธฐ์—ด ๋งจ ์•ž์— ๋„๋‹ฌํ•˜๋ฉด ๋ผ์ด๋ธŒ ์ŠคํŠธ๋ฆผ์„ ํ†ตํ•ด ๋…ธ๋ž˜๊ฐ€ ์žฌ์ƒ๋ฉ๋‹ˆ๋‹ค.

rocket ํฌ๋ ˆ์ดํŠธ์˜ ๋„์›€์œผ๋กœ Rust์—์„œ ์ด ์‹œ์Šคํ…œ์„ ์œ„ํ•œ API๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ๋งค์šฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.

์‹œ์ž‘ํ•˜๊ธฐ



์ด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” rocket ํฌ๋ ˆ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ RESTful API๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™”



์ƒˆ Cargo ํ”„๋กœ์ ํŠธ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋ช…๋ น์„ ์‹คํ–‰ํ•˜์„ธ์š”.

cargo init <YOUR_PROJECT_NAME>


์ข…์†์„ฑ ์ถ”๊ฐ€


Cargo.toml์— ์ข…์† ํ•ญ๋ชฉ์œผ๋กœ rocket์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

[dependencies]

# NOTE: This is a pre-release version.
# Thus, It is suggested NOT to use this in production.
rocket = "0.5.0-rc.2"


์ฃผ์š” ๊ธฐ๋Šฅ



Cargo ํ”„๋กœ์ ํŠธ๊ฐ€ ์ค€๋น„๋˜๋ฉด ๊ธฐ๋ณธ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์„ ์™„์ „ํžˆ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Rocket์˜ ์ ˆ์ฐจ ๋งคํฌ๋กœrocket์— ๊ธฐ์ธํ•œ ์ƒˆ๋กœ์šดlaunch ๋ฐฉ๋ฒ•์„ ์œ„ํ•œ ๊ณต๊ฐ„์ด ์ƒ๊น๋‹ˆ๋‹ค.

์ด ํ•จ์ˆ˜๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ธฐ๋ณธ ํ•จ์ˆ˜๋ฅผ ๋Œ€์ฒดํ•˜๋ฉฐ ์‹œ์ž‘ ์‹œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

//main.rs

#[macro_use]
extern crate rocket;

use rocket::{Build, Rocket};

#[launch]
fn rocket() -> Rocket<Build> {
    Rocket::build()
        // Set the `/` route path as the base for our routes.
        // When we create our routes, we'll include them in the arguments for the `routes!` macro.
        .mount("/", routes![])
}


์ด๊ฒƒ์„ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

Configured for debug.
   >> address: 127.0.0.1
   >> port: 8000
   >> workers: 6
   >> ident: Rocket
   >> limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
   >> temp dir: C:\Users\dev\AppData\Local\Temp\
   >> http/2: true
   >> keep-alive: 5s
   >> tls: disabled
   >> shutdown: ctrlc = true, force = true, grace = 2s, mercy = 3s
   >> log level: normal
   >> cli colors: true
Fairings:
   >> Shield (liftoff, response, singleton)
Shield:
   >> X-Content-Type-Options: nosniff
   >> Permissions-Policy: interest-cohort=()
   >> X-Frame-Options: SAMEORIGIN
Rocket has launched from http://127.0.0.1:8000


์ด์ œ rocket ํ•จ์ˆ˜๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ API์šฉ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋…ธ๋ž˜ ๋Œ€๊ธฐ์—ด ์ €์žฅ



๋…ธ๋ž˜ ๋Œ€๊ธฐ์—ด์„ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด ์ •์ LinkedList์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ํฌํ•จ๋˜์–ด ์žˆ๊ณ  ๊ธฐ๋ณธ์ ์œผ๋กœ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์ด ์žˆ๋Š” ๋Œ€๊ธฐ์—ด ์—ญํ• ์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— LinkedList๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ ๋ชฉ๋ก์„ ์ •์ ์œผ๋กœ ์ˆ˜์ •ํ•˜๋ ค๋ฉด ๋ชฉ๋ก์„ Mutex ๋กœ ๋ž˜ํ•‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

use std::collections::LinkedList;
use std::sync::Mutex;

static SONG_QUEUE: Mutex<LinkedList<String>> = Mutex::new(LinkedList::new());

Mutex์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๋ ค๋ฉด here์„ ํด๋ฆญํ•˜์‹ญ์‹œ์˜ค.

์œ ํ‹ธ๋ฆฌํ‹ฐ ๊ธฐ๋Šฅ



์šฐ๋ฆฌ๋Š” ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ๋งŽ์ด ๋ฐ˜๋ณตํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

let lock = SONG_QUEUE
    .lock()
    .expect("Unable to acquire lock on song queue because the Mutex was poisoned");


์šฐ๋ฆฌ๋ฅผ ์œ„ํ•ด ์ด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

use std::sync::{Mutex, MutexGuard};

fn acquire_queue<'a>() -> MutexGuard<'a, LinkedList<String>> {
    SONG_QUEUE
        .lock()
        .expect("Unable to acquire lock on song queue because the Mutex was poisoned")
}


์ถ”๊ฐ€ ๊ฒฝ๋กœ ๋งŒ๋“ค๊ธฐ


SONG_QUEUE ๋ณ€์ˆ˜๊ฐ€ ์ •์˜๋˜๋ฉด ๊ฒฝ๋กœ ์ƒ์„ฑ์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
add ๊ฒฝ๋กœ๋Š” ๋…ธ๋ž˜ ์ด๋ฆ„์ด ํฌํ•จ๋œ POST ์š”์ฒญ์„ ๋ฐ›์€ ๋‹ค์Œ ๋…ธ๋ž˜ ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค.

์„ ํƒ์ ์œผ๋กœ ๋…ธ๋ž˜๊ฐ€ ๋ฐฐ์น˜๋œ ์œ„์น˜๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

#[post("/add/<song_name>")]
fn add_song(song_name: String) -> String {
    let mut lock = acquire_queue();

    lock.push_back(song_name);

    format!("Song added. This song is in position {}.", lock.len())
}


์ด ์ƒˆ ๊ฒฝ๋กœ๋ฅผ routes! ๋งคํฌ๋กœ์— ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์‹ญ์‹œ์˜ค.
  • - .mount("/", routes![])
  • + .mount("/", routes![add_song])

  • ์ด ์ƒˆ๋กœ์šด ๊ฒฝ๋กœ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋ ค๋ฉด ํ”„๋กœ๊ทธ๋žจ์„ ์‹คํ–‰ํ•œ ๋‹ค์Œ ๋ช‡ ๊ฐ€์ง€ ๋งค๊ฐœ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฝ๋กœ๋ฅผ ์ปฌ๋งํ•ด ๋ณด์‹ญ์‹œ์˜ค.

    ๊ฒฐ๊ณผ




    C:\Users\dev>curl -X POST http://localhost:8000/add/Hello
    Song added. This song is in position 1.
    



    C:\Users\dev>curl -X POST http://localhost:8000/add/Hello%20number%202
    Song added. This song is in position 2.
    


    ๋ณด๊ธฐ ๊ฒฝ๋กœ ๋งŒ๋“ค๊ธฐ



    ์ด์ œ ์‚ฌ์šฉ์ž๋Š” ๋…ธ๋ž˜๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ํ˜„์žฌ ๋Œ€๊ธฐ์—ด์— ์žˆ๋Š” ๋…ธ๋ž˜๋ฅผ ๋ณผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
    ๊ฑฑ์ •ํ•˜์ง€ ๋งˆ์„ธ์š”. ์ƒˆ GET ๊ฒฝ๋กœ๋ฅผ ๋งŒ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค.

    ๋‹จ ํ•œ ์ค„์˜ ์ฝ”๋“œ!

    #[get("/view")]
    fn view() -> String {
        format!("{:?}", acquire_queue())
    }
    


    ์ด ์ƒˆ ๊ฒฝ๋กœ๋ฅผ routes! ๋งคํฌ๋กœ์— ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์„ ์žŠ์ง€ ๋งˆ์‹ญ์‹œ์˜ค.
  • - .mount("/", routes![add_song])
  • + .mount("/", routes![add_song, view])

  • ๊ฒฐ๊ณผ




    C:\Users\dev>curl -X POST http://localhost:8000/add/Hello%20World
    Song added. This song is in position 1.
    
    C:\Users\dev>curl http://localhost:8000/view
    ["Hello World"]
    


    ๋…ธ๋ž˜ ์ œ๊ฑฐ



    ๊ธฐ์‚ฌ์˜ ๋‹จ์ˆœ์„ฑ์„ ์œ„ํ•ด ๋…ธ๋ž˜๊ฐ€ ๋Œ€๊ธฐ์—ด ๋งจ ์•ž์— ๋„๋‹ฌํ•˜๋ฉด ์‹ค์ œ๋กœ ๋…ธ๋ž˜๋ฅผ ์žฌ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๋…ธ๋ž˜๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.

    ์ด ๊ฒฝ์šฐ ๋Œ€๊ธฐ์—ด ๋งจ ์•ž์— ๋„๋‹ฌํ•œ ํ›„ 60์ดˆ ํ›„์— ๋…ธ๋ž˜๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.

    use std::thread;
    use std::time::Duration;
    
    fn remove_song_timer() {
        while !acquire_queue().is_empty() {
            thread::sleep(Duration::from_secs(60));
            acquire_queue().pop_front();
        }
    }
    


    ๋…ธ๋ž˜๊ฐ€ ๋นˆ ๋Œ€๊ธฐ์—ด์— ์ถ”๊ฐ€๋˜๋ฉด add_song ์Šค๋ ˆ๋“œ๊ฐ€ ์ƒ์„ฑ๋˜๋„๋ก remove_song_timer ๊ฒฝ๋กœ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    #[post("/add/<song_name>")]
    fn add_song(song_name: String) -> String {
        let mut lock = acquire_queue();
    
        if lock.is_empty() {
            thread::spawn(remove_song_timer);
        }
        lock.push_back(song_name);
    
        format!("Song added. This song is in position {}.", lock.len())
    }
    


    ๊ฒฐ๊ณผ




    C:\Users\dev>curl -X POST http://localhost:8000/add/Hello%20World
    Song added. This song is in position 1.
    C:\Users\dev>curl http://localhost:8000/view
    ["Hello World"]
    


    60์ดˆ ํ›„...

    C:\Users\dev>curl http://localhost:8000/view
    []
    


    ๊ทธ๊ฒŒ ๋‹ค์•ผ! Rust์—์„œ RESTful API๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ rocket ํฌ๋ ˆ์ดํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์—„์ฒญ๋‚˜๊ฒŒ ์‰ฝ์Šต๋‹ˆ๋‹ค. ์ƒˆ ๊ธฐ์‚ฌ๋ฅผ ๊ฒŒ์‹œํ•  ๋•Œ๋งˆ๋‹ค ์•Œ๋ฆผ์„ ๋ฐ›์œผ๋ ค๋ฉด dev.to์—์„œ ์ €๋ฅผ ํŒ”๋กœ์šฐํ•˜์‹ญ์‹œ์˜ค.

    ์ฝ์–ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

    ์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ