MP 5: HTTP Client-Server

Due Date: Completed and turned in via git before October 11, 2022 at 11:59pm
Points: MP 5 is worth 40 points
Semester-Long Details: Programming Environment and MP Policy

Overview

The Hyper-Text Transfer Protocol (HTTP) – defined in RFC 2616 – is the fundamental protocol for transferring data on the World Wide Web (i.e. Internet). It is an application-level protocol which through the use of its request methods, error codes and headers is widely used for various different applications.

HTTP is a Client-Server protocol, where the Client sends a request to the Server which processes the request and returns appropriate response. In this MP, you will:

  • Dive into the HTTP protocol to understand the technical design of HTTP request and response packets.
  • Write an HTTP server that responds to HTTP requests (from web browsers like Chrome, command line utilities like curl, and anything else that “speaks” HTTP).
  • Parse HTTP headers into key-value pairs.
  • Have a foundational understanding of how libraries parse HTTP requests for building web services in the future.

MP Overview Session

An MP Overview will be held on Thursday, Oct. 6 at 05:00pm in 1304 SC.

Initial Files

In your CS 340 directory, merge the initial starting files with the following commands:

git fetch release
git merge release/mp5 --allow-unrelated-histories -m "Merging initial files"

Implementation

We’ve split up the implementation into three parts:

  1. Parsing an HTTP request packet for the request and headers (as a string, without needing to worry about the socket code). This will include all of http.c except httprequest_read.
  2. Reading an HTTP request over a socket (the httprequest_read function).
  3. Building a web server using your httprequest code that can be used with Chrome or other web browsers.

Modifiable Files

In your solution, you must only modify the following files. Modifications of other files may break things:

  • http.c
  • http.h
  • server.c

Part 1: Parsing an HTTP request

Implement the API provided in http.{c,h}, excluding httprequest_read. The main function in this part is httprequest_parse_headers, which must populate the provided HTTPRequest *req data structure with the contents from char *buffer.

The other functions include:

  • httprequest_get_action, to get the action verb (ex: GET) from the HTTP request,
  • httprequest_get_path, to get the path (ex: /) from the HTTP request,
  • httprequest_get_header, to get a value for a specific header (ex: Host -> localhost), and
  • httprequest_destroy, to free any memory stored by an HTTPRequest struct

While working on this part:

  • You can (and will have to) add to the HTTPRequest struct in http.h (possibly also creating other structs in there, too).
  • You must populate the action, path, version fields in HTTPRequest while parsing the packet as part of httprequest_parse_headers.

Testing Part 1

We have provided a simple test suite to test the correctness of your parsing logic:

  • In your terminal, type make test to compile the test suite.
  • Run ./test "[part=1]" to run the tests that have been tagged with [part=1] (covering this portion of the MP).

Part 2: Reading an HTTP request from a socket

Complete httprequest_read. This function is called with a file descriptor where you must read to read the contents of the request.

  • You should use your httprequest_parse_headers to parse the headers of the request.
  • The Content-Length header is a special HTTP header that will help you out to read the payload of the request.

Testing Part 2

We have provided a simple test suite to test the correctness of your parsing logic. The first five tests are identical to part 1, except that they’re now delivered via the sockfd file descriptor instead of as a string. The final test tests if your code can read the payload of a requests.

  • In your terminal, type make test to compile the test suite.
  • Run ./test "[part=2]" to run the tests that have been tagged with [part=2] (covering this portion of the MP).

Part 3: Building a Web Server

In Lecture, you saw a simple socket-based web server. We’ve extended the server to launch a new thread for each incoming connection (client_thread).

In the client_thread function in server.c:

  • You must read an HTTP request from the fd (use your httprequest_read).
  • You must create an HTTP response to respond to the request.
    • If the requested path is /, you should process that request as if the path is /index.html.
    • If the file does not exist in your static directory (excluding the /), you must respond with a 404 Not Found response.
    • If the file requested does exist, you will respond with a 200 OK packet and:
      • Return the contents of the file as the payload,
      • If the file name ends in .png, the Content-Type header must be set to image/png.
      • If the file name ends in .html, the Content-Type header must be set to text/html.
  • You can (and probably should, to make it easier for you) close(fd) after responding to the request. This will ensure the browser opens a new socket (and you will have a new thread) when making another request. (You can continue to re-use this same socket and keep the connection alive, but this is not required.)

Testing Part 3

You will test Part 3 using your favorite web browser.

If you can see the pages and images, you just made your first static web server! 🎉

There are a few tests ./test "[part=3]" to verify it works by code – but that’s not really the point of this part.

Memory Correctness

For full credit, your MP must run “valgrind clean”. This means that you must:

  • Compile all test cases on the command line using make test,
  • Run all test cases using valgrind --leak-check=full --show-leak-kinds=all ./test,
  • This must report the following output: All heap blocks were freed -- no leaks are possible

macOS Specific Information

Sadly, valgrind does not work on macOS. However, it does work in Docker running on a Mac:
# Build a light-weight docker:
docker build -t cs340  .

# Run make clean, make, and run valgrind:
docker run --rm -it -v `pwd`:/mp5 cs340 "make clean"
docker run --rm -it -v `pwd`:/mp5 cs340 make
docker run --rm -it -v `pwd`:/mp5 cs340 "valgrind ./test"

Submission

You will submit via git using the usual commands:

git add -u
git commit -m "MP5 submission"
git push origin master

You can verify your code was successfully submitted by viewing your git repo on github.com. :)