Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Network Programming - Server Sockets

Java Programming, Lesson # 562, Revised 01/23/98.

Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.

Introduction

As you are already aware, sockets come in three varieties which are implemented by the Java classes named Socket, DatagramSocket, and ServerSocket. The first two socket classes represent TCP and UDP communications respectively.

Generally, the two socket classes are used to implement both clients and servers , while the ServerSocket class is used to implement servers.

A previous lesson illustrated how to use the Socket class to implement several different types of clients. All of them were relatively simple, and we learned that the difficulty in socket programming derives from the requirements to implement the application protocol and not from the sockets themselves.

In this lesson, we will develop a considerably more substantive program. This program will be a TCP/IP server, implemented using Socket and ServerSocket, that can support both the echo port protocol and part of the HTTP protocol.

In addition, this program will illustrate how to implement a custom security manager for a Java application.

We will defer discussion of the DatagramSocket class until a subsequent lesson.

Socket programming provides a low-level mechanism by which you can connect two computers for the exchange of data. One of those is considered to be the client and the other is considered to be the server.

The client initiates a connection with a server. Servers wait for a clients to initiate connections.

Socket programming makes it possible for you to cause data to flow in a full-duplex mode between a client and a server.

Server Program

This program uses sockets to implement two different servers on an IP network. The program is intended for illustration and experimentation purposes only.

If you use this program for any purpose, you are using it at your own risk.

If you use this program, you should tighten the security manager to the degree needed by your particular situation. You can tighten the security manager by removing the overridden methods that begin with the word check (such as checkAccept) in the class named ServerSecurityManager.

This program implements two different servers operating on two different ports on the same computer running the same program.

One of the servers is an "echo" server that implemented by using a thread object to monitor port 7. This server echoes the string that it receives from a client. Port 7 is the standard echo port.

The other server is an abbreviated HTTP server implemented by using a thread object to monitor port 80. Port 80 is the standard HTTP port. This server has the ability to respond to a GET command from a client and deliver a specified file as a stream of bytes.

Two different servers were implemented on two different ports in this program to illustrate the manner in which threads can be used to service multiple ports within the same program.

A custom security manager is implemented which attempts to prevent the server from delivering any files other than those in the current directory or in a subdirectory of the current directory. The current directory is the directory from which the server program was started.

Otherwise, the security manager is wide open and doesn't enforce any security at all.

Please DO NOT install this server on your network and leave it unattended because client computers could then connect and have broad access to your computer.

This program was tested using JDK 1.1.3 under Win95.

You must be logged onto an appropriate network for this program to run properly. Otherwise, it will throw an exception of type UnknownHostException.

To test this program on a Win95 system, simply start the program as a normal Java application prior to starting the client program being used to test the program (or before causing the client program to attempt to contact the server).

The HTTP portion of this program can be tested under Win95 using an ordinary browser by constructing a URL using the server name localhost and entering that URL in the browser's Location field. Your browser should connect to this program and attempt to download the files that you specify in the URL.

The HTTP portion of the program can also be tested using the client program named Sockets06 which was designed specifically for testing the security manager installed in this program. A listing of Sockets06 is provided near the end of this lesson.

You can also test the security manager portion of this program using an ordinary browser by specifying illegal file names in the URL.

The echo portion of this program can be tested using the client program named Sockets05 which was designed specifically for testing this program. A listing of this program is also provided near the end of this lesson.

I assume that the same, or similar test procedures can also be used on other platforms, but since I don't have access to other platforms, I have no way to verify that assumption.

Code Fragments

This program has a security manager. However, it is a Java application, it is not an applet.

The main() method of the controlling class for this program contains three significant statements as shown below.

The first statement establishes a custom security manager for the program. I will have more to say about the security manager later.

The second statement instantiates a server object that monitors port 80. This server object implements part of the HTTP protocol and delivers specified files to web browsers.

The third statement instantiates a server object that monitors port 7. This server object functions as an echo server, echoing strings received from TCP/IP clients.
public class Server01{
  public static void main(String[] args){
    System.setSecurityManager(new ServerSecurityManager());
    HttpServer httpServerThread = new HttpServer();
    EchoServer echoServerThread = new EchoServer();
  }//end main
}//end class Server01
Note that the two servers are implemented on different threads so that they can operate in parallel in an asynchronous manner.

Security Manager

Normally when you execute a Java application, there is no security manager, of if there is one, it doesn't restrict in any way what the program can do. However, when you establish a security manager for an application, using the setSecurityManager() method, just the reverse is true.

When you create a security manager for an application, the code in your application is prohibited from doing just about everything. The application has no privileges at all, and you as the designer of the custom security manager, must decide what privileges it should have and arrange for those privileges to be provided.

This is a very tedious and complex process. Although it isn't difficult to make a privilege available once you decide that it should be available, making that decision requires a significant understanding of how privileges relate to operations. Sometimes the relationship between privileges and operations is not obvious at the surface.

To create a custom security manager, you must extend the SecurityManager class. That class contains about 30 methods with names beginning in the word check (such as checkAccept). Those methods control access to various privileges. Each method controls access to a privilege of a type that generally matches the second part of the method name.

The default version of each of these methods is to disallow the privilege that it controls. To make that privilege available, you must override the method, either with an empty method, or with a method that implements a custom version of the privilege.

Deciding which methods to override requires a great deal of knowledge as to the relationship between the privileges controlled by the methods and the operations that the code in your program needs to perform.

In this program, I have overridden all of the check methods except one with an empty method. This causes the privileges associated with each of those methods to be available without restriction.

I overrode the checkRead(String str) method to customize the privilege that it controls rather than to simply make that privilege available without restriction. My intent was to modify this privilege so that the HTTP server could only deliver files from the current directory or from a subdirectory of the current directory. The code that I provided in the overridden version of this method to customize this privilege is shown in the next code fragment.

This is accomplished by confirming that the file specification in the URL is neither based on an absolute path, nor is in the parent directory of the current directory. Recall that the parent directory of the current directory is indicated by two periods (..).

If either of these conditions is true, the code in the overridden method throws a SecurityException.

The following code fragment also contains a small sampling of the other overridden methods of the SecurityManager class. All of these methods are overridden with empty methods. This has the effect of disabling the default version of the methods.

Many methods were omitted from this listing. You will find those methods in the complete listing of the program near the end of this lesson.
class ServerSecurityManager extends SecurityManager{
  public void checkRead(String str){
    if(new File(str).isAbsolute() 
                              || (str.indexOf("..") != -1))
      throw new SecurityException(
                    "Access to file: " + str + " denied.");
  }//end checkRead()

  public void checkAccept(String s, int i){}
  public void checkAccess(Thread t){}

  //many methods were omitted from this listing

  public void checkWrite(FileDescriptor f){}
  public void checkWrite(String s){}
}//end class ServerSecurityManager

Echo Server

There are two additional groups of classes that we will be discussing. The first group of classes are those classes needed to create and operate the echo server on its own thread.

The second group of classes are those classes needed to create and operate the HTTP server on its own thread.

The echo server is the simpler of the two, so we will discuss it first.

The general operation of both of these servers is as follows. First, each server operates on its own thread, listening to its assigned port for a client to request a connection. While a server is listening for a connection, it is blocked so as to consume minimal resources.

Whenever a client requests a connection on one of the two ports, the server for that port spawns another thread to handle that client's needs and then goes back to listening on its own thread.

The client-support threads are spawned at a lower priority than the server threads to keep them from interfering with the ability of the servers to recognize and respond to the fact that another client might be requesting a connection.

If many different clients request a connection on one of the ports, the server for that port will spawn a new thread to handle each of those clients (within the capabilities of the system). Thus, there could be many different threads in operation at any given time where each thread is serving the needs of a different client.

Let's examine the classes associated with the echo server to see how this is accomplished. As we saw in the main() method earlier in this lesson, the echo server is implemented as on object of the EchoServer class. This class extends Thread and is used to instantiate a thread object that monitors port 7 for connection requests from clients.

As shown below, the constructor for this class simply invokes its own start() method to cause the thread to start running.
class EchoServer extends Thread{
  
  EchoServer(){//constructor
    start(); //start the thread and invoke the run() method
  }//end constructor
The key to server operation in Java is the accept() method of the ServerSocket class. As you will see below, the ServerSocket constructor has a single parameter: the number of the port that will be monitored by the ServerSocket object being constructed.

When you instantiate a ServerSocket object and invoke the accept() method on that object, the method blocks and waits until a client requests a connection on the specified port. When this happens, a Socket object is automatically instantiated and returned by the accept() method.

This Socket object is automatically connected to the Socket being used by the client that requested the connection, and can be used to communicate with the client.

This is very important. Think about it some more.

No data transfer takes place by way of the ServerSocket object. Rather, the accept() method of that object simply blocks and monitors for a connection request from a client on the specified port. When a request is received, a Socket object is automatically instantiated and automatically connected to the Socket through which the client requested the connection. The new Socket object is returned by the accept() method and can be used to communicate with the Socket owned by the client.

So the first thing that we will do in the run() method of our EchoServer class is to instantiate a ServerSocket object tied to port 7.

Then we will enter an infinite loop and invoke the accept() method on that object as the only statement in the loop.

The accept() method will block, consuming few if any resources, until a connection request is received from a client.

When a connection request is received, we will spawn a new thread object of type EchoConnection to deal with that specific client. We will pass the new Socket object as a parameter to the constructor for the EchoConnection object, and the EchoConnection object will be able to use that Socket object to communicate with that client. The communication protocol for the service provided by this port will be incorporated into the run() method of the EchoConnection class.

Recall that the thread on which the EchoServer object is running is in the middle of an infinite loop. Once the new EchoConnection thread is successfully spawned, the EchoServer object will go back to listening for a connection request from another client.
  
  public void run(){//run method of EchoServer class
    try{
    ServerSocket serverSocket = new ServerSocket(7);
    while(true)
      new EchoConnection(serverSocket.accept());
    }catch(IOException e){System.out.println(e);}
  }//end run
That brings us to the class from which we spawn the new thread to deal with the client object. This class is named EchoConnection and it extends Thread as shown below.

This thread knows nothing about servers or clients. All it knows is that it is connected by way of a TCP/IP socket to another socket on another computer (or possibly another process on the same computer).

The constructor for this object receives a Socket object as a parameter and saves it as an instance variable. This is the Socket object that is connected to the Socket object on the client machine.

We need to make certain that the threads monitoring the ports are not prevented from doing their job by the threads servicing the clients, particularly on platforms that don't provide time slicing for threads of equal priority. Therefore, we will reduce the priority of this new thread to a level that is below the priority level of the threads monitoring the ports. That way, the threads monitoring the ports can always preempt the threads servicing clients whenever a new connection request is received from another client.

Once we have saved the Socket object and adjusted the priority, we invoke the start() method to get this thread up and running. As usual, the start() method will invoke the run() method on this thread.
class EchoConnection extends Thread{
  Socket socket;

  EchoConnection(Socket socket){//constructor
    this.socket = socket;//save the incoming socket
    setPriority( NORM_PRIORITY-1 );
    start();//start this thread and invoke the run method
  }//end constructor
The run() method for this thread looks a lot like the code that we saw in a previous lesson where we were doing socket programming from the client side.

We will get input and output streams on the Socket object exactly as we did when programming from the client side. I have removed that code from the following code fragment for brevity. I have also removed the exception handling code for brevity.

The purpose of the server on this port is to receive a string from a client and to echo it exactly as received.

We will read a line of text from the input stream on the Socket object and then print that same line to the output stream on the same Socket object. The incoming line of text comes from the client and the outgoing line of text goes to the client.

Then we close the socket and that satisfies the protocol requirements for this simple echo server thread.
  public void run(){
    //Code to get input and output streams and exception
    // handling code removed for brevity.    

      String input = inputStream.readLine();
      outputStream.println(input);
      socket.close();
  }//end run method
That wraps up the discussion of the echo server.

HTTP Server

Next we will discuss the class from which objects are instantiated to take care of HTTP-protocol clients who request a connection. Although this is a considerably more complex case, much of the code is identical to that for the simple echo server case above.

Note that this abbreviated HTTP server is designed to respond only to the GET command from the HTTP protocol. Other HTTP commands are ignored and an error message is sent to the client.

Code in main() instantiates an object of type HttpServer. This is a thread object that instantiates a ServerSocket tied to port 80 which is the standard HTTP port.

Except for being tied to port 80, the operation of this server is identical to the operation of the simple echo server up to the point where protocol becomes important.

The accept() method is invoked on the ServerSocket object inside an infinite loop to block and monitor port 80, listening for a connection request from a client. When such a request occurs, the accept() method instantiates and returns a Socket object, already connected to the Socket on the client machine.

This Socket object is passed as a parameter to the constructor of a new thread object of type HttpConnection. I have not shown any of this preliminary code here because it is virtually identical to the corresponding code in the echo server discussed above. You can view it in the complete program listing near the end of this lesson.

The HttpConnection class is used to spawn a new thread that will communicate with the client and implement an abbreviated version of the HTTP protocol.

The constructor for this class is essentially the same as for the echo server. It saves the incoming Socket object, adjusts the priority of the thread downward as explained earlier, and invokes the start() method which in turn invokes the run() method. Since you have seen code like this earlier in this lesson, I did not include it here.

That brings to the run() method where the work of a thread is always done (or at least where it is controlled).

In the interest of brevity, I am going to remove exception handling code from the following fragments. I am also going to remove code that is the same as what you have seen previously and replace that code with comments.

Most of our previous programs on this topic have created input and output streams to move text data across the socket, and this program does the same. I have removed that code from the following fragment and replaced it with a comment.

However, this program also creates and uses an output stream to transmit an array of bytes to the client. We haven't seen that before in this series of lessons, so the following code fragment will begin with the creation of this new type of output stream object.
  public void run(){
    //Remove exception handling code.
    //Remove code used to get input and output streams
    // that are used to handle text data transfers.   

    //Get stream to transmit an array of bytes       
    byteOutput = new DataOutputStream(
                                 socket.getOutputStream());
Continuing inside the run() method of the HTTP protocol class, the next thing that we expect to happen is to receive a request from the client. We will use the readLine() method on our input stream to read this request and store it in a String object as shown below.
      String request = inputStream.readLine();
Now we are faced with the challenge of analyzing this request to determine what the client is requesting, and whether or not we can comply. Recall that the only request that this program can accommodate is a request to GET a file.

We begin the analysis by creating a new StringTokenizer object named stringTokenizer that represents the String object named request. You might need to go back and review the use of StringTokenizer in order to fully understand some of this code.

First we need to confirm that the request is a GET request. We use an if statement to make this test as shown below.
      StringTokenizer stringTokenizer = 
                              new StringTokenizer(request);
      if((stringTokenizer.countTokens() >= 2) 
             && stringTokenizer.nextToken().equals("GET")){
Later we will see that if the request is not a GET request, we will create an HTML document on the fly containing an error message and send it to the client.

The following code fragment is executed if the request is a GET request.

Basically what we are trying to do here is to provide a default file name of index.html in the event that the client didn't specify a file name. It is common practice among web servers to try to send a file having that name when you don't specify the name of the file in your request.

First we will eliminate the possibility that the request contains an extra "/" character, and if so, we will strip it off and discard it.
        if((request = stringTokenizer.nextToken()).
                                          startsWith("/")){
          request = request.substring(1);
        }//end if on startsWith("/")
If the request string as it exists at this point ends with a "/" or is an empty string, we will assume that the client wants to download the file named index.html. In that case, we will append that filename to the request string.
        if(request.endsWith("/") || request.equals("")){
          request = request + "index.html";
        }
At this point, we believe that we know the name of the requested file and the path to that file so we will attempt to find and read it.

We attempt to open a FileInputStream object tied to the specified file name and path. If it doesn't exist, an exception will be thrown at this point and processed in a catch block further down the page.

If the file does exist and can be read, we create a byte array named data whose size equals the number of bytes that can be read from the file. Then we read the file into that array.
        FileInputStream fileInputStream = 
                              new FileInputStream(request);
        byte[] data = 
                     new byte[fileInputStream.available()];
        fileInputStream.read(data);
Now we have identified, located, and read the file into an array in memory. The next step is to use the output stream that we instantiated earlier to transmit the entire contents of this array to the client as shown below. We finish up by flushing this output stream.
        byteOutput.write(data);
        byteOutput.flush();
The next fragment is the code that is executed in the event that the client request was not a GET request. In this case, we create an HTML document on the fly containing an error message and send it to the client.
else
  outputStream.println(
      "<HTML><BODY><P>400 Bad Request<P></BODY></HTML>");
Assuming that no exceptions have been thrown, the last thing that we do is to close the socket and allow the thread to terminate naturally as shown below.
      socket.close();
If exceptions have been thrown, there are several exception handlers to deal with those exceptions as shown below.

Note that for brevity, I have removed the code from these handlers that displays diagnostic information on the server console.

In fact, I have been removing code that displays status information on the server console throughout this discussion. You can view that code in the complete listing of the program that follows shortly.

Two of these event handlers create an HTML page containing an error message and send it to the client.
    catch(FileNotFoundException e){
      outputStream.println(
         "<HTML><BODY><P>404 Not Found<P></BODY></HTML>");
      try{
        socket.close();
      }catch(IOException evt){System.out.println(evt);}
    }//end catch
         
    catch(SecurityException e){
      outputStream.println(
          "<HTML><BODY><P>403 Forbidden<P></BODY></HTML>");
      outputStream.println(e);
      try{
        socket.close();
      }catch(IOException evt){System.out.println(evt);}
    }//end catch SecurityException
    
    catch( IOException e){
      System.out.println( "I/O error " + e );
      try{
        socket.close();
      }catch(IOException evt){System.out.println(evt);}
    }//end catch
  }//end run method
The numbers that you see in these error messages come from a standard list of errors for the HTTP protocol.

The structure of the last one of these exception handlers is a little bothersome. In particular, we need to close the socket inside the exception handler for an IOException. However, the close() method throws an IOException so we have the possibility of throwing an IOException inside a handler of the same type.

I don't know how to force an IOException while a previous IOException is being handled, so I don't have any way to test this handler to see exactly how it will behave.

Program Listing

A complete listing of the program follows. In addition, listings are also given for two short client programs that were specially designed to test certain aspects of this program.
/*File Server01.java Copyright 1998, R.G.Baldwin
This program uses sockets to implement two different 
servers on an IP network.  The program is intended for 
illustration and experimentation purposes only.  

If you use this program for any purpose, you use it at 
your own risk.

If you use this program, you should tighten the security 
manager to the degree needed by your situation.  You can
tighten the security manager by removing the overridden
methods that begin with the word check (such as 
checkAccept) in the class named ServerSecurityManager.

This program implements two different servers.  One of the
servers is an "echo" server implemented by a thread
monitoring port 7.  This server simply echoes the
string that it receives from a client.  Port 7 is the
standard echo port.

The other server is an abbreviated HTTP server implemented
by a thread monitoring port 80.  Port 80 is the standard
HTTP port.  This server has the ability to respond to a
GET command from a web browser and serve a file as a stream
of bytes.

Two different servers were implemented on two different
ports to illustrate the manner in which threads can be 
used in Java to service multiple ports.

A custom security manager is implemented which attempts
to prevent the server from serving files other than those
in the current directory or in a subdirectory of the 
current directory.  Otherwise, the security manager is 
wide open and doesn't enforce any security at all.

DO NOT install this server on your network and leave it
unattended because client computers could connect and
have broad access to your computer.

This program was tested using JDK 1.1.3 under Win95.

The HTTP portion of this program can be tested using an 
ordinary browser with the server name localhost.  It can 
also be tested using the program named Sockets06 which was
designed specifically for testing the security manager
installed in this program.  However, you can also test
the security manager using an ordinary browser.

The echo portion of this program can be tested using
the program named Sockets05 which was designed 
specifically for testing this program.
**********************************************************/

import java.net.*;
import java.io.*;
import java.util.*;

public class Server01{
  public static void main(String[] args){
    //Instantiate a new custom security manager.
    System.setSecurityManager(new ServerSecurityManager());
    //Instantiate a server object to listen on port 80
    HttpServer httpServerThread = new HttpServer();
    //Instantiate a server object to listen on port 7
    EchoServer echoServerThread = new EchoServer();
  }//end main
}//end class Server01
//=======================================================//
//=======================================================//

//This class is used to instantiate a security manager
// that confirms that files can be downloaded only from
// the current directory or a subdirectory of that
// directory. Note that other than checking for file
// downloading, this is a wide open security manager. DO 
// NOT install it on your network without considering the
// need to implement tighter security features.  This
// security manager class was tested using JDK 1.1.3
// under Win95.
class ServerSecurityManager extends SecurityManager{
  //This overridden method attempts to allow the server
  // to deliver files only from the current directory or
  // from a subdirectory of that directory.
  public void checkRead(String str){

    if(new File(str).isAbsolute() 
                              || (str.indexOf("..") != -1))
      throw new SecurityException(
                    "Access to file: " + str + " denied.");
  }//end checkRead()

  //The following list of overridden methods causes the
  // security manager to be wide open and eliminates all
  // security checks other than the one provided by the
  // previous overridden method.
  public void checkAccept(String s, int i){}
  public void checkAccess(Thread t){}
  public void checkAccess(ThreadGroup g){} 
  public void checkAwtEventQueueAccess(){}
  public void checkConnect(String s, int i){} 
  public void checkConnect(String s, int i, Object o){}
  public void checkCreateClassLoader(){}
  public void checkDelete(String s){}
  public void checkExec(String s){}
  public void checkExit(int i){}
  public void checkLink(String s){}
  public void checkListen(int i){}
  public void checkMemberAccess(Class c, int i){}
  public void checkMulticast(InetAddress a){}
  public void checkMulticast(InetAddress a, byte b){}
  public void checkPackageAccess(String s){}
  public void checkPackageDefinition(String s){}
  public void checkPrintJobAccess(){}
  public void checkPropertiesAccess(){}
  public void checkPropertyAccess(String s){}
  public void checkRead(FileDescriptor f){}
//public void checkRead(String s){}//overridden above
  public void checkRead(String s, Object o){}
  public void checkSecurityAccess(String s){}
  public void checkSetFactory(){}
  public void checkSystemClipboardAccess(){}
  public boolean checkTopLevelWindow(Object o)
    {return true;}
  public void checkWrite(FileDescriptor f){}
  public void checkWrite(String s){}
}//end class ServerSecurityManager
//=======================================================//
//=======================================================//

//This class is used to instantiate a server thread that
// listens on port 7 which is the echo port.
class EchoServer extends Thread{
  
  EchoServer(){//constructor
    start(); //start the thread and invoke the run() method
  }//end constructor
  //-----------------------------------------------------//
  
  public void run(){
    try{
    //Instantiate a serverSocket on port 7 (echo port)
    ServerSocket serverSocket = new ServerSocket(7);
    System.out.println("Server Listening on port 7");
    //Loop and listen to port 7.  If a call is
    // received, spawn an EchoConnection thread to
    // deal with it.
    while(true)
      //This statement blocks on the accept() method
      // and returns a socket if a call is received.
      // The socket is passed as a parameter to the
      // new thread that is spawned.
      new EchoConnection(serverSocket.accept());
    }catch(IOException e){System.out.println(e);}
  }//end run
  
}//end class EchoServer
//=======================================================//

//This class is used to spawn a thread to deal with a
// call that is received on port 7 which is the echo
// port.
class EchoConnection extends Thread{
  Socket socket;

  EchoConnection(Socket socket){//constructor
    System.out.println("Received a call on port 7");
    this.socket = socket;
    //Operate at a priority that is below the threads
    // listening on the ports.
    setPriority( NORM_PRIORITY-1 );
    start();//start this thread and invoke the run method
  }//end constructor
  //-----------------------------------------------------//
  
  public void run(){
    System.out.println("Running thread for port 7");
    BufferedReader inputStream = null;
    PrintWriter outputStream = null;
    
    try{
      //Get an input stream from the socket
      inputStream = 
                 new BufferedReader(new InputStreamReader(
                                 socket.getInputStream()));
      //Get  an output stream to the socket.  Note
      // that this stream is supposed to autoflush.
      outputStream = 
                   new PrintWriter(new OutputStreamWriter(
                           socket.getOutputStream()),true);

      //Get input from the browser and echo it to 
      // the browser
      String input = inputStream.readLine();
      outputStream.println(input);
      System.out.println("Got Input: "+ input);
      socket.close();
      System.out.println("Socket closed");
    }//end 
    catch( IOException e)
      {System.out.println( "I/O error " + e );}
  }//end run method
}//end class EchoConnection
//=======================================================//
//=======================================================//

//This class is used to instantiate a server thread that
// listens on port 80 which is the http port.
class HttpServer extends Thread{
  
  HttpServer(){//constructor
    start(); //start the thread and invoke the run() method
  }//end constructor
  //-----------------------------------------------------//
  
  public void run(){
    try{
    //Instantiate a serverSocket on port 80 (http port)
    ServerSocket serverSocket = new ServerSocket(80);
    System.out.println("Server Listening on port 80");
    //Loop and listen to port 80.  If a call is
    // received, spawn an HttpConnection thread to
    // deal with it.
    while(true)
      //This statement blocks on the accept() method
      // and returns a socket if a call is received.
      // The socket is passed as a parameter to the
      // new thread that is spawned.
      new HttpConnection(serverSocket.accept());
    }catch(IOException e){System.out.println(e);}
  }//end run
  
}//end class HttpServer
//=======================================================//

//This class is used to spawn a thread to deal with a
// call that is received on port 80 which is the http
// port.
class HttpConnection extends Thread{
  Socket socket;
    BufferedReader inputStream = null;
    PrintWriter outputStream = null;
    DataOutputStream byteOutput = null;

  HttpConnection(Socket socket){//constructor
    System.out.println("Received a call on port 80");
    this.socket = socket;
    //Operate at a priority that is below the threads
    // listening on the ports.
    setPriority( NORM_PRIORITY-1 );
    start();//start this thread and invoke the run method
  }//end constructor
  //-----------------------------------------------------//
  
  public void run(){
    System.out.println("Running thread for port 80");
    
    try{
      //Get an input stream from the socket
      inputStream = 
                 new BufferedReader(new InputStreamReader(
                                 socket.getInputStream()));
                                 
      //Get  an output stream to the socket to use for
      // sending text strings.  Note that this stream 
      // will autoflush.
      outputStream = 
                   new PrintWriter(new OutputStreamWriter(
                           socket.getOutputStream()),true);
                           
      //Get an output stream to the socket to use for
      // sending an array of byte data to a client.       
      byteOutput = new DataOutputStream(
                                 socket.getOutputStream());

      //Get request from the browser
      String request = inputStream.readLine();
      System.out.println("Got Request: "+ request);

      //Now analyze and attempt to respond to the request.
      // Note that this program supports only the GET
      // request.  Any other request will be ignored.
      StringTokenizer stringTokenizer = 
                              new StringTokenizer(request);
      if((stringTokenizer.countTokens() >= 2) 
             && stringTokenizer.nextToken().equals("GET")){
        System.out.println("First token is GET");
        if((request = stringTokenizer.nextToken()).
                                          startsWith("/")){
          System.out.println(
                 "Next token starts with /, strip it off");
          request = request.substring(1);
        }//end if on startsWith("/")
          
        if(request.endsWith("/") || request.equals("")){
          System.out.println("Request endsWith / or " +
                               "blank, append index.html");
          request = request + "index.html";
          System.out.println(
                           "Modified request: " + request);
        }//end if on / or ""
          
        System.out.println("Try to read: " + request);
        FileInputStream fileInputStream = 
                              new FileInputStream(request);
        //Instantiate a byte array whose size is equal to
        // the number of bytes that can be read from this
        // fileInputStream without blocking. Then read the
        // file into the byte array.
        byte[] data = 
                     new byte[fileInputStream.available()];
        fileInputStream.read(data);

        //Send the bytes in the array to the client.
        byteOutput.write(data);
        byteOutput.flush();
        
      }//end if stringTokenizer.countTokens...
      else
        //This happens when the request is not GET
        outputStream.println("<HTML><BODY><P>400 Bad " +
                               "Request<P></BODY></HTML>");
      
      socket.close();
      System.out.println("Socket closed");
    }//end 
    catch(FileNotFoundException e){
      outputStream.println(
         "<HTML><BODY><P>404 Not Found<P></BODY></HTML>");
      try{
        socket.close();
        System.out.println("Socket closed");
      }catch(IOException evt){System.out.println(evt);}
    }//end catch
         
    catch(SecurityException e){
      System.out.println(e);
      e.printStackTrace();
      outputStream.println(
          "<HTML><BODY><P>403 Forbidden<P></BODY></HTML>");
      outputStream.println(e);
      try{
        socket.close();
        System.out.println("Socket closed");
      }catch(IOException evt){System.out.println(evt);}
    }//end catch SecurityException
    
    catch( IOException e){
      System.out.println( "I/O error " + e );
      try{
        socket.close();
        System.out.println("Socket closed");
      }catch(IOException evt){System.out.println(evt);}
    }//end catch
  }//end run method
}//end class HttpConnection
//=======================================================//
The following client program was designed to test certain aspects of the server program listed above.
/*File Sockets05.java Copyright 1998, R.G.Baldwin
Revised 01/20/98

This program is just like Sockets03 except that it
opens a socket to localhost.  It is used to test the
echo portion of the server in the program named
Server01.

This program performs a simple echo test with a server
by sending a line of text to the echo port, port 7.

You must be logged onto an appropriate network for this
program to run properly.  Otherwise, it will throw
an exception of type UnknownHostException.

Most of the program is enclosed in a try/catch block to
deal with possible exceptions.

The program begins by instantiating a String object
containing the name of an echo server that you are
using to test the program.

This is followed by declaration and initialization of
an int variable containing the standard echo port number.
The standard echo port is number 7.

Than the program gets a socket connection to port 7
on the echo server.

Then the program gets input and output streams from the
socket and wraps them in the new reader classes.

Once the input and output streams are ready to use, the
sends a line of text to the echo port on the server.

The echo server sends the same line of text back to
the client.

Then the program reads the line of text that is 
received and displays it.

Then the program closes the socket and terminates.

This program was tested using JDK 1.1.3 under Win95.

The output from this program is:
  
This is an echo test
**********************************************************/

import java.net.*;
import java.io.*;
import java.util.*;

class Sockets05{
  public static void main(String[] args){
    String server = "localhost";
    int port = 7; //echo port
    
    try{
      //Get a socket, connected to the specified server
      // on the specified port.
      Socket socket = new Socket(server,port);
      
      //Get an input stream from the socket
      BufferedReader inputStream = 
                 new BufferedReader(new InputStreamReader(
                                 socket.getInputStream()));

      //Get  an output stream to the socket.  Note
      // that this stream will autoflush.
      PrintWriter outputStream = 
                   new PrintWriter(new OutputStreamWriter(
                           socket.getOutputStream()),true);

      //Send line of text to the server
      outputStream.println("This is an echo test");
      //Get echoed line back from server and display it
      System.out.println(inputStream.readLine());
        
      //Close the socket
      socket.close();
    }//end try
    catch(UnknownHostException e){
      System.out.println(e);
      System.out.println(
                       "Must be online to run properly.");
    }//end catch UnknownHostException
    catch(IOException e){System.out.println(e);}
  }//end main
}//end class Sockets05
//=======================================================//
The following client program was designed to test certain aspects of the server program listed above.
/*File Sockets06.java Copyright 1998, R.G.Baldwin
Revised 01/20/98

The sole purpose of this program is to test the custom 
security manager implemented in the program named Server01.

This program is the same as Sockets01 except that
it connects to localhost and attempts to download
a file protected by the security manager in the program
named Server01.

This program is a simple http client (web browser)
implemented using sockets.

The program implements just enough of the http protocol
to make it capable of getting an html page from an
http server (assuming that access to the requested file
is allowed by the security manager on the server).

You must be logged onto an appropriate network for this
program to run properly.  Otherwise, it will throw
an exception of type UnknownHostException.

Most of the program is enclosed in a try/catch block to
deal with possible exceptions.

The program begins by defining the name of a server and
the number of the http port on that server.  The standard
port number for http servers is 80.

Then the program opens a socket to the specified server
on the specified port.

Then it uses the new BufferedReader class along with the
new InputStreamReader class to open an input stream from
the socket.  These classes are wrapped around an input
stream provided by the Socket class.

Then it uses the new PrintWriter class along with the
new OutputStreamWriter class to open an output stream to
the socket.  These classes are also wrapped around an 
output stream provided by the Socket class.

The output stream will autoflush, which is critical.  If
the output stream isn't flushed, the server will not
respond (presumably it doesn't receive all of the 
output data until the stream is flushed).

Then the program, acting as an http client, sends a GET
command to the server specifying a particular path and 
file name. Two versions of this statement are included.
One statement attempts to get a file using an absolute
pathname.  The other statement attempts to get a file
from the parent directory.  Both of these two possibilities
are prohibited by the custom security manager in the 
program named Server01.

This causes the server to attempt to fetch the specified 
file and send it to the client.

Since access to these files is not allowed, a 
SecurityException is thrown in Server01 which causes
error messages to be sent to the client.

Then this program reads lines from the input stream and 
displays them on the standard output device.  In this
case, they will be error messages.

When there are no more lines to be read, a null is 
received.  This causes the client to exit the input
loop and to close the socket.

This program was tested using JDK 1.1.3 under Win95.
**********************************************************/

import java.net.*;
import java.io.*;

class Sockets06{
  public static void main(String[] args){
    String server = "localhost";//server name
    int port = 80; //http port
    try{
      //Get a socket, connected to the specified server
      // on the specified port.
      Socket socket = new Socket(server,port);
      
      //Get an input stream from the socket
      BufferedReader inputStream = 
                 new BufferedReader(new InputStreamReader(
                                 socket.getInputStream()));

      //Get  an output stream to the socket.  Note
      // that this stream will autoflush.
      PrintWriter outputStream = 
                   new PrintWriter(new OutputStreamWriter(
                           socket.getOutputStream()),true);

      //Send a GET command to the server.  Either of the
      // following statements will cause the program named
      // Server01 to throw a SecurityException.
      //Test ability to get file from parent directory
//      outputStream.println("GET ../baldwin/Test01.html");
      //Test ability to get file using absolute pathname
      outputStream.println("GET C:/index.html");

      //Declare a String to read lines into.
      String line = null;
      
      //Loop reading and displaying lines until null 
      // is received.
      while((line = inputStream.readLine()) != null) 
        System.out.println(line);
        
      //Close the socket
      socket.close();
    }//end try
    catch(UnknownHostException e){
      System.out.println(e);
      System.out.println(
                       "Must be online to run properly.");
    }//end catch UnknownHostException
    catch(IOException e){System.out.println(e);}
  }//end main
}//end class Sockets06
//=======================================================//
.

-end-