Project 6: Web Server Part 1

Write a command-line program called webserver. This program will listen for http requests on port 8888. To achieve this, set up a TcpListener that is bound to localhost:8888.

Step 1: Listen

When your server receives a connection, it should print the IP address of the peer that contacted it. Test it using your webget program as follows:

webget http://localhost:8888/test

Since webget is running on the same machine, you can send the message to localhost and it will look for a server on the local machine. Since the server is not actually going to send any data, the filename doesn’t matter. Using http avoids a panic when it does not receive a TLS secured connection.

Note that you can also make this request using your web browser. Just paste http://localhost:8888/test into its URL bar.

Step 2: Display Message

Refine your program as follows:

  • Whenever it receives a connection, the program should spawn a new thread to handle the connection.
  • In this new thread, it should await a message from the client. Create a mutable String to store the accumulated message. Then, within a loop:
    • Use read() with a 500-byte buffer.
    • Use std::str::from_utf8 to convert the buffer into a &str.
    • Use the push_str() method to append it to the accumulated message.
    • As the client is awaiting a reply, it will not close the connection. Even after it finishes its transmission, the read() will still block, waiting for more data. Since the http protocol specifies that a client’s message ends with the character sequence \r\n\r\n, once the accumulated message contains that sequence, the loop can end. As some clients end with \n\n, the http specification allows servers to end with that sequence too.
  • It should then print the message it received.

In the above example, it would print somemthing akin to the following:

client IP address: 127.0.0.1:60632                                                                                      
Read 54 bytes from 127.0.0.1:60632                       
GET /test HTTP/1.1                                     
Host: localhost                                                                                                         
Connection: Close

Step 3: Respond

Once the client message is received, the server should reply to the client with the following:

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: [Number of bytes in HTML message]

<html>
<body>
<h1>Message received</h1>
Requested file: [Insert file request element of the GET here]<br>
</body>
</html>

In our example from earlier, the response would be:

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 82

<html>
<body>
<h1>Message received</h1>
Requested file: /test<br>
</body>
</html>

I recommend testing your server at this stage using your web browser, so you can see the result of rendering the HTML.

Step 4: Validate File Requests

In principle, a web server can send any file on the machine it is running to anyone on the Internet. To prevent this, we need to validate the file requests. The requested file will be assumed to be relative to the current working directory for the executing server. The server should determine the absolute path of the resulting file and guarantee that it is subordinate to the server’s current working directory.

The PathBuf data type has several methods that you may find helpful in validating the file request.

If the file does not exist, send a message with the header HTTP/1.1 404 Not Found. If the file exists but it violates our security policy, send a message with the header HTTP/1.1 403 Forbidden.

In practice, if the file requested by the client is a directory, it will return the file index.html from within that directory. For this assignment, it is acceptable to return HTTP/1.1 404 Not Found if the client requests a directory.

Step 5: Counter

Maintain two counters:

  • Total number of requests.
  • Total number of valid requests.

Whenever the server answers a client request, it will add one to the total-request counter. If the client request is valid, it will also add one to the valid-request counter.

You can store these as two separate variables, or you can aggregate them into a single struct. Because each client thread will need to access them, they will need to be wrapped with an Arc and a Mutex.

The server should print the counts to the command line every time it receives a request.

Step 6: Send files

If the request is valid, and the request is for a file, the server should send back the contents of the file. You may assume that all requested files are text files.

Submissions

Assessment

  • Partial: Steps 1-4 complete.
  • Complete: All six steps complete.

Acknowledgement

This assignment was adapted from materials developed by David Evans at the University of Virginia.