Audio Back End

Cover Page

DUE Wed, 02/21, 2 pm

We first prepare Chatter back end to support audio.

Install updates

Remember to install updates available to your Ubuntu back end. If N in the following notice you see when you ssh to your back-end server is not 0,

N updates can be applied immediately.

run the following:

server$ sudo apt update
server$ sudo apt upgrade

Failure to update your packages could lead to the lab back end not performing correctly and also make you vulnerable to security hacks.

If you see *** System restart required *** when you ssh to your server, please run:

server$ sync
server$ sudo reboot

Your ssh session will be ended at the server. Wait a few minutes for the system to reboot before you ssh to your server again.

Modified Chatter API data formats

In this lab, we allow user to post an audio clip with their chatt.

As in previous labs, the chatts retrieval API will send back all accumulated chatts in the form of a JSON object consisting of a dictionary entry with key “chatts” and value being an array of string arrays. In addition to the three elements “username”, “message”, “timestamp” of the first lab, each string array now carries one additional element which is a base64-encoded string of the audio clip:

{ 
    "chatts": [["username0", "message0", "timestamp0", "base64-encoded audio0"],
               ["username1", "message1", "timestamp1", "base64-encoded audio1"], 
               ... 
              ]
}

To post a chatt, the client correspondingly sends a JSON object with keys “username”, “message”, and “audio”, where the value for key “audio” is a base64-encoded string. If no audio is posted, the value may be a JSON null or the empty string (""). For example:

{	
   "username": "YOUR_UNIQNAME",	
   "message": "Hello world!",	
   "audio": ""
}

In terms of testing, unfortunately there’s no practical way to include a Base64-encoded sample audio string, nor a way to play it back without a front-end to decode it.

Database table

We first add a new column named audio of type text to the chatts table in your chatterdb database:

Rename column

If you want to change a column name, for example, from “voice” to “audio”, you first remove the “voice” column from your chatts table using:

ALTER TABLE chatts DROP COLUMN voice;

and then add a new “audio” column as per above.

Web server

We now add new URL paths and their corresponding handlers to your back-end server.

Switching back-end stack

:warning:Should you decide to switch from one back-end stack to another, be sure to disable the previous one completely before enabling the new one or you won’t be able to start the new one due to the HTTP and HTTPS ports being already in use:

  server$ sudo systemctl disable nginx gunicorn chatterd
  server$ sudo systemctl stop nginx gunicorn chatterd

To switch back-end stack, you do not need to set up a new AWS/GCP server, you can re-use the same server. You can also re-use your public key certificate and private key. All three back-end alternatives assume the same file locations for these. You do not need to move them. All three alernatives also work with the same PostgreSQL set up. You can re-use your existing chatterdb PostgreSQL database and chatts table. But you would need to set up the alternate back-end stack you want to switch to from scratch, following the chatter lab back-end spec starting from the Web server framework section to the end of the spec before continuing with the steps below.

Go with atreugo

Go with atreugo

Editing views.go

Add one new property to the Chatt struct in views/views.go:

type Chatt struct {
    // . . .
    Audio  string    `json:"audio"`
}

To handle audio data uploads, make a copy of your PostChatt() function inside your views.go file and name the copy PostAudio(). Replace the call to chatterDB.Exec() with:

chatterDB.Exec(ctx, `INSERT INTO chatts (username, message, audio) VALUES ($1, $2, $3)`, chatt.Username, chatt.Message, chatt.Audio)

which extracts the audio data from the JSON object and insert it into the chatts table, along with the rest of its associated chatt. To the back-end database, the audio data is just a string.

Next, make a copy of your GetChatts() function inside your views.go file and name the copy GetAudio(). Replace the SELECT statement with `SELECT username, message, time, COALESCE(audio, '') FROM chatts ORDER BY time DESC` and replace the two lines in the for rows.Next() {} block with these:

        rows.Scan(&chatt.Username, &chatt.Message, &chatt.Timestamp, &chatt.Audio)
        chattArr = append(chattArr, []any{chatt.Username, chatt.Message, chatt.Timestamp, chatt.Audio})

In addition to the original three columns, we added reading the audio column and included it in the chatt data returned to the front end.

Save and exit views.go.

Routing for new URLs

For the newly added GetAudio() and PostAudio() functions, add the following new routes to the routes array in router/router.go:

var routes = []Route {
    // . . .
    {"GET", "/getaudio/", views.GetAudio},
    {"POST", "/postaudio/", views.PostAudio},
}

Save and exit router.go.

:point_right:Go is a compiled language, like C/C++ and unlike Python, which is an interpreted language. This means you must run go build each and every time you made changes to your code, for the changes to show up in your executable.

Rebuild and restart chatterd:

server$ go build
server$ sudo systemctl restart chatterd

Python with Django

Python with Django

Editing views.py

Let’s edit your views.py to handle audio uploads. Make a copy of your postchatt() function inside your views.py file and name the copy postaudio(). Add code to your postaudio() to extract the audio data from the JSON object and insert it into the chatts table, along with the rest of its associated chatt. To the back-end database, the audio data is just a string.

Next, make a copy of your getchatts() function inside your views.py file and name the copy getaudio(). Replace the SELECT statement with 'SELECT username, message, time, audio FROM chatts ORDER BY time DESC;'.

Save and exit views.py.

Routing for new urls

For the newly added getaudio() and postaudio() functions, add the following new routes to the urlpatterns array in urls.py:

    path('getaudio/', views.getaudio, name='getaudio'),
    path('postaudio/', views.postaudio, name='postaudio'),

Save and exit urls.py and restart Gunicorn.

Rust with axum

Rust with axum

Editing handlers.rs

Add one new property to the Chatt struct in handlers.rs:

pub struct Chatt {
    // . . .
    audio: Option<String>,
}

Now make a copy of your postchatt() function inside your handlers.rs file and name the copy postaudio(). Replace the parameters in the call to chatterDB.execute() with:

            "INSERT INTO chatts (username, message, audio) VALUES ($1, $2, $3)",
            &[&chatt.username, &chatt.message, &chatt.audio],

which extracts the audio from the JSON object and insert it into the chatts table, along with the rest of its associated chatt. To the back-end database, the audio data is just a string.

Next, make a copy of your getchatts() function inside your handlers.rs file and name the copy getaudio(). In getaudio(),

  1. replace the declaration of chattArr to:
    let mut chattArr: Vec<Vec<Option<String>>> = Vec::new();
    
  2. replace the SELECT statement with the following: "SELECT username, message, time, audio FROM chatts ORDER BY time DESC". This statement will retrieve all data, including the new audio data from the PostgreSQL database.

  3. replace the chattArr.push(/*...*/); line with:
         chattArr.push(vec![
             row.get(0),
             row.get(1),
             Some(row.get::<usize, DateTime<Local>>(2).to_string()),
             row.get(3),
         ]); 
    

    In addition to the original three columns, we added reading the audio column and included it in the chatt data returned to the front end.

Save and exit handlers.rs.

Routing for new URLs

For the newly added getaudio() and postaudio() functions, add the following new routes to the Router instantiation of the router variable in main.rs, before the .with_state(pgpool); line:

        .route("/getaudio/", get(handlers::getaudio))
        .route("/postaudio/", post(handlers::postaudio))

Save and exit main.rs. Rebuild and restart chatterd.

server$ cargo build --release
server$ sudo systemctl restart chatterd

Submitting your back end

Congratulations! You’ve completed the back end modifications necessary to support audio!


Prepared for EECS 441 by Wendan Jiang, Ollie Elmgren, Benjamin Brengman, Mark Wassink, Alexander Wu, Yibo Pi, and Sugih Jamin Last updated: February 7th, 2024