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); } } }