使用Rust+Rocket创建一个CRUD的RESTful歌曲请求API


如果您在 YouTube 和 Twitch 等平台上观看流媒体,您可能遇到过歌曲请求系统。歌曲请求系统允许观众将歌曲添加到 歌曲队列中。当歌曲到达队列的最前面时,歌曲会在直播流中播放。
在 Rocket crate 的帮助下,Rust 为这个系统创建一个 API 非常容易。
使用Rocket箱创建一个 RESTful API。

初始化:

cargo init <YOUR_PROJECT_NAME>

加入依赖:

[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的发射程序宏。

这个函数实质上将取代我们的主函数,并在启动时被调用。

//main.rs

#[macro_use]
extern crate rocket;

use rocket::{Build, Rocket};

#[launch]
fn rocket() -> Rocket<Build> {
    Rocket::build()
       
// 设置`/`路由路径作为我们路由的基础。
       
// 当我们创建我们的路由时,我们将把它们包括在`routes!`宏的参数中。
        .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, click here.(中文:https://github.com/kumakichi/easy_rust_chs)

我们将多次重复以下代码:

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")
}

创建add路由
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!宏中注册这个新路由。

  1. - .mount("/", routes![])
  2. + .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.

创建view路由
用户现在可以添加歌曲,但无法查看当前在队列中的歌曲。
别担心,我们将创建一个新的GET路由。

只有一行代码!

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

不要忘记在routes!宏中注册这个新路由。

  1. - .mount("/", routes![add_song])
  2. + .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
[]