Homework 1 - HTTP




Introduction


In this assignment you will build the foundation of the server that you will develop throughout the semester. All homework assignments will continue to add features to the same web app. A front end of the app is provided for you, which you can clone using the link below.

GitHub Repository link: https://github.com/CSE-312/CSE312-Server

This front end is designed to send requests to your server for most of the homework objectives throughout the semester and your goal will be to implement the corresponding endpoints on the back end. This front end is offered to make it easier to complete, and test, your server. You are also free to modify this front end in any way you'd like, or design and build your own, as long as the features for each objective are clear and accessible when visiting your front end.


Deviations


In addition to modifying the front end, or building your own, you are free to deviate from the specific details of each objective as long as you still implement the features for each objective. This offers you flexibility to implement your app the way you'd like, while still learning the concepts required for each objective. There is no limit to the amount you are allowed to deviate as long as the concepts are still covered.

If you choose to deviate from the stated objectives, you must justify how this deviation still covers the concepts required for the objective.


Objective 1: Hosting Static Files


Goals:

  • Host all the static files needed to load your homepage
  • Utilize separate modules/classes/files to organization your server code

Concepts Covered:

  • Project organization
  • HTTP Requests
  • HTTP Responses
  • Routing Requests
  • MIME Types
  • UTF-8
  • 404 Not Found
  • Rendering HTML Templates

If this objective, you will build the foundation of your server by hosting all the static files needed to load your homepage. In the provided front end, this involves hosting every file in the public directory with the proper MIME type, and rending 2 HTML templates.

Project Organization

You are required to have some thoughtful organization of your server code. As with all objectives, you may deviate from the structure below as long as your architecture is clear and maintainable. If you deviate from this structure and it is not clear, it will be more difficult for the course staff to help you if you need assistance with your code and you may not be able to get the help you need later in the semester (In addition to losing credit for this objective).


The Request Class

Create a class that will parse HTTP requests and set appropriate instance variables. This class should take a raw request as bytes and set instance variables containing the request information. These instance variables must include:

  • method
  • path
  • headers
  • body
In later objectives, you may want to add more instance variables. For example, on the next objective you may find it useful to add cookies to your request parsing.

Tips:

  • Headers may contain the character ':'
  • The body of a request may contain the sequence "\r\n\r\n"
  • The body may contain data that is not text. If you treat the body as text, you will have to update this class in HW3 when you handle image and video uploads
  • HTTP headers are case-insensitive so "Content-Length" and "content-length" should be treated the same

The Response Class

Create a class that will build HTTP responses with methods to populate the data of the response. This class must include ways to:

  • Set the status code and message
  • Add headers
  • Add the body of the response as either text, JSON, or binary
  • Convert the object into bytes to send over the TCP connection
  • Set the Content-Length header to the correct value
  • Always set the X-Content-Type-Options: nosniff header
As with the Request class, you may way to add more functionality to this class in later objectives, including cookies.

Tips:

  • Choosing thoughtful defaults can save you time. For example, if the status code and message are never set, default to 200 OK since it will be the most common status for your responses
  • When adding cookie functionality for the next objective, note that you must handle multiple Set-Cookie headers in the same response. By handling cookies as a special case instead of as a raw header, it can be easier to add this feature
  • When sending JSON, the Content-Type header must be set to "application/json"

The Router Class

Create a class that will route different requests to the appropriate function. This class must include ways to:

  • Add routes to the router containing the method, path, and function to call for that route
  • Route requests that do not have to match the exact path for the route (e.g. Host all files in the public directory for any path that starts with "/public")
  • When a request is read over the TCP connection, route the requests to the appropriate function using the routes that have been added to the router
  • Send a 404 Not Found response if no matching route is found for a request

Tips:

  • Your method that routes requests can take a Request object using your request class
  • The method and path must both match for a route to be considered a match
  • If multiple routes match the request, you must decide how to decide which route to use. The typical way to do this is to choose the route that was added first. Then to route a request, iterate through all routes in the order in which tehy were added until you find a match. Of no match is found, send your 404

Hosting Static Files

This is the first task where you'll start adding functionality to your server (The three classes you've written will make it easier for you develop your server throughout the semester). Add a route to your router that will host all files in the public directory.

To accomplish this task, you should add a route that will match any path starting with "/public", then extract the file path from the rest of the path and serve that files contents in the response. For example, if you receive a request for the path "/public/imgs/dog.jpg", you should read the file "/public/imgs/dog.jpg" and send it in the body of a response. You must use the three support classes you've created to accomplish this.

All files must be served with the correct MIME type. You may use the file extensions to determine these MIME types. For this HW, you only need to handle the file extensions that appear in the provided public directory. (Note: .ico is an image with MIME type "image/x-icon").


In addition to hosting all the files in the public directory, you will add paths for each of the pages of the app. For each of these pages, you will need to render an HTML template (Don't panic, this comes down to reading 2 files and doing 1 find and replace). There is an HTML template in "public/layout/layout.html". This template contains all the structure of the app including menus, navigation, metadata, imports, etc. This template has one placeholder that is exactly the string "{{content}}". Each html file for a page will be inserted at this placeholder so the structure does not have to be copied into every file. This also makes it easier to make changes to every page on the app since only template.html has to be changed to affect all pages. To render a page, for example "index.html", you will read both the layout.html and index.html files, then replace "{{content}}" from the template with all the content of index.html to render the full page. This rendered page is what you'll send to the client when they request index.html.

Add the following special paths that require rendering HTML using layout.html as described above:

  • "/" - Render index.html
  • "/chat" - Render chat.html

At this point, you can run your server and visit "http://localhost:<your_port_number>" in your browser to see the provided front end.

Security: The X-Content-Type-Options: nosniff header must be set on all responses (Please double/triple check this header for the exact spelling and syntax. If you are off by 1 character, the browser will not disable MIME type sniffing which renders your header useless and can be a security issue). It is recommended that you add this header in your Response class so you'll never forget it

UTF-8: Some files contain emojis that will be displayed when the page loads. These characters must display properly.

404: If a request is received for any path that should not serve content, return a 404 response with a message saying that the content was not found. This can be a plain text message.


Objective 2: Guest Chat


Goals:

  • Add a chat feature to your server that does not require authentication
  • Ensure each message posted by the same user has the same username
  • Use a PostgreSQL database to store chat messages
  • Implement ways for users to create, update, and delete their own messages

Concepts Covered:

  • POST/PATCH/DELETE requests
  • Cookies
  • PostgreSQL

The front end contains a chat feature that will send specific requests to your server. To enable this feature, implement the following end points. Note that you will need to use a database to complete these end points. All functionality must persist through a server restart, and you must use a PostgreSQL database.

To test your app with a database, you can either install and run PostgreSQL on your device, or setup Docker to run a PostgreSQL container (Which is the next objective so you have to do this anyway).


Create Chat Message (`POST /api/chats`)

Creates a new chat message. The frontend will send a POST request to your backend at the route `/api/chats`. Listed below is the format expected. The entirety of the body of the request will be a JSON string

Request (JSON): {"content": string}

Response: 200 OK with a message of your choosing. A simple text response of "message sent" is fine

You may assume the request is properly formatted

When a message is sent, you should create a unique id for the message and store it in your database along with the author of the post. See the spec for the GET request to see the expected format of a chat message.


Get Chat Messages (`GET /api/chats`)

Retrieves all chat messages. The frontend will send a GET request to your backend at the route `/api/chats`. Listed below is the format expected.

Response (JSON): {"messages": [{"author": string, "id": string, "content": string, "updated": boolean}, ...]}

The `updated` parameter that is sent back in the list of messages represents if the message has ever been updated. If it has it must be set to true. When a message is first created, this should be set to false.

If the same user creates multiple posts, all posts must have the same author. This should be tracked using a cookie that is set and tracked by your server. This cookie must be set when they send their first message

Since we don't have user accounts yet, you can choose random author names for users for now. The names must be different for different users, but can be randomly generated. You are allowed to use a package to generate ids and tokens if you'd like.


Update Chat Message (`PATCH /api/chats/{id}`)

Updates an existing message with new content. The frontend will send a PATCH request to your backend at the route `/api/chats/{id}`. Listed below is the format expected.

Request (JSON): {"content": string}

Errors:

  • **403 Forbidden**: This error is for when the user lacks permission (Users can only update their own messages. Your server needs to check for this). You may choose the message for this error

After a message has been updated using this endpoint, its "updated" field must be set to true


Delete Chat Message (`DELETE /api/chats/{id}`)

Deletes an existing message from chat history. The frontend will send a DELETE request to your backend at the route `/api/chats/{id}`

Errors:

  • **403 Forbidden**: This error is for when the user lacks permission (can only delete own messages). You may choose the message for this error

After a message is deleted by its author, it must never be sent by the GET endpoint again.

Security: You must escape any HTML in the users' messages. Since your users can submit any text they want, a malicious user could submit HTML tags that attack other users. You cannot allow this. You must escape any submitted HTML so it displays as plain text instead of being rendered by the browser.


1 - Hosting static files 2 - Guest chat + API + Cookies 3 - Docker 4 - Keep-Alive 5 - Caching

Objective 3: Docker and docker-compose


Goals:

  • Setup Docker and docker-compose to compile and run your server with a single "docker compose up" command
  • Run your server and database in separate containers

Concepts Covered:

  • Docker
  • docker-compose
  • Multiple Containers for a Single Application

Setup your app to run in a docker container such that running "docker compose up" from the root directory of your project will start your server and database in separate containers with your app available on localhost port 8080. To accomplish this, you will need to write a Dockerfile that will both compile and run your server code. You will also need a docker-compose.yml file that will start your server and database in separate containers.

Tips:

  • While debugging, you should run "docker compose up --build --force-recreate" to ensure that the containers are fully rebuilt. If you do not see your new changes, this is usually the issue
  • You may need to regularly delete your volumes to clear your database, and/or manually delete your database container
  • If you are running Windows, install WSL2 and run Docker from Ubuntu (Note: Windows will make your life as a developer more difficult. Consider switching to Linux if you are frustrated by the issues)

Objective 4: Keep-Alive


Goals:

  • Implement Keep-Alive support for your server
  • Reuse TCP connections for multiple requests

Concepts Covered:

  • Keep-Alive
  • Concurrent connections
  • Efficient TCP connection management

Implement Keep-Alive support for your server such that a client is able to send multiple requests, and receive multiple responses, over the same TCP connection. You must properly handle these multiple requests, especially requests that are received in quick succession.


Objective 5: Caching


Goals:

  • Add caching to your server to improve performance
  • Use both ETag and timeouts to cache responses

Concepts Covered:

  • HTTP Caching
  • ETag
  • Timeouts (Max-Age or Last-Modified)

The goal for this objective is to set the appropriate headers to add caching support to your server. Note that you will not cache anything, but you are setting headers that will inform the browser how to cache your responses.

You must implement both ETag and timeouts to cache responses. For timeouts you may choose to use Max-Age or Last-Modified. In either case, you should set a very short timeout (e.g. 10 seconds). This will make testing much faster and simpler. If a request is received for content that is cached, you should return a 304 Not Modified response.

All of you static content must be cached. This includes every file in the public directory, and the rendered HTML templates.


Submission


Submit all files for your server to Autolab in a .zip file

Complete the homework submission form after submitting your zip file to Autolab: Homework Submission Form

If you do not both submit your zip file to Autolab and complete the homework submission form before the due date, your submission will not be graded. Do not be one of the students who will get a 0 for not submitting the form after completing the assignment.

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 --build --force-recreate", then navigate to localhost:8080 in your browser and test all of your features. This simulates exactly what the TAs will do during grading. If you notice that your database is still populated from your prior tests, you may also want to run "docker volume prune" to clear your database and all other volumes on your machine.

If you ignore this recommendation, don't be surprised when you do not earn credit even if your code functions properly on your laptop.


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 attempt was made to complete the objective
0 (Incomplete) No attempt to complete the objective or violation of the assignment (Ex. Using an HTTP library)
0 (Security Risk) If any security risk is found while testing, all objectives will be scored 0 unless a proper security essay is submitted

Note that for your final grade there is no difference between a 2 and 3, or a 0 and a 1.

3 Objective Complete
2 Objective Complete
1 Objective Not Complete
0 Objective Not Complete



Security Essay


If a security risk is found in your submission, you will be assigned a 0 for all 5 objectives. However, you will still have an opportunity to earn credit for the objectives by submitting an essay about the security issue you exposed. These essays must:

  • Be at least 1000 words in length
  • Explain the security issue from your submission with specific details about your code
  • Describe how you fixed the issue in your submission with specific details about the code you changed
  • Explain why this security issue is a concern and the damage that could be done if you exposed this issue in production code with live users

If your submission contains multiple security risks, you may have to write multiple essays to recover your homework grade

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.