Lab: Simple Web Server

CSC 364 - Computer Networks - Weinman



Summary:
We implement a very simple web server in Python.
Assigned:
Friday 14 February
Due:
11:59 PM Thursday 20 February
Objectives:
 
Background
 
Collaboration:
You will complete this lab in pairs assigned by the instructor.

Introduction

In this lab you will implement and test a very simple web server using the hypertext transport protocol (HTTP), a text-based application-layer protocol. The basic outline is as follows. Your server will establish a listening socket and wait for connections in an infinite loop (so that it can serve as many as come in while it is waiting). It will then need to accept a connection, then receive and parse the HTTP request. If the request is valid, the server should read the file from the file system and send the file contents (in small chunks, say 2 kilobytes) to the requestor preceded by the appropriate response header lines. If the request is not valid, a 400 error code should be sent instead. If there is some other error reading the file, a 404 error code should be sent.

Implementation

Create a file called wwwserv.py. The port your server listens on should be a clearly identifiable variable; its value should be greater than 5000, where users have rights to establish port listeners. You will want to make it a variable because if you kill the server, the port will remain "in use" for a short while to ensure that no stray requests for the port filter in.
After creating, binding, and listening on the socket, your server body should act like the following:
while True
    Establish connection with a client
    Try
        Read a request
        If the request is invalid
            Send a 400 status code, close the connection, and continue
        Open the requested file (strip the leading '/' for a relative path)
        Send a single response header to the client
        Send the file to the client in small chunks
        Close the file and connection
    Handle IO Error (sending a 404 status code and closing the connection)
In lieu of a proper logging system, your server should report simple messages to the console as it takes these steps.

Testing

Place a simple HTML file, such as example.html in the same directory as your wwwserv.py. Requests to your web server for files will be relative to the server's directory. For example, if you used port 8765 and were running your program on the machine rosser, you can request the file via a web browser with the url http://rosser.cs.grinnell.edu:8765/example.html or using telnet(1), as demonstrated in Kurose and Ross. You can even make such a telnet-based request to a "real" web server, as in the following transcript, which you can try yourself.
weinman@laplace:~$ telnet www.cs.grinnell.edu 80
Trying 132.161.196.27...
Connected to baran.cs.grinnell.edu.
Escape character is ''.
GET /weinman/courses/CSC364/2014S/labs/attribution.html
<!DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Transitional//EN
           DTD/xhtml1-transitional.dtd>
<html xmlns=http://www.w3.org/1999/xhtml>
[ ... Lots of intermediate stuff snipped out ...]
</html>
Connection closed by foreign host.
weinman@laplace:~$ 

Questions

  1. What happens if you try to make a second connection to your server while it is still handling another connection?
    For example, make one telnet connection to your server, but don't issue a request yet. Make another telnet connection. If it succeeds, issue a GET request. Then go back to your original connection and issue a GET request.
    What happened? Why?
  2. Note that we use the program's directory to implicitly specify where the web server should look for files. Does this guarantee that a malicious web client cannot access files outside of the web server's directory? If so, explain why. If not, give an example of a request a client could make to obtain an unauthorized file.

What to turn in

Python Hints

Conditionals and looping

if (foo == 1 and 
    bar != ") or 
    baz == "bish":
    print "Success!n"
while 1:
    print "Are we there yet?n"

Simple string operations

myString = "foo bar baz"
myStringSub = myString[2:4] # myStringSub will be "o b"
myStringSub2 = myString[5:] # myStringSub2 will be "ar baz"

Splitting a string on whitespace

myString = "foo bar baz"
myStringParts = myString.split()
# myStringParts[0] is "foo", myStringParts[1] is "bar" and myStringParts[2] is "baz"
# len(myStringParts) is 3

Reading files

try:
    fileHandle = open( "myFilename" ) # Open file and return a file handle (object)
    chunk = fileHandle.read(64) # Read 64 bytes from the file (advancing file pointer)
    if chunk == '':  # Test whether EOF has been reached (no string data from read method)
        print "No more file!"
    fileHandle.close() # Close the file
except IOError:
    print "Something went wrong (like a file not found or bad permissions)!"

Acknowledgments

This lab was inspired in part by CSC 213, Fall 2006 : Lab 10 : A Simple Web Client and Server, by Janet Davis, as well as Socket Programming Assignment 1: Web Server, by James F. Kurose and Keith W. Ross.