|
Design of an Improved HTTP Server
A Simple Network Application in Java |
|
Prof. David Bernstein |
| Computer Science Department |
| bernstdh@jmu.edu |
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);
}
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.
}
}
}
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;
}
}
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;
}
}
}
}
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);
}
}
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);
}
}
}