JMU
Design of an Improved HTTP Server
A Simple Network Application in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Comments on our Current Design
Improving the Current Design
The Servlet Interface
javaexamples/http/v3/HttpServlet.java
import java.io.*;


/**
 * The requirements of HTTP servlets.
 *
 * Most servlets will extend the AbstractHttpServlet class rather
 * than implement this interface directly.  This interface exists
 * to allow for the creation of servlets that cannot take advantage
 * of the functionality in AbstractHttpServlet.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 0.3
 */
public interface HttpServlet
{
    /**
     * Service an HTTP request.
     *
     * @param req    The request to read from
     * @param res    The response to write to
     */
    public abstract void service(HttpRequest req, HttpResponse res);
    


    /**
     * Set the streams for this HttpServlet
     *
     * @param in  The HttpInputStream to read from
     * @param out The HttpOutputStream to write to
     */
    public abstract void setStreams(HttpInputStream in, HttpOutputStream out);
}
        
An Improved HTTP Connection Handler
javaexamples/http/v3/HttpConnectionHandler.java
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.*;


/**
 * Handle an HTTP 1.0 connection in a new thread of execution
 *
 * This version:
 *
 *    Handles all requests using servlets
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 0.3
 */
public class HttpConnectionHandler implements Runnable
{
    private HttpInputStream     in;
    private HttpOutputStream    out;
    private Logger              logger;    
    private Socket              socket;


    /**
     * Explicit Value Constructor
     * (Starts the thread of execution)
     *
     * @param s    The TCP socket for the connection
     */
    public HttpConnectionHandler(Socket s)
    {
       // Setup the logging system
       logger     = Logger.getLogger("edu.jmu.cs");

       socket = s;
    }


    /**
     * The entry point for the thread
     */
    public void run()
    {
       HttpServlet            servlet;
       HttpServletFactory     factory;
       HttpRequest            request;
       HttpResponse           response;
       InputStream            is;
       OutputStream           os;

       try 
       {
          // Get the I/O streams for the socket
          is = socket.getInputStream();
          os = socket.getOutputStream();

          in  = new HttpInputStream(is);
          out = new HttpOutputStream(os);          

          // Create an empty request and response
          request = new HttpRequest();
          response = new HttpResponse();

          try
          {
             // Read and parse the request information
             request.read(in);

             // Log the request for debugging purposes
             logger.log(Level.CONFIG, request.toString());

             // Process the request
             factory = HttpServletFactory.createFactory();
             servlet = factory.createServlet(request, in, out);
             servlet.service(request, response);
          }
          catch (Exception e)
          {
             response.sendError(HttpResponse.SC_BAD_REQUEST, out);
          }
       } 
       catch (IOException ioe)
       {
          // I/O problem so terminate the thread.
          // The server should close the socket.
       }
    }
}
        
An Abstract Servlet
javaexamples/http/v3/AbstractHttpServlet.java
import java.io.*;


/**
 * An abstract class that should be subclassed to create 
 * an HTTP servlet.
 *
 * A subclass of HttpServlet must override at least one 
 * of the following: doGet(), doPut()
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 0.3
 */
public abstract class AbstractHttpServlet implements HttpServlet
{
    protected HttpInputStream         in;
    protected HttpOutputStream        out;

    /**
     * Default Constructor
     */
    public AbstractHttpServlet()
    {
       in = null;
       out = null;
    }

    /**
     * Explicit Value Constructor
     */
    public AbstractHttpServlet(HttpInputStream in, HttpOutputStream out)
    {
       setStreams(in, out);
    }

    /**
     * Called by the server (via the service method) to 
     * allow a servlet to handle a GET request
     *
     * @param req    The request to read from
     * @param res    The response to write to
     */
    protected void doGet(HttpRequest req, HttpResponse res)
    {
        res.sendError(HttpResponse.SC_FORBIDDEN, out);
    }



    /**
     * Called by the server (via the service method) to 
     * allow a servlet to handle a POST request
     *
     * @param req    The request to read from
     * @param res    The response to write to
     */
    protected void doPost(HttpRequest req, HttpResponse res)
    {
        res.sendError(HttpResponse.SC_FORBIDDEN, out);
    }


    /**
     * Dispatches HTTP requests to the doXXX methods. There's no need to 
     * override this method.
     *
     * @param req    The request to read from
     * @param res    The response to write to
     */
    public void service(HttpRequest req, HttpResponse res)
    {
        String      method;

        method = null;
        if (req != null) method   = req.getMethod().toUpperCase();


        if      ((req == null) || (method == null))
                            res.sendError(HttpResponse.SC_BAD_REQUEST, out);
        else if (method.equals("GET"))  doGet(req, res);
        else if (method.equals("POST")) doPost(req, res);
        else res.sendError(HttpResponse.SC_NOT_IMPLEMENTED, out);
    }

    /**
     * Set the streams for this HttpServlet
     *
     * @param in  The HttpInputStream to read from
     * @param out The HttpOutputStream to write to
     */
    public void setStreams(HttpInputStream in, HttpOutputStream out)
    {
       this.in  = in;
       this.out = out;       
    }
}
        
Invoking Servlets
A Servlet Factory
javaexamples/http/v3/HttpServletFactory.java
import java.io.*;


/**
 * Constructs servlets appropriate based on 
 * the file-type in the request.
 *
 * Note that the Singleton pattern is used to construct
 * the factory itself.
 *
 * @version 0.3
 * @author  Prof. David Bernstein, James Madison University
 */
public class HttpServletFactory
{
    // For the Singleton pattern
    private static HttpServletFactory  instance = new HttpServletFactory();

    // Actual attributes
    private File                file;
    private long                lastModified;
    private NameValueMap        associations;

    private static final String       FILE_NAME            
                                      = "associations.dat";
    private static final NameValueMap DEFAULT_ASSOCIATIONS 
                                      = NameValueMap.createNameValueMap();

    /**
     * Default Constructor
     */
    private HttpServletFactory()
    {
       file = new File(FILE_NAME);
       lastModified = -1;
       associations = NameValueMap.createNameValueMap();
       loadAssociations();
    }

    /**
     * Create an HttpServletFactory
     */
    public static HttpServletFactory createFactory()
    {
       return instance;
    }

    /**
     * Construct an HttpServlet
     *
     * @param req   The HTTP request
     * @param in    The HttpInputStream to read from
     * @param out   The HttpOutputStream to write to
     */
    public HttpServlet createServlet(HttpRequest req, 
                                     HttpInputStream in, HttpOutputStream out)
    {
       Class                  c;
       HttpServlet            servlet;
       String                 className, ext, fname;

       servlet   = null;

       loadAssociations();
       fname     = req.getRequestURI();
       ext       = FileTyper.getExtension(fname);
       className = associations.getValue(ext);

       if (className == null) 
       {
          servlet = new DefaultHttpServlet(in, out);
       }
       else if (className.equals("TemplatedHttpServlet"))
       {
          servlet = new TemplatedHttpServlet(in, out);
       }
        

       return servlet;
    }

    /**
     * Load the associations between file types and
     * servlets (if they have changed on disk)
     */
    private void loadAssociations()
    {
       BufferedReader     in;
       long               modified;
        

       modified = file.lastModified();
       if (modified > lastModified) 
       {
          try 
          {
             in = new BufferedReader(new FileReader(new File(FILE_NAME)));
             associations.clear();
             associations.putPairs(in, "\\s");
             lastModified = modified;
          } 
          catch (Exception e) 
          {
             associations = DEFAULT_ASSOCIATIONS;
          }
       }
    }
}
        
A Default Servlet
javaexamples/http/v3/DefaultHttpServlet.java
import java.io.*;
import java.net.*;


/**
 * The "default" servlet.
 *
 * This servlet handles "normal" GET and POST requests.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 0.3
 */
public class DefaultHttpServlet extends AbstractHttpServlet
{
    /**
     * Default Constructor
     */
    public DefaultHttpServlet()
    {
       super();
    }

    /**
     * Explicit Value Constructor
     *
     * @param in    The HttpInputStream to read from
     * @param out   The HttpOutputStream to write to
     */
    public DefaultHttpServlet(HttpInputStream in, HttpOutputStream out)
    {
       super(in, out);
    }
    

    /**
     * Handle a GET request
     *
     * @param req    The request to read from
     * @param res    The response to write to
     */
    protected void doGet(HttpRequest req, HttpResponse res)
    {
       byte[]             content;
       FileInputStream    fis;
       int                length;
       MIMETyper          mimeTyper;
       SecurityManager    security;       
       String             uri;


       // Initialization
       mimeTyper = MIMETyper.createInstance();
       uri       = "../public_html/"+ req.getRequestURI();

       // Get the SecurityManager
       security = System.getSecurityManager();

       try 
       {
          // Check for read permission before doing anything
          if (security != null) security.checkRead(uri);

          // Create a stream for the file
          // and determine its length
          fis = new FileInputStream(uri);
          length = fis.available();

          // Set the status
          res.setStatus(HttpResponse.SC_OK);
                
          // Set the content type
          res.setContentType(mimeTyper.getContentTypeFor(uri));

          // Read the file
          content = new byte[length];
          fis.read(content);
          fis.close();
                
          // Put the data in the response
          res.setContent(content);
                
          // Transmit the response
          res.write(out);
       } 
       catch (SecurityException se)
       {
          res.sendError(HttpResponse.SC_FORBIDDEN, out);
       }
       catch (IOException ioe)
       {
          res.sendError(HttpResponse.SC_NOT_FOUND, out);
       }
    }


    /**
     * Handle a POST request
     *
     * @param req    The request to read from
     * @param res    The response to write to
     */
    protected void doPost(HttpRequest req, HttpResponse res)
    {
       res.sendError(HttpResponse.SC_FORBIDDEN, out);
    }
}
        
An Example
An Example (cont.)
javaexamples/http/v3/TemplatedHttpServlet.java
import java.io.*;
import java.net.*;


/**
 * A servlet that inserts content into a template.
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 0.3
 */
public class TemplatedHttpServlet extends AbstractHttpServlet
{

    /**
     * Default Constructor
     */
    public TemplatedHttpServlet()
    {
       super();
    }

    /**
     * Explicit Value Constructor
     *
     * @param in    The HttpInputStream to read from
     * @param out   The HttpOutputStream to write to
     */
    public TemplatedHttpServlet(HttpInputStream in, HttpOutputStream out)
    {
       super(in, out);
    }
    

    /**
     * Handle a GET request
     *
     * @param req    The request to read from
     * @param res    The response to write to
     */
    protected void doGet(HttpRequest req, HttpResponse res)
    {
       byte[]             content;
       FileInputStream    fisBottom, fisContent, fisTop;
       int                length, lengthBottom, lengthContent, lengthTop;
       MIMETyper          mimeTyper;
       SecurityManager    security;       
       String             uri;


       // Initialization
       mimeTyper = MIMETyper.createInstance();
       uri = "../public_html"+req.getRequestURI();

       // Get the SecurityManager
       security = System.getSecurityManager();

       try 
       {
          // Check for read permission before doing anything
          if (security != null) security.checkRead(uri);

          // Create streams for the content
          // and the templates
          fisTop     = new FileInputStream("../public_html/top.tmpl");
          fisContent = new FileInputStream(uri);
          fisBottom  = new FileInputStream("../public_html/bottom.tmpl");
          lengthTop     = fisTop.available(); 
          lengthContent = fisContent.available();
          lengthBottom  = fisBottom.available();
          length = lengthTop + lengthContent + lengthBottom;
          
          // Set the status
          res.setStatus(HttpResponse.SC_OK);
          
          // Set the content type
          res.setContentType("text/html");
          
          // Read the files
          content = new byte[length];
          fisTop.read(content, 0, lengthTop);
          fisContent.read(content, lengthTop, lengthContent);
          fisBottom.read(content, lengthTop+lengthContent, lengthBottom);
          fisTop.close();
          fisContent.close();
          fisBottom.close();
          
          // Put the content in the response
          res.setContent(content);
          
          // Transmit the response
          res.write(out);
       } 
       catch (SecurityException se)
       {
          res.sendError(HttpResponse.SC_FORBIDDEN, out);
       }
       catch (IOException ioe)
       {
          res.sendError(HttpResponse.SC_NOT_FOUND, out);
       }
    }

}