Rocket ๐์ ์ฌ์ฉํ์ฌ ์ต์ํ์ RESTful ๋ ธ๋ ์์ฒญ API ๋ง๋ค๊ธฐ
์ ์ฒญ๊ณก ์์คํ ์ด๋?
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
์์ ์ ๋ฅผ ํ๋ก์ฐํ์ญ์์ค.์ฝ์ด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค!
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(Rocket ๐์ ์ฌ์ฉํ์ฌ ์ต์ํ์ RESTful ๋ ธ๋ ์์ฒญ API ๋ง๋ค๊ธฐ), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค https://dev.to/imajindevon/creating-a-minimal-restful-song-request-api-using-rocket-14klํ ์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ ์ธ ๋ฐ๊ฒฌ์ ์ ๋ (Collection and Share based on the CC Protocol.)
์ข์ ์นํ์ด์ง ์ฆ๊ฒจ์ฐพ๊ธฐ
๊ฐ๋ฐ์ ์ฐ์ ์ฌ์ดํธ ์์ง
๊ฐ๋ฐ์๊ฐ ์์์ผ ํ ํ์ ์ฌ์ดํธ 100์ ์ถ์ฒ ์ฐ๋ฆฌ๋ ๋น์ ์ ์ํด 100๊ฐ์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ํ์ต ์ฌ์ดํธ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค