Design and Implementation of an HTTP Server
A Simple Network Application in Java |
Prof. David Bernstein |
Computer Science Department |
bernstdh@jmu.edu |
import java.io.*; import java.net.*; import java.util.*; /** * An encapsulation of an HTTP request * * This version: * * Does not handle headers * Does not handle query string parameters * * @author Prof. David Bernstein, James Madison University * @version 0.0 */ public class HttpRequest { private String method, requestURI, version; private URI uri; /** * Default Constructor */ public HttpRequest() { method = null; requestURI = null; version = null; uri = null; } /** * Returns the name of the HTTP method with which this request was made, * (for example, GET, POST, or PUT) * * @return The method */ public String getMethod() { return method; } /** * Returns the part of this request's URI from the protocol name * up to the query string in the first line of the HTTP request * * @return The URI */ public String getRequestURI() { return uri.getPath(); } /** * Returns the the URI including the protocol name, host, and path * but not the query string * * @return The URL */ public String getRequestURL() { return uri.getScheme()+"://"+getRequestURI(); } /** * Read this request * * @param in The HttpInputStream to read from */ public void read(HttpInputStream in) { String line, request, token, value; String[] tokens; try { line = in.readHttpLine(); tokens = line.split("\\s"); method = tokens[0]; request = tokens[1]; if (tokens.length > 2) version = tokens[2]; else version = "HTTP/0.9"; // Parse the URI tokens = request.split("?"); requestURI = tokens[0].substring(1); try { uri = new URI(requestURI); } catch (URISyntaxException urise) { uri = null; } } catch (IndexOutOfBoundsException ioobe) { // There was a problem processing the request } catch (IOException ioe) { // There was a problem reading the request or // processing the headers } } /** * Returns a String representation of this Object * * @return The String representation */ public String toString() { Enumeration e; String name, s, value; s = "Method: \n\t" + getMethod() + "\n"; s += "URI: \n\t" + getRequestURI()+"\n"; return s; } }
import java.io.*; import java.text.*; import java.util.*; /** * An encapsulation of an HTTP response * * This version: * * Only supports two header elements, Content-Type and Content-Length * * @author Prof. David Bernstein, James Madison University * @version 0.0 */ public class HttpResponse { private byte[] data; private int contentLength, status; private NumberFormat nf; private String contentType; public static final int SC_ACCEPTED = 202; public static final int SC_BAD_GATEWAY = 502; public static final int SC_BAD_REQUEST = 400; public static final int SC_CREATED = 201; public static final int SC_FORBIDDEN = 403; public static final int SC_INTERNAL_ERROR = 500; public static final int SC_MOVED = 301; public static final int SC_NO_RESPONSE = 204; public static final int SC_NOT_FOUND = 404; public static final int SC_NOT_IMPLEMENTED = 501; public static final int SC_OK = 200; public static final int SC_PARTIAL_INFORMATION = 203; public static final int SC_PAYMENT_REQUIRED = 402; public static final int SC_SERVICE_OVERLOADED = 503; public static final int SC_UNAUTHORIZED = 401; /** * Default Constructor * */ public HttpResponse() { nf = NumberFormat.getIntegerInstance(); status = SC_OK; data = null; } /** * Get the default message associated with a status code * * @param sc The status code * @return The associated default message */ public static String getStatusMessage(int sc) { switch (sc) { case SC_ACCEPTED: return "Accepted"; case SC_BAD_GATEWAY: return "Bad Gateway"; case SC_BAD_REQUEST: return "Bad Request"; case SC_CREATED: return "Created"; case SC_FORBIDDEN: return "Forbidden"; case SC_INTERNAL_ERROR: return "Internal Error"; case SC_MOVED: return "Moved"; case SC_NO_RESPONSE: return "No Response"; case SC_NOT_FOUND: return "Not Found"; case SC_NOT_IMPLEMENTED: return "Not Implemented"; case SC_OK: return "OK"; case SC_PARTIAL_INFORMATION: return "Partial Information"; case SC_PAYMENT_REQUIRED: return "Payment Required"; case SC_SERVICE_OVERLOADED: return "Service Overloaded"; case SC_UNAUTHORIZED: return "Unauthorized"; default: return "Unknown Status Code " + sc; } } /** * Send an error response to the client. * * After using this method, the response should be considered to * be committed and should not be written to. * * @param sc The status code * @param out The HttpOutputStream to write to */ public void sendError(int sc, HttpOutputStream out) { String errorHTML; errorHTML = "<HTML><BODY><P>HTTP Error "+ sc + " - " + getStatusMessage(sc)+ "</P></BODY></HTML>\r\n"; setStatus(sc); setData(errorHTML.getBytes()); try { write(out); } catch (IOException ioe) { // Nothing can be done } } /** * Sets the status code for this response * * @param sc The status code */ public void setStatus(int sc) { status = sc; } /** * Sets the length of the content the server returns to the client * * @param length The length (in bytes) */ public void setContentLength(int length) { contentLength = length; } /** * Sets the type of the content the server returns to the client * * @param type The type */ public void setContentType(String type) { contentType = type; } /** * Set the payload/data for this HttpResponse * * @param data The payload/data */ public void setData(byte[] data) { this.data = data; } /** * Write this HttpResponse * * @param out The HttpOutputStream to write to */ public void write(HttpOutputStream out) throws IOException { if (data != null) setContentLength(data.length); else setContentLength(0); writeStatusLine(out); writeHeaders(out); if (data != null) out.write(data); out.flush(); out.close(); } /** * Write the headers to an output stream * * @param out The HttpOutputStream to write to */ private void writeHeaders(HttpOutputStream out) { boolean hasHeaders; Enumeration e; out.printHeaderLine("Content-Length", String.valueOf(contentLength)); out.printHeaderLine("Content-Type", contentType); out.printEOL(); out.flush(); } /** * Write the status line to an output stream * * @param out The HttpOutputStream to write to */ private void writeStatusLine(HttpOutputStream out) { out.print("HTTP/1.0"); out.print(" "); nf.setMaximumIntegerDigits(3); nf.setMinimumIntegerDigits(3); out.print(nf.format(status)); out.print(" "); out.print(getStatusMessage(status)); out.printEOL(); out.flush(); } }
Map
but they really have more
funtionality than we need (making them unsafe) NameValueMap
ClassMap
but don't support
the entire interface (as you would with the decorator)Map
but override the unnecessary
methodsNameValueMap
Class (cont.)import java.io.*; import java.util.*; import java.util.concurrent.*; /** * A mapping of name=value pairs. * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class NameValueMap { private Map<String,String> delegate; /** * Explicit Value Constructor * * @param delegate The Map to delegate to */ private NameValueMap(Map<String,String> delegate) { this.delegate = delegate; } /** * Construct a NameValueMap (that is not thread safe) */ public static NameValueMap createNameValueMap() { return new NameValueMap(new HashMap<String,String>()); } /** * Construct a thread-safe NameValueMap */ public static NameValueMap createConcurrentNameValueMap() { return new NameValueMap(new ConcurrentHashMap<String,String>()); } /** * Remove all name=value pairs from this map. */ public void clear() { try { delegate.clear(); } catch (UnsupportedOperationException uoe) { // Couldn't clear // // Note: We could iterate through each element and remove() // it but this could cause a concurrent modification problem. // So, this would only work if the iterator supported the // remove() operation. } } /** * Get all of the names * * @return All of the names in this Map */ public Iterator<String> getNames() { return delegate.keySet().iterator(); } /** * Get the value for a particular name * * @param name The name of interest * @return The corresponding value (or null) */ public String getValue(String name) { return delegate.get(name); } /** * Add a name=value pair to this map. * * @param name The name * @param value The corresponding value */ public void put(String name, String value) { delegate.put(name, value); } /** * Add a name=value pair to this map. * * Note: Only the first occurrence of the delimiter is significant. * The delimiter may appear in the value. * * @param pair The String containing the name=value pair * @param regex The delimiter between the name and value */ public void putPair(String pair, String regex) { String value; String[] components; components = pair.split(regex); if (components.length == 2) delegate.put(components[0], components[1]); if (components.length > 2) { value = ""; for (int i=1; i<components.length; i++) value += components[i]; delegate.put(components[0], value); } } /** * Add a name=value pair to this map (assuming the delimiter is * the '=' character). * * @param pair The String containing the name=value pair */ public void putPair(String pair) { putPair(pair, "="); } /** * Add one or more name=value pairs to this map. * * @param pairs The BufferedReader containing the lines of pairs * @param regexLine The delimiter between the different pairs * @param regexPair The delimiter between the name and value in each pair */ public void putPairs(String pairs, String regexLine, String regexPair) { String[] components, lines; lines = pairs.split(regexLine); for (int i=0; i<lines.length; i++) { putPair(lines[i], regexPair); } } /** * Add one or more name=value pairs to this map. * * This method assumes that the pairs are delimited by "&" and the * names and values are delimited by "=". * * @param pairs The BufferedReader containing the lines of pairs */ public void putPairs(String pairs) { putPairs(pairs, "&", "="); } /** * Add one or more name=value pairs to this map. * * This method reads from the BufferedReader until either an * end-of-stream is encountered or a line contains the String "". * * In the event of an IOException, this method will return (but * will not remove any pairs that might have been added). * * @param in The BufferedReader containing the lines of pairs * @param regex The delimiter between name and value in each pair */ public void putPairs(BufferedReader in, String regex) { String line; try { while (((line=in.readLine()) != null) && !line.equals("")) { putPair(line, regex); } } catch (IOException ioe) { } } /** * Add one or more name=value pairs to this map. * * This method reads from the BufferedReader until either an * end-of-stream is encountered or a line contains the String "". * * In the event of an IOException, this method will return (but * will not remove any pairs that might have been added). * * @param in The HttpInputStream containing the lines of pairs * @param regex The delimiter between name and value in each pair */ public void putPairs(HttpInputStream in, String regex) { String line; try { while (((line=in.readHttpLine()) != null) && !line.equals("")) { putPair(line, regex); } } catch (IOException ioe) { } } }
import java.io.*; import java.net.*; import java.util.*; /** * An encapsulation of an HTTP request * * This version: * * Handles headers * Handles query string parameters * * @author Prof. David Bernstein, James Madison University * @version 0.1 */ public class HttpRequest { private int contentLength; private NameValueMap headers, queryParameters; private String method, queryString, version; private URI uri; /** * Default Constructor */ public HttpRequest() { method = null; queryString = null; version = null; } /** * Returns the name of the HTTP method with which this request was made, * (for example, GET, POST, or PUT) * * @return The method */ public String getMethod() { return method; } /** * Returns the part of this request's URI from the protocol name * up to the query string in the first line of the HTTP request * * @return The URI */ public String getRequestURI() { String path; path = uri.getPath(); if ((path == null) || path.equals("")) return "index.html"; else return path; } /** * Read this request * * @param in The HttpInputStream to read from */ public void read(HttpInputStream in) throws IndexOutOfBoundsException, IOException, URISyntaxException { InputStream tis; String line, request, token, value; String[] pair, tokens; line = in.readHttpLine(); tokens = line.split("\\s"); method = tokens[0]; request = tokens[1]; if (tokens.length > 2) version = tokens[2]; else version = "HTTP/0.9"; // Parse the URI uri = new URI(request); // Get the decoded query string queryString = uri.getQuery(); // Process the query string queryParameters = NameValueMap.createNameValueMap(); if (queryString != null) queryParameters.putPairs(queryString,"&","="); // Process the headers headers = NameValueMap.createNameValueMap(); headers.putPairs(in, ":"); // Get the content length token = headers.getValue("Content-Length"); try { contentLength = Integer.parseInt(token.trim()); } catch (Exception e) { contentLength = -1; } } /** * Returns a String representation of this Object * * @return The String representation */ public String toString() { Iterator<String> i; String name, s, value; s = "Method: \n\t" + getMethod() + "\n"; s += "URI: \n\t" + getRequestURI()+"\n"; s += "Parameters:\n"+ queryString + "\n"; s += "Headers:\n"; i = headers.getNames(); while (i.hasNext()) { name = i.next(); value = headers.getValue(name); s += "\t" + name + "\t" + value + "\n"; } return s; } }
import java.io.*; import java.text.*; import java.util.*; /** * An encapsulation of an HTTP response * * This version: * * Addse support for headers * * @author Prof. David Bernstein, James Madison University * @version 0.1 */ public class HttpResponse { private byte[] data; private int contentLength, status; private NumberFormat nf; private NameValueMap headers; private String contentType; public static final int SC_ACCEPTED = 202; public static final int SC_BAD_GATEWAY = 502; public static final int SC_BAD_REQUEST = 400; public static final int SC_CREATED = 201; public static final int SC_FORBIDDEN = 403; public static final int SC_INTERNAL_ERROR = 500; public static final int SC_MOVED = 301; public static final int SC_NO_RESPONSE = 204; public static final int SC_NOT_FOUND = 404; public static final int SC_NOT_IMPLEMENTED = 501; public static final int SC_OK = 200; public static final int SC_PARTIAL_INFORMATION = 203; public static final int SC_PAYMENT_REQUIRED = 402; public static final int SC_SERVICE_OVERLOADED = 503; public static final int SC_UNAUTHORIZED = 401; /** * Default Constructor * */ public HttpResponse() { headers = NameValueMap.createNameValueMap(); nf = NumberFormat.getIntegerInstance(); status = SC_OK; data = null; } /** * Get the default message associated with a status code * * @param sc The status code * @return The associated default message */ public static String getStatusMessage(int sc) { switch (sc) { case SC_ACCEPTED: return "Accepted"; case SC_BAD_GATEWAY: return "Bad Gateway"; case SC_BAD_REQUEST: return "Bad Request"; case SC_CREATED: return "Created"; case SC_FORBIDDEN: return "Forbidden"; case SC_INTERNAL_ERROR: return "Internal Error"; case SC_MOVED: return "Moved"; case SC_NO_RESPONSE: return "No Response"; case SC_NOT_FOUND: return "Not Found"; case SC_NOT_IMPLEMENTED: return "Not Implemented"; case SC_OK: return "OK"; case SC_PARTIAL_INFORMATION: return "Partial Information"; case SC_PAYMENT_REQUIRED: return "Payment Required"; case SC_SERVICE_OVERLOADED: return "Service Overloaded"; case SC_UNAUTHORIZED: return "Unauthorized"; default: return "Unknown Status Code " + sc; } } /** * Send an error response to the client. * * After using this method, the response should be considered to * be committed and should not be written to. * * @param sc The status code * @param out The HttpOutputStream to write to */ public void sendError(int sc, HttpOutputStream out) { String errorHTML; errorHTML = "<HTML><BODY><P>HTTP Error "+ sc + " - " + getStatusMessage(sc)+ "</P></BODY></HTML>\r\n"; setStatus(sc); setData(errorHTML.getBytes()); try { write(out); } catch (IOException ioe) { // Nothing can be done } } /** * Sets the length of the content the server returns to the client * * @param length The length (in bytes) */ public void setContentLength(int length) { contentLength = length; setHeader("Content-Length", Integer.toString(contentLength)); } /** * Sets the type of the content the server returns to the client * * @param type The type */ public void setContentType(String type) { contentType = type; setHeader("Content-Type", contentType); } /** * Set the payload/data for this HttpResponse * * @param data The payload/data */ public void setData(byte[] data) { this.data = data; } /** * Adds a field (with the given name and value) to the * response header * * Note: This method must be called before getOutputStream() * * @param name The name of the field * @param value The value of the field */ public void setHeader(String name, String value) { headers.put(name, value); } /** * Sets the status code for this response * * @param sc The status code */ public void setStatus(int sc) { status = sc; } /** * Write this HttpResponse * * @param out The HttpOutputStream to write to */ public void write(HttpOutputStream out) throws IOException { if (data != null) setContentLength(data.length); else setContentLength(0); writeStatusLine(out); writeHeaders(out); if (data != null) out.write(data); out.flush(); out.close(); } /** * Write the headers * * @param out The HttpOutputStream to write to */ private void writeHeaders(HttpOutputStream out) { Iterator<String> i; String name, value; i = headers.getNames(); while (i.hasNext()) { name = i.next(); value = headers.getValue(name); if ((value != null) && (!value.equals(""))) out.printHeaderLine(name, value); } out.printEOL(); out.flush(); } /** * Write the status line * * @param out The HttpOutputStream to write to */ private void writeStatusLine(HttpOutputStream out) { out.print("HTTP/1.0"); out.print(" "); nf.setMaximumIntegerDigits(3); nf.setMinimumIntegerDigits(3); out.print(nf.format(status)); out.print(" "); out.print(getStatusMessage(status)); out.printEOL(); out.flush(); } }
HttpRequest
and
HttpResponse
classesimport java.io.*; import java.net.*; import java.util.*; /** * A partial encapsulation of an HTTP message (i.e., request * or response) * * @author Prof. David Bernstein, James Madison University * @version 0.2 */ public abstract class HttpMessage { protected byte[] content; protected NameValueMap headers; /** * Default Constructor */ public HttpMessage() { content = null; headers = NameValueMap.createNameValueMap(); } /** * Get the content of this HttpMessage * * @return The content */ public byte[] getContent() { return content; } /** * Returns the length, in bytes, of the content contained in the * request and sent by way of the input stream * * @return The length or -1 if the length is not known */ public int getContentLength() { int result; try { result = Integer.parseInt(getHeader("Content-Length")); } catch (Exception e) { result = -1; } return result; } /** * Returns the value of the specified header * * @param name The name of the header * @return The value of the header */ public String getHeader(String name) { return headers.getValue(name); } /** * Returns the names of all headers * * @return The names of all headers */ public Iterator<String> getHeaderNames() { return headers.getNames(); } /** * Read this HttpMessage (up to, but not including, the * content). * * Note: The content is not read so that the content may * be read in a specialized way (e.g., as formatted binary data) * or in case the content is large (and should be "streamed"). * * @param in The HttpInputStream to read from */ public abstract void read(HttpInputStream in) throws IndexOutOfBoundsException, IOException, URISyntaxException; /** * Read the content of this HttpMessage. * Note: This method must only be called after read() * in the child classes. * * @param in The HttpInputStream to read from */ public void readContent(HttpInputStream in) { int contentLength; contentLength = getContentLength(); if (contentLength > 0) { content = new byte[contentLength]; try { in.readFully(content); } catch (IOException ioe) { content = null; setContentLength(-1); } } } /** * Set the content (i.e., payload) for this HttpMessage * * @param content The payload/content */ public void setContent(byte[] content) { this.content = content; setContentLength(content.length); } /** * Sets the content length * * @param contentLength The contentLength */ public void setContentLength(int contentLength) { setHeader("Content-Length", Integer.toString(contentLength)); } /** * Sets the type of the content the server returns to the client * * @param type The type */ public void setContentType(String type) { setHeader("Content-Type", type); } /** * Adds a field (with the given name and value) to the * response header * * Note: This method must be called before getOutputStream() * * @param name The name of the field * @param value The value of the field */ public void setHeader(String name, String value) { headers.put(name, value); } /** * Set the headers * * @param headers The headers */ public void setHeaders(NameValueMap headers) { this.headers = headers; } }
import java.io.*; import java.net.*; import java.util.*; /** * An encapsulation of an HTTP request * * This version: * * Extends the abstract class HttpMessage * * @author Prof. David Bernstein, James Madison University * @version 0.2 */ public class HttpRequest extends HttpMessage { private NameValueMap queryParameters; private String method, queryString, version; private URI uri; /** * Default Constructor */ public HttpRequest() { super(); method = null; queryString = null; version = null; } /** * Returns the name of the HTTP method with which this request was made, * (for example, GET, POST, or PUT) * * @return The method */ public String getMethod() { return method; } /** * Returns the part of this request's URI from the protocol name * up to the query string in the first line of the HTTP request * * @return The URI */ public String getRequestURI() { String path; if (uri == null) path = null; else path = uri.getPath(); if ((path == null) || path.equals("")) return "index.html"; else return path; } /** * Read this HttpRequest (up to, but not including, the * content). * * Note: The content is not read so that the content may * be read in a specialized way (e.g., as formatted binary data) * or in case the content is large (and should be "streamed"). * * @param in The HttpInputStream to read from */ public void read(HttpInputStream in) throws IndexOutOfBoundsException, IOException, URISyntaxException { String line, request, token, value; String[] pair, tokens; line = in.readHttpLine(); tokens = line.split("\\s"); method = tokens[0]; request = tokens[1]; if (tokens.length > 2) version = tokens[2]; else version = "HTTP/0.9"; // Parse the URI uri = new URI(request); // Get the decoded query string queryString = uri.getQuery(); // Process the query string queryParameters = NameValueMap.createNameValueMap(); if (queryString != null) queryParameters.putPairs(queryString,"&","="); // Process the headers headers = NameValueMap.createNameValueMap(); headers.putPairs(in, ":"); // Get the content length token = headers.getValue("Content-Length"); try { setContentLength(Integer.parseInt(token.trim())); } catch (Exception e) { setContentLength(-1); } } /** * Set the method */ public void setMethod(String method) { this.method = method; } /** * Set the URI */ public void setURI(URI uri) { this.uri = uri; } /** * Set the version */ public void setVersion(String version) { this.version = version; } /** * Returns a String representation of this Object * * @return The String representation */ public String toString() { Iterator<String> i; String name, s, value; s = "Method: \n\t" + getMethod() + "\n"; s += "URI: \n\t" + getRequestURI()+"\n"; s += "Parameters:\n"+ queryString + "\n"; s += "Headers:\n"; i = getHeaderNames(); while (i.hasNext()) { name = i.next(); value = headers.getValue(name); s += "\t" + name + "\t" + value + "\n"; } return s; } /** * Write this HttpRequest * * @param out The HttpOutputStream to write to */ public void write(HttpOutputStream out) { Iterator<String> i; String name, value; out.printHttpLine(method+" "+getRequestURI()+" HTTP/"+version); i = getHeaderNames(); while (i.hasNext()) { name = i.next(); value = headers.getValue(name); out.printHeaderLine(name, value); } out.printEOL(); try { if (content != null) out.write(content); } catch (IOException ioe) { // Couldn't write the content } out.flush(); } }
import java.io.*; import java.text.*; import java.util.*; /** * An encapsulation of an HTTP response * * This version adds: * * Extends the abstract class HttpMessage * * @author Prof. David Bernstein, James Madison University * @version 0.2 */ public class HttpResponse extends HttpMessage { private int status; private NumberFormat nf; public static final int SC_ACCEPTED = 202; public static final int SC_BAD_GATEWAY = 502; public static final int SC_BAD_REQUEST = 400; public static final int SC_CREATED = 201; public static final int SC_FORBIDDEN = 403; public static final int SC_INTERNAL_ERROR = 500; public static final int SC_MOVED = 301; public static final int SC_NO_RESPONSE = 204; public static final int SC_NOT_FOUND = 404; public static final int SC_NOT_IMPLEMENTED = 501; public static final int SC_OK = 200; public static final int SC_PARTIAL_INFORMATION = 203; public static final int SC_PAYMENT_REQUIRED = 402; public static final int SC_SERVICE_OVERLOADED = 503; public static final int SC_UNAUTHORIZED = 401; /** * Default Constructor * */ public HttpResponse() { super(); nf = NumberFormat.getIntegerInstance(); status = SC_OK; } /** * Get the status associated with this HttpResponse * * @return The status */ public int getStatus() { return status; } /** * Get the default message associated with a status code * * @param sc The status code * @return The associated default message */ public static String getStatusMessage(int sc) { switch (sc) { case SC_ACCEPTED: return "Accepted"; case SC_BAD_GATEWAY: return "Bad Gateway"; case SC_BAD_REQUEST: return "Bad Request"; case SC_CREATED: return "Created"; case SC_FORBIDDEN: return "Forbidden"; case SC_INTERNAL_ERROR: return "Internal Error"; case SC_MOVED: return "Moved"; case SC_NO_RESPONSE: return "No Response"; case SC_NOT_FOUND: return "Not Found"; case SC_NOT_IMPLEMENTED: return "Not Implemented"; case SC_OK: return "OK"; case SC_PARTIAL_INFORMATION: return "Partial Information"; case SC_PAYMENT_REQUIRED: return "Payment Required"; case SC_SERVICE_OVERLOADED: return "Service Overloaded"; case SC_UNAUTHORIZED: return "Unauthorized"; default: return "Unknown Status Code " + sc; } } /** * Read this HttpResponse (up to, but not including, the * content). * * Note: The content is not read so that the content may * be read in a specialized way (e.g., as formatted binary data) * or in case the content is large (and should be "streamed"). * * @param in The HttpInputStream to read from */ public void read(HttpInputStream in) { String line; String[] tokens; try { line = in.readHttpLine(); tokens = line.split("\\s"); try { setStatus(Integer.parseInt(tokens[1])); } catch (NumberFormatException nfe) { setStatus(-1); } headers.putPairs(in, ":"); } catch (IOException ioe) { setStatus(SC_NO_RESPONSE); } } /** * Send an error response to the client. * * After using this method, the response should be considered to * be committed and should not be written to. * * @param sc The status code * @param out The HttpOutputStream to write to */ public void sendError(int sc, HttpOutputStream out) { String errorHTML; errorHTML = "<HTML><BODY><P>HTTP Error "+ sc + " - " + getStatusMessage(sc)+ "</P></BODY></HTML>\r\n"; setStatus(sc); setContent(errorHTML.getBytes()); try { write(out); } catch (IOException ioe) { // Nothing can be done } } /** * Sets the status code for this response * * @param sc The status code */ public void setStatus(int sc) { status = sc; } /** * Returns a String representation of this Object * * @return The String representation */ public String toString() { Iterator<String> i; String name, s, value; s = "Status: \n\t" + status + "\n"; s += "Headers:\n"; i = getHeaderNames(); while (i.hasNext()) { name = i.next(); value = headers.getValue(name); s += "\t" + name + "\t" + value + "\n"; } return s; } /** * Write this HttpResponse * * @param out The HttpOutputStream to write to */ public void write(HttpOutputStream out) throws IOException { if (content != null) setContentLength(content.length); else setContentLength(0); writeStatusLine(out); writeHeaders(out); if (content != null) out.write(content); out.flush(); out.close(); } /** * Write the headers to an output stream * * @param out The HttpOutputStream to write to */ private void writeHeaders(HttpOutputStream out) { Iterator<String> i; String name, value; i = headers.getNames(); while (i.hasNext()) { name = i.next(); value = headers.getValue(name); if ((value != null) && (!value.equals(""))) out.printHeaderLine(name,value); } out.printEOL(); out.flush(); } /** * Write the status line to an output stream * * @param out The HttpOutputStream to write to */ private void writeStatusLine(HttpOutputStream out) { out.print("HTTP/1.0"); out.print(" "); nf.setMaximumIntegerDigits(3); nf.setMinimumIntegerDigits(3); out.print(nf.format(status)); out.print(" "); out.print(getStatusMessage(status)); out.printEOL(); out.flush(); } }
MIMETyper
Class:
NameValueMap
so we don't want to construct
a new one every time it is needed (hence, should use the
Singleton pattern)MIMETyper
Classimport java.io.*; import java.net.FileNameMap; /** * A utility class for working with MIME types * * Note: This class makes use of the Singleton Pattern since there * is never need for more than one MIMETyper. The MIMETyper class is * thread-safe, * * @author Prof. David Bernstein, James Madison University * @version 1.0 */ public class MIMETyper implements FileNameMap { private NameValueMap types; private static MIMETyper instance = new MIMETyper(); private static final String DEFAULT = "application/octet-stream"; /** * Default Constructor */ private MIMETyper() { types = NameValueMap.createConcurrentNameValueMap(); initializeTypes(); } /** * Create an instance of a MIMETyper if necessary. * Otherwise, return the existing instance. * * @return The instance */ public static MIMETyper createInstance() { return instance; } /** * Guess the MIME type from a file extension * * @param ext The extension (e.g., ".gif") * @return The MIME type (e.g., "image/gif") */ public String getContentTypeForExtension(String ext) { String type; type = types.getValue(ext.toLowerCase()); if (type == null) type = DEFAULT; return type; } /** * Guess the MIME type from a file name * (possibly including a path) * * @param name The name (e.g., "/pictures/dome.gif") * @return The MIME type (e.g., "image/gif") */ public String getContentTypeFor(String name) { String ext; ext = FileTyper.getExtension(name); return getContentTypeForExtension(ext); } /** * Initialize the types table */ private void initializeTypes() { BufferedReader in; String line; try { in = new BufferedReader(new FileReader("mimetypes.dat")); types.putPairs(in, "\t"); } catch (IOException ioe) { types.put(".htm","text/html"); types.put(".html","text/html"); types.put(".text","text/plain"); } } }
import java.io.*; import java.net.*; import java.util.*; /** * Handle an HTTP 1.0 connection in a new thread of execution * * This version: * * Only supports GET requests * * @author Prof. David Bernstein, James Madison University * @version 0.2 */ public class HttpConnectionHandler implements Runnable { private HttpInputStream in; private HttpOutputStream out; private MIMETyper mimeTyper; private Socket socket; /** * Explicit Value Constructor * (Starts the thread of execution) * * @param s The TCP socket for the connection */ public HttpConnectionHandler(Socket s) { socket = s; mimeTyper = MIMETyper.createInstance(); } /** * The entry point for the thread */ public void run() { HttpRequest request; HttpResponse response; InputStream is; OutputStream os; String method; 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 response = new HttpResponse(); request = new HttpRequest(); try { // Read and parse the request information request.read(in); // Determine the method to use method = request.getMethod().toUpperCase(); // Respond to the request if ((request == null) || (method == null)) response.sendError(HttpResponse.SC_BAD_REQUEST, out); else if (!method.equals("GET")) response.sendError(HttpResponse.SC_NOT_IMPLEMENTED, out); else doGet(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. } } /** * Handle the GET request * * @param request Contents of the request * @param response Used to generate the response */ private void doGet(HttpRequest request, HttpResponse response) { byte[] content; FileInputStream fis; int length; String uri; // NOTE: We should check to make sure that the // URI is in a "public" portion of the file system uri = "../public_html"+request.getRequestURI(); try { // Create a stream for the file // and determine its length fis = new FileInputStream(uri); length = fis.available(); response.setStatus(HttpResponse.SC_OK); // Set the content type response.setContentType(mimeTyper.getContentTypeFor(uri)); // Read the file content = new byte[length]; fis.read(content); // Set the payload response.setContent(content); //Write the response response.write(out); // Close the file fis.close(); } catch (IOException ioe) { response.sendError(HttpResponse.SC_NOT_FOUND, out); } } }
import java.io.*; import java.net.*; import java.util.concurrent.*; import java.util.logging.*; /** * A simplified HTTP server * * Note: This version only supports GET requests * * @author Prof. David Bernstein, James Madison University * @version 3.0 */ public class HttpServer implements Runnable { private volatile boolean keepRunning; private final ExecutorService threadPool; private final ServerSocket serverSocket; private Thread controlThread; private static Logger logger; private static final int MAX_THREADS = 100; // Should be tuned /** * The entry point of the application * * @param args The command line arguments */ public static void main(String[] args) { BufferedReader in; HttpServer server; Handler logHandler; // Setup the logging system logger = Logger.getLogger("edu.jmu.cs"); try { logHandler = new FileHandler("log.txt"); logHandler.setFormatter(new SimpleFormatter()); logger.addHandler(logHandler); logger.setLevel(Level.parse(args[0])); logger.setUseParentHandlers(false); } catch (Exception e) { // The FileHandler couldn't be constructed or the Level was bad // so use the default ConsoleHandler (at the default Level.INFO) logger.setUseParentHandlers(true); } server = null; try { in = new BufferedReader(new InputStreamReader(System.in)); // Construct and start the server server = new HttpServer(); server.start(); System.out.println("Press [Enter] to stop the server..."); // Block until the user presses [Enter] in.readLine(); } catch (IOException ioe) { System.out.println(" Stopping because of an IOException"); } // Stop the server if (server != null) server.stop(); } /** * Default COnstructor */ public HttpServer() throws IOException { serverSocket = new ServerSocket(8080); logger.log(Level.INFO, "Created Server Socket on 8080"); threadPool = Executors.newFixedThreadPool(MAX_THREADS); serverSocket.setSoTimeout(5000); } /** * The code to run in the server's thread of execution */ public void run() { HttpConnectionHandler connection; Socket s; while (keepRunning) { try { s = serverSocket.accept(); logger.log(Level.INFO, "Accepted a connection"); connection = new HttpConnectionHandler(s); // Add the connection to a BlockingQueue<Runnable> object // and, ultimately, call it's run() method in a thread // in the pool threadPool.execute(connection); } catch (SocketTimeoutException ste) { // The accept() method timed out. Check to see if // the thread should keep running or not. } catch (IOException ioe) { // Problem with accept() } } stopPool(); controlThread = null; } /** * Stop the threads in the pool */ private void stopPool() { // Prevent new Runnable objects from being submitted threadPool.shutdown(); try { // Wait for existing connections to complete if (!threadPool.awaitTermination(5, TimeUnit.SECONDS)) { // Stop executing threads threadPool.shutdownNow(); // Wait again if (!threadPool.awaitTermination(5, TimeUnit.SECONDS)) { logger.log(Level.INFO, "Could not stop thread pool."); } } } catch (InterruptedException ie) { // Stop executing threads threadPool.shutdownNow(); // Propagate the interrupt status controlThread.interrupt(); } } /** * Start the thread of execution */ public void start() { if (controlThread == null) { controlThread = new Thread(this); keepRunning = true; controlThread.start(); } } /** * Stop the thread of execution (after it finishes the * current connection) */ public void stop() { keepRunning = false; } }