2.8 Building a Simple Web Server

Now that we have studied HTTP in some detail and have learned how to write client-server applications in Java, let us combine this new-found knowledge and build a simple Web server in Java. We will see that the task is remarkably easy.

Our goal is to build a server that does the following:

Let's try to make the code as simple as possible in order to shed insight on the networking concerns. The code that we present will be far from bullet proof! For example, let's not  worry about handling exceptions. And let's assume that the client requests an object that is in server's file system.

WebServer.java

Here is the code for a simple Web server:
 

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

class WebServer{

    public static void main(String argv[]) throws Exception  {

          String requestMessageLine;
          String fileName;

          ServerSocket listenSocket = new ServerSocket(6789);
          Socket connectionSocket = listenSocket.accept();

          BufferedReader inFromClient =
            new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
          DataOutputStream outToClient =
            new DataOutputStream(connectionSocket.getOutputStream());

          requestMessageLine = inFromClient.readLine();

          StringTokenizer tokenizedLine =
            new StringTokenizer(requestMessageLine);

                      if (tokenizedLine.nextToken().equals("GET")){

          fileName = tokenizedLine.nextToken();

          if (fileName.startsWith("/") == true )
                         fileName  = fileName.substring(1);

                      File file = new File(fileName);
          int numOfBytes = (int) file.length();

          FileInputStream inFile  = new FileInputStream (fileName);

                      byte[] fileInBytes = new byte[numOfBytes];
          inFile.read(fileInBytes);

          outToClient.writeBytes("HTTP/1.0 200 Document Follows\r\n");

          if (fileName.endsWith(".jpg"))
                  outToClient.writeBytes("Content-Type: image/jpeg\r\n");
          if (fileName.endsWith(".gif"))
                  outToClient.writeBytes("Content-Type: image/gif\r\n");

          outToClient.writeBytes("Content-Length: " + numOfBytes + "\r\n");
          outToClient.writeBytes("\r\n");

          outToClient.write(fileInBytes, 0, numOfBytes);

          connectionSocket.close();
                     }

     else System.out.println("Bad Request Message");
 
     }
}
 
 
Let us now take a look at the code. The first half the program is almost identical to TCPServer.java. As with TCPServer.java, we import the java.io and java.net packages. In addition to these two packages we also import the java.util package, which contains the StringTokenizer class, which is used for parsing HTTP request messages. Looking now at the lines within the class WebServer, we define two string objects:

        String requestMessageLine;
    String fileName;

The object requestMessageLine is a string that will contain the first line in the HTTP request message. The object fileName is a string that will contain the file name of the requested file. The next set of commands is  identical to the corresponding set of commands in TCPServer.java.

         ServerSocket listenSocket = new ServerSocket(6789);
    Socket connectionSocket = listenSocket.accept();

         BufferedReader inFromClient =
      new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
    DataOutputStream outToClient =
      new DataOutputStream(connectionSocket.getOutputStream());

Two socket-like objects are created. The first of these objects is listenSocket, which is of type ServerSocket. The object listenSocket is created by the server program before receiving a request for a TCP connection from a client. It listens at port 6789, and waits for a request from some client to establish a TCP connection. When a request for a connection arrives, the accept() method of listenSocket creates a new object, connectionSocket, of type Socket.  Next two streams are created: the BufferedReader inFromClient and the DataOutputStream outToClient. The HTTP request message comes from the network, through connectionSocket and into inFromClient; the HTTP response message goes into  outToClient, through connectionSocket and into the network. The remaining portion of the code differs significantly from TCPServer.java.

        requestMessageLine = inFromClient.readLine();

The above command reads the first line of the HTTP request message. This line is supposed to be of the form:

Our server must now parse the line to extract the filename.

       StringTokenizer tokenizedLine = new StringTokenizer(requestMessageLine);

       if (tokenizedLine.nextToken().equals("GET")){

             fileName = tokenizedLine.nextToken();

             if (fileName.startsWith("/") == true )
                     fileName  = fileName.substring( 1 );

The above commands parse the first line of the request message to obtain the requested filename. The object tokenizedLine can be thought of as the original request line with each of the "words" GET, file_name and HTTP/1.0 placed in a separate place holder called a token. The server knows from the HTTP RFC that the file name for the requested file is contained in the token that follows the token containing "GET". This file name is put in a string called fileName. The purpose of the last if statement in the above code is to remove the backslash that may precede the filename.

       FileInputStream inFile  = new FileInputStream (fileName);

The above command attaches a stream, inFile,  to the file fileName.

       byte[] fileInBytes = new byte[numOfBytes];
       inFile.read(fileInBytes);
 
The above commands determine the size of the file and construct an array of bytes of that size. The name of the array is fileInBytes. The last command reads from the stream inFile to the byte array fileInBytes. The program must convert to bytes because the output stream outToClient may only be fed with bytes.
 

Now we are ready to construct the HTTP response message. To this end we must first send the HTTP response header lines into the DataOutputStream outToClient:

 
               outToClient.writeBytes("HTTP/1.0 200 Document Follows\r\n");

                if (fileName.endsWith(".jpg"))
                             outToClient.writeBytes("Content-Type: image/jpeg\r\n");
                if (fileName.endsWith(".gif"))
                             outToClient.writeBytes("Content-Type: image/gif\r\n");

                outToClient.writeBytes("Content-Length: " + numOfBytes + "\r\n");
                outToClient.writeBytes("\r\n");

The above set of commands are particularly interesting. These commands prepare the header lines for HTTP response message and send the header lines to the TCP send buffer. The first command sends the mandatory status line: HTTP/1.0 200 Document Follows, followed by a carriage return and a line feed. The next two command lines prepare a single content-type header line. If the server is to transfer a gif image, then the server prepares the header line Content-Type: image/jpeg. If, on the other hand, the server is to transfer a jpeg image, then the server prepares the header line Content-Type: image/gif. (In this simple Web server, no content line is sent if the object is neither a gif nor a jpeg image.) The server then prepares and sends a content-length header line and a mandatory blank line to precede the object itself that is to be sent. We now must send the file FileName into the DataOutputStream outToClient. But because outToClient works with bytes, we first must perform a conversion to bytes:

We can now send the requested file:
 
           outToClient.write(fileInBytes, 0, numOfBytes);

The above command sends the requested file, fileInBytes, to the TCP send buffer. TCP will concatenate the file, fileInBytes, to the header lines just created, segment the concatenation if necessary, and send the TCP segments to the client.

             connectionSocket.close();

After serving  one request for one file, the server performs some housekeeping by closing the socket connectionSocket.

To test this web server, install it on a host. Also put some files in the host. Then use a browser running on any machine to request a file from the server. When you request a file, you will need to use the port number that you include in the server code (e.g., 6789). So if your server is located at somehost.somewhere.edu, the file is somefile.html, and the port number is 6789, then the browser should request http://somehost.somewhere.edu:6789/somefile.html .
 

Return to Table of Contents


Copyright 1996-2000 Keith W. Ross and James F. Kurose