Project 4a: Scalable Web Server
Make sure to read the general instructions for the project here.
Security: It might be worthwhile to be a little careful with security during this project. Probably not a big deal, but a good way to help with this is to make sure to run the web server out of a special directory with only a few files in it (e.g., a subdirectory of your build, or something you create specially in /tmp), and further to disallow any path names that have a .. in them (which would allow people to go up a level or more in the directory hierarchy and thus explore any files you have access to). Minimally, don't leave your web server running for a long time.
In this assignment, you will be developing a real, working web server. To simplify this project, we are providing you with the code for a very basic web server. This basic web server operates with only a single thread; it will be your job to make the web server multi-threaded so that it is more efficient.
Before describing what you will be implementing in this project, we will provide a very brief overview of how a simple web server works and the HTTP protocol. Our goal in providing you with a basic web server is that you should be shielded from all of the details of network connections and the HTTP protocol. The code that we give you already handles everything that we describe in this section. If you are really interested in the full details of the HTTP protocol, you can read the specification, but we do not recommend this for this project.
Most web browsers and web servers interact using a text-based protocol called HTTP (Hypertext Transfer Protocol). A web browser opens an Internet connection to a web server and requests some content with HTTP. The web server responds with the requested content and closes the connection. The browser reads the content and displays it on the screen.
Each piece of content on the server is associated with a file. If a client
requests a specific disk file, then this is referred to as static content. If
a client requests that a executable file be run and its output returned, then
this is dynamic content. Each file has a unique name known as a URL (Universal
Resource Locator). For example, the URL
An HTTP request (from the web browser to the server) consists of a request
line, followed by zero or more request headers, and finally an empty text
line. A request line has the form:
An HTTP response (from the server to the browser) is similar; it consists
of a response line, zero or more response headers, an empty text line, and
finally the interesting part, the response body. A response line has the form
Again, you don't need to know this information about HTTP unless you want to understand the details of the code we have given you. You will not need to modify any of the procedures in the web server that deal with the HTTP protocol or network connections.
Basic Web Server
The code for the web server is available from
When you run this basic web server, you need to specify the port number
that it will listen on; you should specify port numbers that are greater than
about 2000 to avoid active ports. When you then connect your web browser to
this server, make sure that you specify this same port. For example, assume
that you are running on galapagos-21.cs and use port number 2003; copy your
favorite html file to the directory that you start the web server from. Then,
to view this file from a web browser (running on the same or a different
machine), use the url:
The web server that we are providing you is only about 200 lines of C code, plus some helper functions. To keep the code short and understandable, we are providing you with the absolute minimum for a web server. For example, the web server does not handle any HTTP requests other than GET, understands only a few content types, and supports only the QUERY_STRING environment variable for CGI programs. This web server is also not very robust; for example, if a web client closes its connection to the server, it may crash. We do not expect you to fix these problems!
The helper functions are simply wrappers for system calls that check the error codes of those system codes and immediately terminate if an error occurs. One should always check error codes! Ideally, a program should gracefully handle each error. However, many programmers don't like to do it because they believe that it makes their code less readable. At the very least, however, you should write your code so that your programs crash when calls fail. This can be done cleanly with wrapper functions, as used by the server source code. We expect that you will use these wrapper functions (and add your own for the new system routines that you call).
Overview: New Functionality
In this project, you will make the web server multi-threaded. You will also be modifying how the web server is invoked so that it can handle new input parameters (e.g., the number of threads to create).
The basic web server that we provided has a single thread of control. Single-threaded web servers suffer from a fundamental performance problem in that only a single HTTP request can be serviced at a time. Thus, every other client that is accessing this web server must wait until the current http request has finished; this is especially a problem if the current http request is a long-running CGI program or is resident only on disk (i.e., is not in memory). Thus, the most important extension that you will be adding is to make the basic web server multi-threaded.
The simplest approach to building a multi-threaded server is to spawn a new thread for every new http request. The OS will then schedule these threads according to its own policy. The advantage of creating these threads is that now short requests will not need to wait for a long request to complete; further, when one thread is blocked (i.e., waiting for disk I/O to finish) the other threads can continue to handle other requests. However, the drawback of the one-thread-per-request approach is that the web server pays the overhead of creating a new thread on every request.
Therefore, the generally preferred approach for a multi-threaded server is to create a fixed-size pool of worker threads when the web server is first started. With the pool-of-threads approach, each thread is blocked until there is an http request for it to handle. Therefore, if there are more worker threads than active requests, then some of the threads will be blocked, waiting for new http requests to arrive; if there are more requests than worker threads, then those requests will need to be buffered until there is a ready thread.
In your implementation, you must have a master thread that begins by creating a pool of worker threads, the number of which is specified on the command line. Your master thread is then responsible for accepting new http connections over the network and placing the descriptor for this connection into a fixed-size buffer; in your basic implementation, the master thread should not read from this connection. The number of elements in the buffer is also specified on the command line. Note that the existing web server has a single thread that accepts a connection and then immediately handles the connection; in your web server, this thread should place the connection descriptor into a fixed-size buffer and return to accepting more connections.
Each worker thread is able to handle both static and dynamic requests. A worker thread wakes when there is an http request in the queue. Once the worker thread wakes, it performs the read on the network descriptor, obtains the specified content (by either reading the static file or executing the CGI process), and then returns the content to the client by writing to the descriptor. The worker thread then waits for another http request.
Note that the master thread and the worker threads are in a
producer-consumer relationship and require that their accesses to the
shared buffer be synchronized. Specifically, the master thread must
block and wait if the buffer is full; a worker thread must wait if the
buffer is empty. In this project, you are required to
Side note: Do not be confused by the fact that the basic web server we provide forks a new process for each CGI process that it runs. Although, in a very limited sense, the web server does use multiple processes, it never handles more than a single request at a time; the parent process in the web server explicitly waits for the child CGI process to complete before continuing and accepting more http requests. When making your server multi-threaded, you should not modify this section of the code.
Extra CreditTo get extra points, you can implement a shortest-job first approximation when deciding which HTTP requests to service first. Use file size as a proxy for job length. In order to implement this policy, you'll probably want to check the file size from the master (producer) thread. Thus, you may want to refactor
Your C program must be invoked exactly as follows:
prompt> server [portnum] [threads] [buffers]
The command line arguments to your web server are to be interpreted as follows.
For example, if you run your program as:
server 5003 8 16
then your web server will listen to port 5003, create 8 worker threads for handling http requests, and allocate 16 buffers for connections that are currently in progress (or waiting).
We recommend understanding how the code that we gave you works. All
of the code is available from
We also provide you with a sample Makefile that creates server, client, and output.cgi. You can type make to create all of these programs. You can type make clean to remove the object files and the executables. You can type make server to create just the server program, etc. As you create new files, you will need to add them to the Makefile.
The best way to learn about the code is to compile it and run it. Run the server we gave you with your preferred web browser (note, however, that some browsers may send more complex HTTP requests that crash the server). You should also run this server with the client code we gave you. You can even have the client code we gave you contact any other server that speaks HTTP. Make small changes to the server code (e.g., have it print out more debugging information) to see if you understand how it works.
We anticipate that you will find the following routines useful for creating
and synchronizing threads: