You will continue to add features to your app and pull from the repo to get the new front end features.
Sample Deployment: https://312demo.nico.engineer/
Before you get started on this hw, please go to this line in your server.py: "socketserver.TCPServer.allow_reuse_address = True" and "server = socketserver.TCPServer((host, port), MyTCPHandler)". Change both of these lines from TCPServer to ThreadingTCPServer. This will make sure that your server can handle multiple requests at once, without blocking other requests.
By the end of this objective, your users will be able to upload an image to use as their profile picture and share videos with other users. To enable these features, we'll first need a multipart parser.
Create a file named util/multipart.py containing the following function (This file does not define a class). In this file, write a function named "parse_multipart" that takes a Request object as a parameter. This function will assume that the input Request is a multipart request and will extract all the relevant values of the request. This function returns an object containing the following fields (You have some freedom in how you design the classes for the objects as long as they have the required fields. Please note that a dict with these fields is not an object. You must create a class, then create objects using that class).
Add functionality to allow users to change their avatar (profile picture). Add the following path to render a new page using layout.html.
Visit the Change Avatar page to find a form that sends multipart requests to your server containing an image chosen by the user. Your server must listen for POST requests at the "/api/users/avatar" endpoint and process the uploaded images. This endpoint must have the following features:
Please keep the following notes in mind when implementing this feature. All of these will also apply to video uploads.
File Naming: When an image is uploaded, your server will save the image as a file. It is recommended that you devise a naming convention for the image files instead of using the names submitted by your users. Naming the images "image1.jpg", "image2.jpg", "image3.jpg", etc is fine, or you can generate guids as the names.
Persistence: Your uploads must persist through a server restart. You should store your images in files (It's generally bad practice to store large files in a database), and store the filenames in your database. Since your images are stored in files, they will already persist through a restart.
Buffering: Your app must allow for large images to be uploaded. You'll accomplish this by buffering your HTTP requests. Read the content length of the request and buffer until you read the whole body. Your buffering should be able to handle arbitrarily large files. You must use proper buffering for this. Do not increase the TCP buffer size by passing a large int to the recv method (That wouldn't work anyway for very large files). You can assume that all the headers, upto the first "\r\n\r\n", of the request are always read in the first read from the socket with a 2048 buffer (eg. You do not have buffer while reading the headers, and you can safely read the content length before any buffering). It's recommended that you buffer in server.py before sending the request to your router, and not just in the route for changing the avatar, as this will cover all future large requests for any path.
Do not submit with any print statements printing out the full buffered request body. If you submit to autolab with these print statements, it will crash your submission. This is because you're printing the contents of a 2GB video file.
Implement a [simplified] YouTube clone where users can share videos with each other. Add the following paths that will be rendered using layout.html.
To enable the video sharing feature on the front end, you will build out three additional endpoints. These endpoints will allow users to upload videos, return all videos, and return the details of a single video.
This endpoint with receive videos uploaded from the front end that will contain the videos title, description, and the video itself. You should parse the request and store all this information. As with images, the videos should be stored in files with only the filename stored in your database. Respond to uploads with a 200 OK containing a JSON object with the unique id of the new video.
Response (JSON): {"id": str}
This endpoint only needs to handle .mp4 videos.
After a video has been uploaded, be sure your "/public" path will serve these videos. As long as you save videos in the public directory, this will be mostly automatic but don't forget to add the MIME type for .mp4 content.
Testing will only be done with authenticated users. It's up to you if you want to support guest video uploads.
Return a JSON object containing information about all videos that have been uploaded in the following format:
Response (JSON): {"videos": [{"author_id": str, "title": str, "description": str, "video_path": str,"created_at": str "id": str}, ...]}
Where "created_at" should be a timestamp that will be displayed on the front end. You can/should use the datetime package to get a string format for the current time.
Return all information about the video with an id matching {video_id}. The format follows the same structure as the previous path, but with only 1 video object.
Response (JSON): {"video": {"author_id": str, "title": str, "description": str, "video_path": str,"created_at": str "id": str}}
Use ffmpeg to generate 5 possible thumbnail images for each video that is uploaded. These 5 thumbnails should be: The first frame of the video, the frame at 25% of the videos runtime, the frame at 50%, the frame at 75%, and the last frame. Save all 5 frames as images and add them all to your videos data in a "thumbnails" field as an array.
Example field to be added to videos - "thumbnails": [ "public/imgs/thumbnails/67c260f1bb497fca803f6c49_0.jpg", "public/imgs/thumbnails/67c260f1bb497fca803f6c49_1.jpg", "public/imgs/thumbnails/67c260f1bb497fca803f6c49_2.jpg", "public/imgs/thumbnails/67c260f1bb497fca803f6c49_3.jpg", "public/imgs/thumbnails/67c260f1bb497fca803f6c49_4.jpg" ]
Add a second field named "thumbnailURL" to your video that is the selected thumbnail which will default to the first frame of the video.
Example field to be added to videos - "thumbnailURL": "public/imgs/thumbnails/67c260f1bb497fca803f6c49_0.jpg"
Add the following path to render the thumbnail chooser page.
Using this page, users can change their thumbnail for a specific video. When they do, a request will be sent to the following endpoint.
Request body: {"thumbnailURL":"public/imgs/thumbnails/67c260f1bb497fca803f6c49_4.jpg"}
When a request is received at this endpoint, update the "thumbnailURL" field of the video with id {videoId} to the provided value. Respond with a message in JSON object indicating that the update was successful.
Response format (JSON): {"message": str}
When a video is uploaded, re-encode it to HLS with at least 2 different resolutions/bit-rates and host all required files for the video. Add the path to the index file for the video in a new field named "hls_path" to the video.
Example field to be added to videos - "hls_path": "public/videos/2439857234.m3u8"
The single index file should refer to multiple other index files with different bit-rates. If done correctly, you will be able to select the resolution manually on the front end when viewing a video.
Submit all files for your server to Autolab in a .zip file
If you used any external libraries, be sure to add them to your requirements.txt. Autolab will install all dependencies in this file, and no other dependencies, before starting your server.
It is strongly recommended that you download and test your submission after submitting. To do this, download your zip file into a new directory, unzip your zip file, enter the directory where the files were unzipped, run docker compose up, then navigate to localhost:8080 in your browser. This simulates exactly what the TAs will do during grading.
Each objective will be scored on a 0-3 scale as follows:
3 (Complete) | Clearly correct |
2 (Complete) | Mostly correct, but with some minor issues |
1 (Incomplete) | Not all features outlined in this document are functional, but an honest attempt was made to complete the objective |
0.3 (Incomplete) | The objective would earn a 3, but a security risk was found while testing |
0.2 (Incomplete) | The objective would earn a 2, but a security risk was found while testing |
0.1 (Incomplete) | The objective would earn a 1, but a security risk was found while testing |
0 (Incomplete) | No attempt to complete the objective or violation of the assignment (Ex. Using an HTTP library) |
Note that for your final grade there is no difference between a 2 and 3, or a 0 and a 1. The numeric score is meant to give you more feedback on your work.
3 | Objective Complete |
2 | Objective Complete |
1 | Objective Not Complete |
0 | Objective Not Complete |
Autograded objectives are graded on a pass/fail basis with grades of 3.0 or 0.0.
For each objective for which you earned a 0.3 or 0.2, you will still have an opportunity to earn credit for the objective by submitting an essay about the security issue you exposed. These essays must:
Any submission that does not meet all these criteria will be rejected and your objective will remain incomplete.
Due Date: Security essays are due 1-week after grades are released.
Any essay may be subject to an interview with the course staff to verify that you understand the importance of the security issue that you exposed. If an interview is required, you will be contacted by the course staff for scheduling. Decisions of whether or not an interview is required will be made at the discretion of the course staff.
When you don't have to write an essay: