Web Server in Java: Part B

Instead of simply terminating the thread after displaying the browser's HTTP request message, we will analyze the request and send an appropriate response. We are going to ignore the information in the header lines, and use only the file name contained in the request line. In fact, we are going to assume that the request line always specifies the GET method, and ignore the fact that the client may be sending some other type of request, such as HEAD or POST.

We extract the file name from the request line with the aid of the StringTokenizer class. First, we create a StringTokenizer object that contains the string of characters from the request line. Second, we skip over the method specification, which we have assumed to be "GET". Third, we extract the file name.

   // Extract the filename from the request line.
   StringTokenizer tokens = new StringTokenizer( requestLine ) ;
   tokens.nextToken( ) ;  // skip over the method, which should be "GET"
   String fileName = tokens.nextToken( ) ;

   // Prepend a "." so that file request is within the current directory.
   fileName = "." + fileName ;

Because the browser precedes the filename with a slash, we prefix a dot so that the resulting pathname starts within the current directory.

Now that we have the file name, we can open the file as the first step in sending it to the client. If the file does not exist, the FileInputStream() constructor will throw the FileNotFoundException. Instead of throwing this possible exception and terminating the thread, we will use a try/catch construction to set the boolean variable fileExists to false. Later in the code, we will use this flag to construct an error response message, rather than try to send a nonexistent file.

   // Open the requested file.
   FileInputStream fis = null ;
   boolean fileExists = true ;
   try 
   {
      fis = new FileInputStream( fileName ) ;
   } 
   catch ( FileNotFoundException e ) 
   {
      fileExists = false ;
   }

There are three parts to the response message: the status line, the response headers, and the entity body. The status line and response headers are terminated by the character sequence CRLF. We are going to respond with a status line, which we store in the variable statusLine, and a single response header, which we store in the variable contentTypeLine. In the case of a request for a nonexistent file, we return 404 Not Found in the status line of the response message, and include an error message in the form of an HTML document in the entity body.

   // Construct the response message.
   String statusLine = null ;
   String contentTypeLine = null ;
   String entityBody = null ;
   if ( fileExists )
   {
      statusLine = ? ;
      contentTypeLine = "Content-type: " + 
         contentType( fileName ) + CRLF ;
   }
   else
   {
      statusLine = ? ;
      contentTypeLine = ? ;
      entityBody = "<HTML>" + 
         "<HEAD><TITLE>Not Found</TITLE></HEAD>" +
         "<BODY>Not Found</BODY></HTML>" ;
   }

When the file exists, we need to determine the file's MIME type and send the appropriate MIME-type specifier. We make this determination in a separate private method called contentType(), which returns a string that we can include in the content type line that we are constructing.

Now we can send the status line and our single header line to the browser by writing into the socket's output stream.

   // Send the status line.
   os.write( statusLine.getBytes( ) ) ;

   // Send the content type line.
   os.write( ? ) ;

   // Send a blank line to indicate the end of the header lines.
   os.write( CRLF.getBytes( ) ) ;

The class OutputStream does not include a method for writing strings, so we need to convert our string data into byte arrays. We do this with the getBytes() method of the String class.

Now that the status line and header line with delimiting CRLF have been placed into the output stream on their way to the browser, it is time to do the same with the entity body. If the requested file exists, we call a separate method to send the file. If the requested file does not exist, we send the HTML-encoded error message that we have prepared.

   // Send the entity body.
   if ( fileExists )
   {
      sendBytes( fis, os ) ;
      fis.close( );
   }
   else
   {
      os.write( ? ) ;
   }

After sending the entity body, the work in this thread has finished, so we close the streams and socket before terminating.

We still need to code the two methods that we have referenced in the above code, namely, the method that determines the MIME type, contentType(), and the method that writes the requested file onto the socket's output stream. Let's first take a look at the code for sending the file to the client.

   private static void sendBytes( FileInputStream fis, OutputStream os ) throws Exception
   {
      // Construct a 1K buffer to hold bytes on their way to the socket.
      byte[] buffer = new byte[1024] ;
      int bytes = 0 ;

      // Copy requested file into the socket's output stream.
      while ( ( bytes = fis.read( buffer ) ) != -1 ) 
      {
         os.write( buffer, 0, bytes );
      }
   }

Both read() and write() throw exceptions. Instead of catching these exceptions and handling them in our code, we throw them to be handled by the calling method.

The variable, buffer, is our intermediate storage space for bytes on their way from the file to the output stream. When we read the bytes from the FileInputStream, we check to see if read() returns minus one, indicating that the end of the file has been reached. If the end of the file has not been reached, read() returns the number of bytes that have been placed into buffer. We use the write() method of the OutputStream class to place these bytes into the output stream, passing to it the name of the byte array, buffer, the starting point in the array, 0, and the number of bytes in the array to write, bytes.

The final piece of code needed to complete the Web server is a method that will examine the extension of a file name and return a string that represents it's MIME type. If the file extension is unknown, an empty string is returned.

   private static String contentType( String fileName )
   {
      if ( fileName.endsWith(".htm") || fileName.endsWith(".html") )
      {
         return "text/html" ;
      }
      if ( ? )
      {
         ? ;
      }
      if ( ? )
      {
         ? ;
      }
      return "" ;
   }

There is a lot missing from this method. For instance, nothing is returned for GIF or JPEG files. You may want to add the missing file types yourself, so that the components of your home page are sent with the content type correctly specified in the content type header line.

This completes the code for the second phase of development of your Web server. Try running the server from the directory where your home page is located, and try viewing your home page files with a browser. Remember to include a port specifier in the URL of your home page.