programming project 1 web server - villanova computer sciencechan/csc8560/project/webserver.pdf ·...

22
Programming Project 1 Web Server CSC 8560 – Computer Networks Section 001 Spring 2007 Villanova University CSC Department Instructor : Dr. Paul Schragger Name : Charlie Han

Upload: duongphuc

Post on 13-Mar-2019

222 views

Category:

Documents


0 download

TRANSCRIPT

Programming Project 1Web Server

CSC 8560 – Computer NetworksSection 001Spring 2007

Villanova University CSC DepartmentInstructor : Dr. Paul Schragger

Name : Charlie Han

1. Project Description

In this project, I developed a web server that can handle multiple incoming HTTP GET requests

and send HTTP response message back to the client, with appropriate Content-type in the response.

According to the project description, these are the requirements of the web server:

● Listen on a specified port, other than the standard port 80, and accept HTTP requests.

● Handle each HTTP request in a separate thread.

● Handle HTTP version 1.0 GET requests – not including conditional GET

● Extract filename from HTTP request and return the file or an appropriate error message.

● Return a HTTP response message and close the client socket connection.

● Return appropriate status code, e.g. 200 (OK) or 404 (NOT FOUND), in the response.

● Determine and send the correct MIME type specifier in the response message.

Optional (extra credit) requirements are as follows:

● Implement and demonstrate at least two of the client errors found on Page 35 of RFC 1945.

● Implement an XML formatted configuration file control of the startup of the web server.

2. Design and Implementation

Initial design and the basic architecture of the web server followed the project description, although

I diverged from it as I developed the project.

2.1. WebServer class

The main class of the project is the WebServer, which has little change from the project

description. I added two constants SERVER_PORT (8102) and BACKLOG (10) to be used when

the ServerSocket object is created. Also added is a static httpReqCount integer variable to keep

track of the number of HTTP counts received. This number gets incremented every time a new

request is accepted, and gets appended to the HttpRequest thread name, for better debugging

purposes.

The heart of the class lies in the infinite loop that listens for client connection and hands off client

socket to a separate thread running HttpRequest object, as shown in the code snippet below:

// Process HTTP service requests in an infinite loop.while (true){

Socket client_socket = null;

try{

// Listen for a TCP connection request.client_socket = socket.accept(); Thread processThread = new Thread(new HttpRequest(client_socket));processThread.setName("HttpRequest-"+httpReqCount);//processThread.setDaemon(true);processThread.start();

}catch (IOException ioe){

// exception in accept()...

}...

}

2.2. HttpRequest class

HttpRequest implements Runnable, so it can be instantiated as a Thread. The constructor takes the

client socket as input that was retrieved from the server socket listening on the SERVER_PORT for

client requests, and saves it for later usage. Its run() method simply calls processRequest() and

handles any Exception it may have thrown.

The processRequest() method get references to the client socket’s input and output streams, parse

the HTTP request message, construct and send an appropriate request message, and closes the

streams and the client socket. The client socket is closed as we are implementing a version 1.0

HTTP server for this project. Below is the code snippet of the processRequest() method:

try

{// Get a reference to the socket's input and output streams.is = socket.getInputStream();os = new DataOutputStream(socket.getOutputStream());

// Set up input stream filters.br = new BufferedReader(new InputStreamReader(is), BUFFER_IN_SIZE);

// Parse the request line of the HTTP request message.String requestLine = br.readLine();String errorMsg = parseRequestLine(requestLine);

// Get and display the remainder of the header lines....

if (errorMsg == null){

// Open the requested file....

}else{

// DEBUG: display error message while parsing request messageSystem.out.println();System.out.println(errorMsg);

}

// Construct and send response message.sendResponseMessage(fis, os);

}finally{

// Close streams and socket (HTTP/1.0)....socket.close();

}

The parseRequestLine() method implements the logic to extract the request method (GET, HEAD,

POST, etc.), requested filename, and HTTP version information. It checks for the validity of the

request line and determines which status code will be used in the response message. The return

value of this method is a String object which is the error message or null for successful parsing of

the request line. For example, if the request line does not conform by not having three fields

(method, filename, and version), a BAD REQUEST (400) status code will be given and an

appropriate error message is returned. Other cases include NOT FOUND (404) when specified file

is not found, FORBIDDEN (403) when the requested file is not readable, and HTTP VERSION

NOT SUPPORTED (505) when HTTP version is not HTTP/1.0 or HTTP/1.1.

StringTokenizer tokens = new StringTokenizer(requestLine);if (tokens.countTokens() != 3){

code = StatusCode.BAD_REQUEST;return "Request line is malformed. Returning BAD REQUEST.";

}

// Extract requested method from the request line (GET, POST, HEAD, etc.)String method = tokens.nextToken().toUpperCase();

// Currently we only support the "GET" methodif (!method.equals("GET")){

code = StatusCode.NOT_IMPLEMENTED;return "Received unsupported " + method + " request. " +

"Returning NOT IMPLEMENTED.";}

// Extract the filename from the request line.String fileName = tokens.nextToken();

...

// Check if file existsif (!file.exists()){

code = StatusCode.NOT_FOUND;return "Requested file " + fileName + " does not exist. " +

"Returning NOT FOUND.";}

In addition, I implemented to search for an index.html or index.htm file if a directory is specified in

the request line. If neither is found, a NOT FOUND (404) status code is returned. If both of them

exist, an INTERNAL SERVER ERROR (500) is returned, unlike a typical apache implementation

where it takes one of them. How the status codes are designed is discussed in the following section.

The sendResponseMessage() method is responsible for sending a response to the client. It sends out

the status line with the appropriate status code, header lines (which currently only has a content-

type line), and entity body. How the status code is used to generate the correct response message is

discussed in the next section. The MIME type is retrieved from a pre-combiled map of filename

extension to the content type string. This is constructed as shown below:

final static String DEFAULT_CONTENT_TYPE = "application/octet-stream";...final static Properties CONTENT_TYPES = new Properties();...static{

// Map filename extensions to content-type strings// TODO: set this by reading configuration fileCONTENT_TYPES.setProperty("html", "text/html");CONTENT_TYPES.setProperty("htm", "text/html");CONTENT_TYPES.setProperty("txt", "text/plain");...

}

From this static mapping, the implementation of contentType() becomes very simple and scalable:

private String contentType(String fileName){

String fname = fileName.toLowerCase();int lastdot = fname.lastIndexOf(".");if ((lastdot != -1) && (lastdot != fname.length()-1))

return CONTENT_TYPES.getProperty(fname.substring(lastdot+1), DEFAULT_CONTENT_TYPE);

return DEFAULT_CONTENT_TYPE;}

2.3. StatusCode enum

Java 5 introduces the enum keyword that is somewhat similar to the old C-like enum. Java enums

are classes, however, and provides extra functionalities and type-safe operations. The HttpRequest

class has a StatusCode enum declaration at the beginning that specifies all status codes

implemented for this web server project:

/** * Enumeration of HTTP response status codes */enum StatusCode{

OK,MOVED_PERMANENTLY,MOVED_TEMPORARILY,BAD_REQUEST,FORBIDDEN,NOT_FOUND,INTERNAL_SERVER_ERROR,NOT_IMPLEMENTED,HTTP_VERSION_NOT_SUPPORTED,

}

Given the StatusCode enum class, I constructed an EnumMap to map these enum values to the

actual String representation of the status codes:

final static EnumMap<StatusCode, String> SCODES= new EnumMap<StatusCode, String>(StatusCode.class);

static{

...// Map enums to status code stringsSCODES.put(StatusCode.OK, "200");SCODES.put(StatusCode.MOVED_PERMANENTLY, "301");SCODES.put(StatusCode.MOVED_TEMPORARILY, "302");SCODES.put(StatusCode.BAD_REQUEST, "400");SCODES.put(StatusCode.FORBIDDEN, "403");SCODES.put(StatusCode.NOT_FOUND, "404");SCODES.put(StatusCode.INTERNAL_SERVER_ERROR, "500");SCODES.put(StatusCode.NOT_IMPLEMENTED, "501");SCODES.put(StatusCode.HTTP_VERSION_NOT_SUPPORTED, "505");

}

The parseRequestLine() method assigns the correct enum code to the HttpRequest object. The

sendResponseMessage() method then uses this code to construct the correct status line and entity

body for the outgoing response message:

private void sendResponseMessage(FileInputStream fis, DataOutputStream os) throws Exception{

String statusLine = "HTTP/" + HTTP_VERSION + " " + SCODES.get(code) + " "; String entityBody = "<HTML>" + CRLF +

" <HEAD><TITLE>?</TITLE></HEAD>" + CRLF + " <BODY>?</BODY>" + CRLF + "</HTML>";

// Construct message string to be sentString message;switch (code) {

case OK:message = "OK";break;

case MOVED_PERMANENTLY:message = "Moved Permanently";break;

...

default:message = "What is this???";

}

statusLine += message;if (code != StatusCode.OK)

entityBody = entityBody.replaceAll("\\?", message");...// Construct header lines with contentType() and send out all lines

}

3. Execution of Program and Test ResultsI have problem uploading files to the CSC department at Villanova, where my homepage is being

hosted, so I’m running my web server at my home server at port 8102. I placed a few different

types of files under the files/ folder under where the web server is running. Here are a few outputs:

1. Request to a directory that has a single index.html file:

and here’s the console output of my webserver in the above case:

Received HTTP request:GET / HTTP/1.1Host: localhost:8102User-Agent: Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7Keep-Alive: 300Connection: keep-alivestatusLine: HTTP/1.0 200 OKentityBody:<HTML> <HEAD><TITLE>?</TITLE></HEAD> <BODY>?</BODY></HTML>Sending requested file to client...

2. Request to a directory that has both index.html and index.htm file:

and here’s the console output of my webserver in the above case:

GET /files HTTP/1.1Host: localhost:8102User-Agent: Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.8.1) Gecko/20061010 Firefox/2.0Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7Keep-Alive: 300Connection: keep-alive

Found more than one index file at requested location ./files. Returning INTERNAL SERVER ERROR.statusLine: HTTP/1.0 500 Internal Server ErrorentityBody:<HTML> <HEAD><TITLE>Internal Server Error - sent by Charlie's WebServer</TITLE></HEAD> <BODY>Internal Server Error - sent by Charlie's WebServer</BODY></HTML>Sending error message to client...

3. Request to a specific htm file:

4. Request to a specific html file:

5. Request for a non-existing file:

6. Request for a PPT file:

7. Request for a GIF file:

8. Request for a CSS file:

9. Request for a txt file:

10. Request for a PDF file:

4. ConclusionsThis project provided a good hands on experience in implementing one of the many well known

application layer protocol. The implementation of the web server was very straightforward, so was

the implementation of the HTTP request parsing and constructing HTTP response message. This is

mostly due to the fact that HTTP is a text base protocol.

For a web server like program, it is easy to get complex due to too many status codes, status

messages, different types of error message to be sent to the client, etc. However, a good design can

be used that can handle these in a generic manner, such as the StatusCode enum construct or a

static Properties object for the mapping of the filename extensions to MIME types strings.

Due to time constraints, I wasn’t able to implement the Xml configuration, but it would be a great

way to provide configurations for the web server. In addition to the port number, document

directory and log directory, additional fields can be put together into a configuration file that can

include fields such as backlog, showDirectoryContents, etc. Even the content-type mapping can be

put together into a configuration file and loaded when the server starts up.

5. Bibliography

None

Appendix A – WebServer.java

import java.net.ServerSocket;import java.net.Socket;import java.net.BindException;import java.io.IOException;

/** * Main web server class that establishes a server socket, * listens to client connections, and deligate HTTP processing. * HTTP requests are handled by HttpRequest. * * @see HttpRequest */public final class WebServer{

final static int SERVER_PORT = 8102;final static int BACKLOG = 10;

/** HttpRequest thread counter */static int httpReqCount = 0;

// TODO: add signal handler and statements to close server socket

public static void main(String[] args){

ServerSocket socket = null;

try{

// Establish the listen socket.socket = new ServerSocket(SERVER_PORT, BACKLOG);

}catch (BindException be){

System.out.println("Failed to bind to port " + SERVER_PORT + ": " + be.getMessage());System.out.println("Make sure the port is available and no other instance is running");

return;}catch (IOException ioe){

System.out.println("Exception occurred while establishing socket connection: "+ ioe.getMessage());

ioe.printStackTrace();

return;}

// Process HTTP service requests in an infinite loop.while (true){

Socket client_socket = null;

try{

// Listen for a TCP connection request.client_socket = socket.accept(); Thread processThread = new Thread(new HttpRequest(client_socket));processThread.setName("HttpRequest-"+httpReqCount);//processThread.setDaemon(true);processThread.start();

}catch (Exception e){

System.out.println("Exception occurred while accepting client connection: " + e.getMessage());

e.printStackTrace();}

}}

}

Appendix B – HttpRequest.java

import java.net.Socket;import java.util.EnumMap;import java.util.Properties;import java.util.StringTokenizer;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FilenameFilter;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.DataOutputStream;import java.io.BufferedReader;

/** * Enumeration of HTTP response status codes */enum StatusCode{

OK,MOVED_PERMANENTLY,MOVED_TEMPORARILY,BAD_REQUEST,FORBIDDEN,NOT_FOUND,INTERNAL_SERVER_ERROR,NOT_IMPLEMENTED,HTTP_VERSION_NOT_SUPPORTED,

}

/** * Handles a single instance of client HTTP request, * and sends an appropriate HTTP response to the client. * * @version 1.0 Handles GET request and a limited set of content-types */final class HttpRequest implements Runnable{

final static String CRLF = "\r\n";final static String HTTP_VERSION = "1.0";final static String DEFAULT_CONTENT_TYPE = "application/octet-stream";final static int BUFFER_IN_SIZE = 1024;final static int BUFFER_OUT_SIZE = 1024;

final static Properties CONTENT_TYPES = new Properties();

final static EnumMap<StatusCode, String> SCODES = new EnumMap<StatusCode, String>(StatusCode.class);

static{

// Map filename extensions to content-type strings// TODO: set this by reading configuration fileCONTENT_TYPES.setProperty("html", "text/html");CONTENT_TYPES.setProperty("htm", "text/html");CONTENT_TYPES.setProperty("txt", "text/plain");CONTENT_TYPES.setProperty("c", "text/plain");CONTENT_TYPES.setProperty("cc", "text/plain");CONTENT_TYPES.setProperty("c++", "text/plain");CONTENT_TYPES.setProperty("cpp", "text/plain");CONTENT_TYPES.setProperty("h", "text/plain");CONTENT_TYPES.setProperty("java", "text/plain");CONTENT_TYPES.setProperty("pl", "text/plain");CONTENT_TYPES.setProperty("css", "text/css");CONTENT_TYPES.setProperty("gif", "image/gif");CONTENT_TYPES.setProperty("xbm", "image/x-xbitmap");CONTENT_TYPES.setProperty("xpm", "image/x-xpixmap");CONTENT_TYPES.setProperty("png", "image/x-png");CONTENT_TYPES.setProperty("jpeg", "image/jpeg");CONTENT_TYPES.setProperty("jpg", "image/jpeg");CONTENT_TYPES.setProperty("jpe", "image/jpeg");CONTENT_TYPES.setProperty("bmp", "image/x-ms-bmp");//CONTENT_TYPES.setProperty("ico", "?");CONTENT_TYPES.setProperty("au", "audio/basic");CONTENT_TYPES.setProperty("snd", "audio/basic");

CONTENT_TYPES.setProperty("wav", "audio/x-wav");CONTENT_TYPES.setProperty("mpeg", "video/mpeg");CONTENT_TYPES.setProperty("mpg", "video/mpeg");CONTENT_TYPES.setProperty("mpe", "video/mpeg");CONTENT_TYPES.setProperty("qt", "video/quicktime");CONTENT_TYPES.setProperty("mov", "video/quicktime");CONTENT_TYPES.setProperty("avi", "video/x-msvideo");CONTENT_TYPES.setProperty("rtf", "application/rtf");CONTENT_TYPES.setProperty("pdf", "application/pdf");CONTENT_TYPES.setProperty("doc", "application/msword");CONTENT_TYPES.setProperty("tar", "application/x-tar");CONTENT_TYPES.setProperty("zip", "application/zip");CONTENT_TYPES.setProperty("bin", "application/octet-stream");CONTENT_TYPES.setProperty("exe", "application/octet-stream");CONTENT_TYPES.setProperty("js", "application/x-javascript");CONTENT_TYPES.setProperty("sh", "application/x-sh");CONTENT_TYPES.setProperty("csh", "application/x-csh");CONTENT_TYPES.setProperty("pl", "application/x-perl");CONTENT_TYPES.setProperty("tcl", "application/x-tcl");CONTENT_TYPES.setProperty("ppt", "application/vnd.ms-powerpoint");

// Map enums to status code stringsSCODES.put(StatusCode.OK, "200");SCODES.put(StatusCode.MOVED_PERMANENTLY, "301");SCODES.put(StatusCode.MOVED_TEMPORARILY, "302");SCODES.put(StatusCode.BAD_REQUEST, "400");SCODES.put(StatusCode.FORBIDDEN, "403");SCODES.put(StatusCode.NOT_FOUND, "404");SCODES.put(StatusCode.INTERNAL_SERVER_ERROR, "500");SCODES.put(StatusCode.NOT_IMPLEMENTED, "501");SCODES.put(StatusCode.HTTP_VERSION_NOT_SUPPORTED, "505");

}

StatusCode code;Socket socket;File requestedFile;

// Constructorpublic HttpRequest(Socket socket) throws Exception

{this.socket = socket;this.code = null;this.requestedFile = null;

}

// Implement the run() method of the Runnable interface.public void run()

{try{

processRequest();}catch (Exception e){

System.out.println("Exception occurred while processing request: ");e.printStackTrace();

} }

/** * Processes incoming HTTP requests * <p> * v0.9 submittal version - does what is requested for the assignment * (unconditional GET requests) * * @throws Exception */private void processRequest() throws Exception{

InputStream is = null;DataOutputStream os = null;FileInputStream fis = null;BufferedReader br = null;

try{

// Get a reference to the socket's input and output streams.is = socket.getInputStream();

os = new DataOutputStream(socket.getOutputStream());

// Set up input stream filters.br = new BufferedReader(new InputStreamReader(is), BUFFER_IN_SIZE);

// TODO: set document base

// Parse the request line of the HTTP request message.String requestLine = br.readLine();String errorMsg = parseRequestLine(requestLine);

// Get and display the remainder of the header lines.// TODO: do more parsing, such as If-Modified-Since, Accept-Language, etc. String headerLine = null;while ((headerLine = br.readLine()).length() != 0){

System.out.println(headerLine);}

if (errorMsg == null){

// Open the requested file.try{

fis = new FileInputStream(requestedFile);}catch (FileNotFoundException e){

System.out.println("FileNotFoundException while opening file input stream.");

e.printStackTrace();code = StatusCode.NOT_FOUND;

}}else{

// DEBUG: display error message while parsing request messageSystem.out.println();System.out.println(errorMsg);

}

// Construct and send response message.sendResponseMessage(fis, os);

}finally{

// Close streams and socket (HTTP/1.0).if (os != null) os.close();if (br != null) br.close();if (fis != null) fis.close();//if (is != null) is.close();socket.close();

}}

private String parseRequestLine(String requestLine){

// DEBUG: Display the request line.// TODO: add log4j debuggingSystem.out.println();System.out.println("Received HTTP request:");System.out.println(requestLine);

StringTokenizer tokens = new StringTokenizer(requestLine);if (tokens.countTokens() != 3){

code = StatusCode.BAD_REQUEST;return "Request line is malformed. Returning BAD REQUEST.";

}

// Extract requested method from the request line (GET, POST, HEAD, etc.)String method = tokens.nextToken().toUpperCase();

// Currently we only support the "GET" methodif (!method.equals("GET")){

code = StatusCode.NOT_IMPLEMENTED;return "Received unsupported " + method + " request. " +

"Returning NOT IMPLEMENTED.";

}

// Extract the filename from the request line.String fileName = tokens.nextToken();

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

// TODO: Check if permanently/temporarily moved file

// Check if file existsif (!file.exists()){

code = StatusCode.NOT_FOUND;return "Requested file " + fileName + " does not exist. " +

"Returning NOT FOUND.";}

// Check if file is readableif (!file.canRead()){

code = StatusCode.FORBIDDEN;return "Requested file " + fileName + " is not readable. " +

"Returning with FORBIDDEN.";}

// If filename is a directory, including "./", look for index filesif (file.isDirectory()){

File[] list = file.listFiles(new FilenameFilter() {public boolean accept(File dir, String f) {

if (f.equalsIgnoreCase("index.htm") || f.equalsIgnoreCase("index.html"))return true;

// TODO: allow more index file extensions

return false; }

});

// Return NOT FOUND response when no index file is found in a directory// TODO: implement configuration flag exposeDirectoryif (list == null || list.length == 0){

code = StatusCode.NOT_FOUND;return "No index file found at requested location " + fileName + " Returning NOT FOUND";

}

// Excuse to return INTERNAL SERVER ERROR, if there are more than one index fileelse if (list.length != 1){

code = StatusCode.INTERNAL_SERVER_ERROR;return "Found more than one index file at requested location " +

fileName + ". Returning INTERNAL SERVER ERROR.";}

// Found index file.file = list[0];

}

// Requested file seems ok. Let's remember it.requestedFile = file;

// Extract HTTP version from the request lineString version = tokens.nextToken().toUpperCase();if (!version.matches("HTTP/([1-9][0-9.]*)")){

code = StatusCode.BAD_REQUEST;return "HTTP version string is malformed. Returning BAD REQUEST.";

}

if (!version.equals("HTTP/1.0") && !version.equals("HTTP/1.1")){

code = StatusCode.HTTP_VERSION_NOT_SUPPORTED;return version + " not supported. Returning HTTP VERSION NOT SUPPORTED.";

}

// At this point, we can assume a good requestcode = StatusCode.OK;return null;

}

private void sendResponseMessage(FileInputStream fis, DataOutputStream os) throws Exception{

String statusLine = "HTTP/" + HTTP_VERSION + " " + SCODES.get(code) + " "; String entityBody = "<HTML>" + CRLF +

" <HEAD><TITLE>?</TITLE></HEAD>" + CRLF +" <BODY>?</BODY>" + CRLF +"</HTML>";

// Construct message string to be sentString message;switch (code) {

case OK:message = "OK";break;

case MOVED_PERMANENTLY:message = "Moved Permanently";break;

case MOVED_TEMPORARILY:message = "Moved Temporarily";break;

case BAD_REQUEST:message = "Bad Request";break;

case FORBIDDEN:message = "Forbidden";break;

case NOT_FOUND:message = "Not Found";break;

case INTERNAL_SERVER_ERROR:message = "Internal Server Error";break;

case NOT_IMPLEMENTED:message = "Not Implemented";break;

case HTTP_VERSION_NOT_SUPPORTED:message = "HTTP Version Not Supported";break;

default:message = "What is this???";

}

statusLine += message;if (code != StatusCode.OK)

entityBody = entityBody.replaceAll("\\?", message + " - sent by Charlie's WebServer");

// DEBUG: print outgoing message//System.out.println("message: " + message);System.out.println("statusLine: "+ statusLine);System.out.println("entityBody:" + CRLF + entityBody);

// Send the status line.os.writeBytes(statusLine + CRLF);

// Construct and send the header lines.sendHeaderLines(os);

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

// Send the entity body.if (code == StatusCode.OK){

System.out.println("Sending requested file to client...");sendBytes(fis, os);

}else{

System.out.println("Sending error message to client...");os.writeBytes(entityBody);

}}

private void sendHeaderLines(DataOutputStream os) throws Exception

{StringBuffer headerLines = new StringBuffer();

// Content-typeString contentTypeLine = "Content-type: ";switch (code) {

case OK:contentTypeLine += contentType(requestedFile.getName());break;

default:// Body should have some error message as contentcontentTypeLine += "text/html";

}headerLines.append(contentTypeLine + CRLF);

// TODO: add more header fields, Content-Length, etc.

// Send the header linesos.writeBytes(headerLines.toString());

}

/** * Send bytes to the outputstream as they are read from the inputstream * * @param fis FileInputStream to read the bytes * @param os OutputStream connected to the client socket * @throws Exception */private 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[BUFFER_OUT_SIZE];int bytes = 0;

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

os.write(buffer, 0, bytes);}

}

/** * Simplified version to get content-type string from the static mapping * * @param fileName * @return Content-Type string mapped by the filename extension */private String contentType(String fileName){

String fname = fileName.toLowerCase();int lastdot = fname.lastIndexOf(".");if ((lastdot != -1) && (lastdot != fname.length()-1))

return CONTENT_TYPES.getProperty(fname.substring(lastdot+1), DEFAULT_CONTENT_TYPE);

return DEFAULT_CONTENT_TYPE;}

}