CPSC 441, Networking and Distributed Processing, Fall 2004
Lab 4: Network Programming II


IN THIS LAB, you should continue the development of a web server. You should arrive at lab with a simple working web server. Today, you will refactor the code in your server to turn it into a threaded server and to begin handling headers in the client's request. (If you don't like your own server, I have a sample solution that you can use instead as the basis for further work.)

You can work on the Web server in a group, if you like. A group project should be a little more ambition than an individual project. Your Web server is due in two weeks, on Friday, October 8. We might continue work on the Web server after that time, to make it more robust and full-featured.

Remember that there will be a test next Friday, instead of a lab.


Turning in Your Chat Code

You should be turning in your Chat client and server today. I would like you to put the Java source code for this project in a place where I can get at it, so that I can compile and run it for testing purpose. Your home directory is now on an AFS file system. AFS is a secure network file system. It does not use the standard UNIX permission bits (as set with the chmod command). Instead, access to files is controlled using access control lists (ACLs). You can give me permission to read the files in a directory by setting an appropriate ACL on that directory.

Create a directory named cs441 in your home directory. (If you would like to use another name, let me know.) Working in your home directory, give the command

      fs setacl cs441 -acl eck rl

This will give me read and list permission on this directory. I will be able to list the files in the directory and I will be able to read (or copy) the files. Put your client and server code in this directory.

To see the ACLs on the directory, by the way, use the command

      fs listacl cs441

Testing My Server

My web server is named "WebServ". You can find the source file, WebServ.java, and the compiled program, WebServ.class, in the directory /classes/f04/cs441. This server serves up documents from a directory named documents that is in the same directory as the program. I've provided a copy of my on-line Java textbook as an example. To try out the server, cd to the directory /classes/f04/cs441 and run it with the command "java WebServ". The log will be written to the window where you run the server, so you can see what is going on. Use a Web browser to access http://localhost:8888/index.html. Try navigating to some pages that contain java applets and/or images such as the Exercises in Chapter 6 or Section 6 of Chapter 1. Try requesting a file that does not exist. Watch what happens to the log.

My solution generally follows the example in Section 2.9 of the textbook. However, I have made some of the changes I mentioned in class, plus a few others that I didn't mention. For example: The server will send certain error responses to the client, such as "404 Not Found." If the client requests a directory, the server will look for a file named "index.html" in that directory and return that instead. I wrote a separate function to find the content type of file, to make it easy to expand the list of supported types. I wrote a function for sending a file to the client in fairly small chunks. (This is more reasonable than reading the whole potentially-very-large file into memory in one big chunk.) And, of course, errors are caught so that they do not crash the server.


Threads

Decide whose simple Web server you want to use as a basis for further work. Your first goal is to modify the server to use threads. This will allow it to service several clients at the same time. To do this, you have to move the code for handling the client into a thread. Instead of handling the connection itself, the server will create a thread to handle the connection. Our first implementation of threads will be defective, since it is possible that a badly behaved client can tie up a thread forever. We will discuss in class how to fix this problem

To make a thread, you can create a sub-class of the Thread class. The subclass must define a method public void run(). This is the method that will be executed when the thread is run. You should put all the code for handling the connection in this method. Any objects that the thread will need in order to handle the connection can be passed to the thread in its constructor. To start a thread running, you just have to call its start() method. (Do not call the run method directly; the start() method will do that for you after creating a new tread of execution where the run() method will actually run.) The thread will continue to exist until its run() method terminates.

The connection-handling thread will have to write messages to the log file. This file will be shared by several threads. Whenever a resource is shared by several threads, access to that resource must be synchronized. Otherwise, it's possible that a log message written by one thread might be interrupted by a log message written by another thread. It's a good idea to create an object to represent the log and let this class be responsible for writing messages to the log. I suggest creating a class named Logger for this purpose. The Logger object should be passed to the connection handler's constructor.

So, to begin, you will need two classes. These can either be defined in separate files, or they can be added as nested classes inside your web-server class. If you nest them in the web-server class, you should declare them as "public static class" rather than simply "public class". Here's some code that you can use or imitate:


            /**
             *  Implements a thread that handles one client.
             *  The constructor is handed a newly connected
             *  socket.  It should do all the work of reading
             *  the request from this client and sending a
             *  response.  It is responsible for all logging
             *  of information about the request.  The socket 
             *  should be closed before the thread ends.
             */
            public class RequestHandler extends Thread {
            
               Socket socket;
               Logger log;
               
               public RequestHandler(Socket socket,
                                     Logger log) {
                  this.socket = socket;
                  this.log = log;
               }
            
               public void run() {
                  // CODE FOR HANDLING THE REQUEST GOES HERE
               }
            
            }


            /**
             *  Provide a synchronized logging capability
             *  that can be used by multiple threads.  Log
             *  messages are written by calling the put()
             *  method (which does NOT add an end-of-line).
             */
            public class Logger {
            
               PrintWriter out;
               
               public Logger(PrintWriter out) {
                  this.out = out;
               }
               
               synchronized void put(String message) {
                  out.print(message);
                  out.flush();
               }
            
            }

Once you have these classes, you need to move the connection-handling code from the current handleConnection() method into the RequestHandler class. You need a Logger object to represent the log file; this can be done in the main() routine. Instead of handling a client connection itself, the web server class should create a RequestHandler to do it. This can done with the following code:

             RequestHandler handler = new RequestHandle(socket,logger);
             handler.start();

There are several ways to organize the code. Hopefully, you can get this done and test it without too much trouble.


Headers

Your web server probably reads just the first two words of the client's request. But the client also sends headers along with the request. You should modify your program so that it reads all the headers from the client's request.

Each header occupies one line. The line has the form "<header-name>: <header-value>". There are no spaces in the header-name, but there can be spaces in the header-value. The end of the headers is marked by a blank line (or possibly by the client closing the connection), so you should continue to read headers until you read a null value or a blank line. (If you follow the textbook, you might be tempted to use a StringTokenizer to parse each line. I think it's easier to use the indexOf() method in the String class to locate the ':' in the input, and then use substring() methods to extract the header-name and header-value.)

The easiest way to process the headers is to read them all and save their values in a HashMap. Then you can look up the headers that you are interested in later, when you want to process them. Recall that the class java.util.HashMap represents a collection of key/value pairs. It has methods put(key,value) to add items to the collection and get(key) to retrieve the value associated with a given key. In this case, the keys will be header-names (possibly converted to lower case), and the values will be header values.

The get started on header handling, write a method to read the headers from the client's request and store them in a HashMap. The function can return the HashMap as its return value. The function can have the form:

      HashMap getHeaders(BufferedReader in) throws Exception

Moving On

In class on Monday, we will discuss what you might do with the headers and what, exactly, you will need to have done when you turn in your Web server in two weeks. We will also discuss some of the ways in which that web server can be improved.


David Eck