Zofia
Posted on October 19, 2022
What is a song request system?
If you watch streamers on platforms such as YouTube and Twitch, you may have encountered a song request system. A song request system allows the viewer to add a song to a song queue. When the song reaches the front of the queue, the song is played over the live stream.
With the help of the rocket crate, creating an API for this system in Rust is quite easy.
Getting started
For this project, I'll be creating a RESTful API using the rocket crate.
Initializing the project
To initialize a new Cargo project, run the following command:
cargo init <YOUR_PROJECT_NAME>
Adding dependencies
Add rocket as a dependency in your Cargo.toml
:
[dependencies]
# NOTE: This is a pre-release version.
# Thus, It is suggested NOT to use this in production.
rocket = "0.5.0-rc.2"
Main function
With our Cargo project ready, we can remove the default main function entirely. This will make room for our new rocket
method, which is attributed with Rocket's launch
procedural macro.
This function will essentially replace our main function, and will be called on startup.
//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![])
}
If we were to run this, we would see the following:
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
Now that we have our rocket
function, we can start developing the functionality for our API.
Storing the song queue
For storing the song queue, we will use a static LinkedList
.
We use a LinkedList
because it's included in the standard library, and basically acts as a queue with extra features.
Though, to allow the list to be modified statically, we must wrap the list in a Mutex
.
use std::collections::LinkedList;
use std::sync::Mutex;
static SONG_QUEUE: Mutex<LinkedList<String>> = Mutex::new(LinkedList::new());
To learn more about Mutex
, click here.
Utility function
We will be repeating the following code a lot:
let lock = SONG_QUEUE
.lock()
.expect("Unable to acquire lock on song queue because the Mutex was poisoned");
It's better if we create a function that does this for us.
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")
}
Creating the add
route
With our SONG_QUEUE
variable defined, we can start creating routes.
The add
route will receive a POST request containing a song name, which will then be added to the song queue.
Optionally, we can return the position the song has been placed into.
#[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())
}
Don't forget to register this new route in the routes!
macro.
- .mount("/", routes![])
+ .mount("/", routes![add_song])
To test this new route, run the program, then try curling our route with some parameters.
Result
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.
Creating the view
route
Users can now add songs, but are unable to view which songs are currently in the queue.
Don't worry, we'll just create a new GET route.
Only one line of code!
#[get("/view")]
fn view() -> String {
format!("{:?}", acquire_queue())
}
Don't forget to register this new route in the routes!
macro.
- .mount("/", routes![add_song])
+ .mount("/", routes![add_song, view])
Result
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"]
Removing songs
For the sake of the simplicity of the article, we won't actually play songs once they reach the front of the queue. Instead, we'll just remove songs after a certain amount of time has passed.
In this case, we'll remove songs 60 seconds after they've reached the front of the queue.
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();
}
}
We need to modify our add_song
route to make sure a remove_song_timer
thread is spawned if a song is added to an empty queue.
#[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())
}
Result
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 seconds later...
C:\Users\dev>curl http://localhost:8000/view
[]
That's about it! Writing RESTful APIs in Rust are ridiculously easy with the rocket crate. Make sure to follow me on dev.to
to be notified whenever I post a new article.
Thanks for reading!
Posted on October 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.