Monday, March 15, 2010

Updated Web Server Stuff (WORKING VERSION!!!!)

/** CacheEntry class
Stores a byte and a filename, allowing the cache
to be useful!  Method calls will get the filename or the byte array

Decided not to have set methods because you don't want to be able to fiddle
with a CacheEntry directly, should only construct via filename
and be able to get the file information and send it along
*/

import java.net.*;
import java.util.*;
import java.lang.Object.*;
import java.text.*;
import java.io.*;

public class CacheEntry
{
    //variables
    private byte [] byteFile_ = null;
    private File file_ = null;
    private InputStream fileReadStream = null;
    //constructors
    public CacheEntry()
    {
    }
   
    public CacheEntry(File file) throws IOException
    {
        System.out.println("Creating a new CacheEntry with filename: " + file.toString());
        file_ = file;
        byteFile_ = new byte[(int)file.length()];
        try
        {
            fileReadStream = new BufferedInputStream(new FileInputStream(file_));
            fileReadStream.read(byteFile_);
        }
       
        catch(IOException ioe)
        {
            System.err.println(ioe);
            return;
        }

        finally
        {
            if(fileReadStream != null)
                fileReadStream.close();
        }
    }
   
    //get functions
    public File getFile() {return file_;}
    public byte[] getBytes() {return byteFile_;}
}

/**
 * XML parser for configuration parameters.
 *
 * This maps configuration parameters to a HashMap and are retrieved
 * through the following getter methods:
 *
 *     public String getLogFile()
 *     public String getDocumentRoot()
 *     public String getDefaultDocument()
 *     public String getServerName()
 *
 * Usage:
 *     Configuration config = new Configuration();
 *
 *    config.getLogFile();
 *    config.getDocumentRoot();
 *    config.getDefaultDocument();
 *    config.getServerName();
 */

import java.io.*;

import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;

import java.util.Map;
import java.util.HashMap;

public class Configuration extends DefaultHandler
{
    private Map  map;
    private String configurationFile;

    /**
     * @param File configurationFile - The name of the configuration file
     */
    public Configuration(String configurationFile) throws ConfigurationException {
    this.configurationFile = configurationFile;

    map = new HashMap();

    try {       
            // Use the default (non-validating) parser
            SAXParserFactory factory = SAXParserFactory.newInstance();

            // Parse the input
            SAXParser saxParser = factory.newSAXParser();
            saxParser.parse( new File(configurationFile), this);
    }
    catch (javax.xml.parsers.ParserConfigurationException pce) {
        throw new ConfigurationException("javax.xml.parsers.ParserConfigurationException");
    }
    catch (org.xml.sax.SAXException se) {
        throw new ConfigurationException("org.xml.sax.SAXException");
    }
    catch (java.io.IOException ioe) {
        throw new ConfigurationException("java.io.IOException");
    }
    }


    /**
     * We will map each configuration attribute to its value
     *
     * @param namespaceURI the namespace
     * @param lName the local name of the element
     * @param qName the qualified name of the element
     * @param attrs the set of attributess associated with the element
      */
    public void startElement(String namespaceURI,
                             String lName,    
                             String qName,    
                             Attributes attrs)   
    throws SAXException
    {
        String elementName = lName; // element name
        if ("".equals(elementName))
        elementName = qName; // namespaceAware = false

    /**
     * Get the attributes associated with this ELEMENT.
      * Attributes are name/value pairs and are stored by index.
     */
        if (attrs != null) {
            for (int i = 0; i < attrs.getLength(); i++) {
                String aName = attrs.getLocalName(i); // Attr name
                if ("".equals(aName))
            aName = attrs.getQName(i);

        // map the element.attribute to its value
        map.put(elementName+"."+aName,attrs.getValue(i));
            }
        }
    }

    // getter methods for mapped configuration values

    /** Returns the location of the log file */
    public String getLogFile() {
        return map.get("logfile.log");
    }

    /** Returns the location of the document base */
    public String getDocumentRoot() {
        return map.get("context.documentRoot");
    }

    /** Returns the name of the default document */
    public String getDefaultDocument() {
        return map.get("context.defaultDocument");
    }

    /** Returns the name of the server */
    public String getServerName() {
        return map.get("webserver.title");
    }
   
    /** Returns the name of the 400 file */
    public String get400()
    {
        return map.get("context.fourHundredDocument");
    }
   
    /** Returns the name of the 404 file */
    public String get404()
    {
        return map.get("context.fourOhFourDocument");
    }
}

public class ConfigurationException extends Exception
{
    public ConfigurationException(String exception) {
        super(exception);
    }
}

/** HTTP Request Header
This Class represents a HTTP Request Header
It takes a byte array and Parses it, basically
Author: Daniel J. Tanner
Date:  March 9th, 2010
CMPT 352
*/
import java.io.*;
import java.net.*;
import java.lang.*;

public class HTTPRequestHeader
{
    //fields
    protected static final String DEFAULT_METHOD = "GET";
    protected static final String DEFAULT_PATH = "/index.html";
    protected static final String DEFAULT_PROTOCOL = "HTTP/1.0";
    protected static final int CHUNK = 5028;
    protected String method_;
    protected String path_;
    protected String protocol_;
   
    //Constructors
    public HTTPRequestHeader()
    {
        method_ = DEFAULT_METHOD;
        protocol_ = DEFAULT_PROTOCOL;
        path_ = DEFAULT_PATH;
    }
   
    //This one is basically the meat, it will parse the byte stream it receives
    public HTTPRequestHeader(InputStream fromClient) throws IOException
    {
        BufferedReader input = new BufferedReader(new InputStreamReader(fromClient));
        String requestLine = null;
        String requestParsed = null;
        StringBuffer request = new StringBuffer(CHUNK);
        int i = 0;
        try
        {
            requestParsed = input.readLine();
       
            while((requestLine = input.readLine()) != null)
            {
                if(requestLine.length() == 0) break;
                i += requestLine.length();
                request.append(requestLine);
            }
        }
        catch (IOException ioe)
        {
            ioe.printStackTrace();
            i = -1;
        }
       
        System.out.println("-->"+requestParsed+"<--");
       
        //Now we parse!
        //String requestParsed = request.toString();
        int index1, index2;
        i = requestParsed.length();
        index1 = requestParsed.indexOf(' ');
       
        //getMethod
        if(index1 != -1)
        {
            method_ = requestParsed.substring(0, index1);
        }
        index2 = requestParsed.indexOf(' ', index1 + 1);
       
        //getURI
        if(index2 > index1)
        {
            path_ = requestParsed.substring(index1 + 1, index2);
        }
        index1 = requestParsed.indexOf(requestParsed.length()-1, index2 + 1);
       
        //getProtocol
   
            protocol_ = requestParsed.substring(index2 + 1, requestParsed.length());
       
       
        System.out.println("-->"+method_+"<--");
        System.out.println("-->"+path_+"<--");              
        System.out.println("-->"+protocol_+"<--");
    }
   
    //Get Functions
    public String getMethod() {return method_;}
    public String getProtocol() {return protocol_;}
    public String getPath() {return path_;}
}

/** HTTP Response Header Class
This class takes a byte array and parses it into a HTTP Response Header

Author:  Daniel J. Tanner
Date:    March 9th, 2010
CMPT 352
*/

import java.io.*;
import java.net.*;
import java.lang.*;

public class HTTPResponseHeader
{
    //Fields
    protected String httpVersion_;
    protected String code_;
    protected String message_;
    protected String date_;
    protected String serverName_;
    protected String contentType_;
    protected long contentLength_;
   
    //constants - default values
    protected static final String DEFAULT_CODE = "200";
    protected static final String DEFAULT_MESSAGE = "Ok";
    protected static final String DEFAULT_HTTPVERSION = "HTTP/1.0";
    protected static final String DEFAULT_DATE = "January 1st, 1970";
    protected static final String DEFAULT_SERVERNAME = "So Long, And Thanks For All The Fish!";
    protected static final String DEFAULT_CONTENTTYPE = "matter/dark";
    protected static final long DEFAULT_CONTENTLENGTH = 42;
   
    //Constructors
    public HTTPResponseHeader()
    {
        code_ = DEFAULT_CODE;
        message_ = DEFAULT_MESSAGE;
        httpVersion_ = DEFAULT_HTTPVERSION;
        date_ = DEFAULT_DATE;
        serverName_ = DEFAULT_SERVERNAME;
        contentType_ = DEFAULT_CONTENTTYPE;
        contentLength_ = DEFAULT_CONTENTLENGTH;
    }
   
    public HTTPResponseHeader(String httpVersion, String code, String message, String date, String serverName, String contentType, long contentLength)
    {
        code_ = code;
        message_ = message;
        httpVersion_ = httpVersion;
        date_ = date;
        serverName_ = serverName;
        contentType_ = contentType;
        contentLength_ = contentLength;
    }
   
    //set functions
    public void setCode(String code) {code_ = code;}
    public void setMessage(String message) {message_ = message;}
    public void setHTTPVersion(String httpVersion) {httpVersion_ = httpVersion;}
    public void setDate(String date) {date_ = date;}
    public void setServerName(String serverName) {serverName_ = serverName;}
    public void setContentType(String contentType) {contentType_ = contentType;}
    public void setContentLength(long contentLength) {contentLength_ = contentLength;}
   
    //get functions
    public String getCode() {return code_;}
    public String getMessage() {return message_;}
    public String getHTTPVersion() {return httpVersion_;}
    public String getDate() {return date_;}
    public String getServerName() {return serverName_;}
    public String getContentType() {return contentType_;}
    public long getContentLength() {return contentLength_;}
   
    //print function
    public byte[] getBytes() throws IOException
    {
        String header = httpVersion_ + " " + code_ + " " + message_ + "\r\n"
            + "Date:  " + date_ + "\r\n"
            + "Server:  " + serverName_ + "\r\n"
            + "Content-Type:  " + contentType_ + "\r\n"
            + "Content-Length:  " + contentLength_ + "\r\n\r\n";
        return header.getBytes("US-ASCII");
    }
       
}

/**
Daniel Tanner
CMPT 352 - Computer Networking
January 26th, 2010
Connection Class for threaded DNS Server
(modified version of G. Gagne's code)
 */

import java.net.*;
import java.io.*;
import java.text.*;
import java.util.*;

public class TannConnection implements Runnable
{
    //create variables
    private Socket    client;
    private WeakHashMap cache = null;
    private Configuration configurator = null;
    private File logFile = null;
    private static TannHandler handler = new TannHandler();

    public TannConnection(Socket client, Configuration configurator, File logFile, WeakHashMap cache)
    {
        this.client = client;
        this.configurator = configurator;
        this.cache = cache;
        this.logFile = logFile;
    }

    //Separate thread
    public void run()
    {
        try
        {
            handler.process(client, configurator, logFile, cache);
        }
       
        catch (java.io.IOException ioe)
        {
            System.err.println(ioe);
        }
    }
}

/** Web Server
by Daniel J. Tanner
March 9th, 2010
CMPT 352

This program implements some basic features of the HTTP protocol
**/

import java.io.*;
import java.net.*;
import java.util.*;


public class TannerWebServer
{
    //constants & globals
    public static final int PORT = 2880;
    private static Configuration configurator = null;
    private static WeakHashMap cache = null;
    private static File logFile = null;
   
    public static void main(String[] args) throws java.io.IOException, ConfigurationException
    {
        //begin by initializng the configurator       
        try
        {
            configurator = new Configuration("../conf/config.xml");
        }
       
        catch (ConfigurationException ce)
        {
            System.out.println(ce);
            System.exit(0);
        }
       
        //load the initial files into a hashmap
        cache = new WeakHashMap(20);
        cache.put(configurator.getDefaultDocument(), new CacheEntry(new File(configurator.getDefaultDocument())));
        cache.put(configurator.get400(), new CacheEntry(new File(configurator.get400())));
        cache.put(configurator.get404(), new CacheEntry(new File(configurator.get404())));
       
        //load logfile
        logFile = new File(configurator.getLogFile());
       
        //Create socket
        ServerSocket sock = null;
        try
        {
            //establish socket
            sock = new ServerSocket(PORT);
           
            while(true)
            {
                Socket client = sock.accept();
                //debug testing
                InetAddress ipAddr = client.getInetAddress();
                System.out.print(ipAddr.getHostAddress() + " : ");
                System.out.println(client.getPort());
                Thread worker = new Thread(new TannConnection(client, configurator, logFile, cache));
               
                worker.start();
            }
        }
        //catch any errors opening sockets
        catch(IOException ioe)
        {
            System.err.println(ioe);
            System.exit(0);
        }
       
        finally
        {
            //close streams
            if(sock != null)
                sock.close();
        }
    }
}


/**
Daniel Tanner
CMPT 352 - Computer Networking
January 26th, 2010
Handler class for threaded DNS Server
 */

import java.net.*;
import java.lang.Object.*;
import java.util.*;
import java.text.*;
import javax.activation.*;
import java.io.*;

public class TannHandler
{       
    //Create variables
    private Configuration configurator = null;
    private WeakHashMap cache = null;
    private File logFile = null;
    private InputStream fromClient = null;
    private BufferedOutputStream toClient = null;
    private CacheEntry requestedFile = null;
    private HTTPRequestHeader requestHeader = null;
    private HTTPResponseHeader responseHeader = null;
   
    //This method invoked as a separate thread
    public void process(Socket client, Configuration configurator, File logFile, WeakHashMap cache) throws IOException
    {   
        this.configurator = configurator;
        this.logFile = logFile;
        this.cache = cache;
        //try block for opening streams and getting hostname
        try
        {
            //open Streams
            fromClient = (client.getInputStream());
            toClient = new BufferedOutputStream(client.getOutputStream());

            //Get Request
            requestHeader = new HTTPRequestHeader(fromClient);
            responseHeader = new HTTPResponseHeader();
           
            //process Request
            processRequest(client);
        }
       
        catch(IOException ioe)
        {
            System.err.println(ioe);
            return;
        }
       
                finally
                {
                    // close streams and socket
                    if (fromClient != null)
                        fromClient.close();
                    if (toClient != null)
                        toClient.close();
                    if (client != null)
                        client.close();
                }
    }
   
    /** This function will process the HTTP request
    If GET, will try to find the file in the cache.
    If the file isn't in the hashmap it will add it to the cache.
    If the file doesn't exist, will send the 404 error
    the default response is to send the 400 error
    */
    public void processRequest(Socket client) throws IOException
    {
        //GET command
        System.out.println("Currently the request has method " + requestHeader.getMethod());
        String docRoot = configurator.getDocumentRoot();
        //if(requestHeader.getMethod() == "GET" || requestHeader.getMethod() == "get")
        if( (requestHeader.getMethod()).equals("GET") || (requestHeader.getMethod().equals("get")) )
        {
            //check if the path is asking for the default
            if(requestHeader.getPath().equals("/"))
            {
                //check cache
                System.out.println("You have entered the default path.");
                String defaultDoc = configurator.getDefaultDocument();
                if(cache.containsKey(defaultDoc))
                {
                    requestedFile = cache.get(defaultDoc);
                }
               
                //not in cache, load it in
                else
                {
                    requestedFile = new CacheEntry(new File(defaultDoc));
                    cache.put(defaultDoc, requestedFile);
                }
            }
           
            //check if it's in cache, if it's not, add it to cache
            else if(cache.containsKey(requestHeader.getPath()))
            {               
                requestedFile = cache.get(requestHeader.getPath());
            }
            else
            {
                //open new File
                File requestedFileCheck = new File(docRoot + requestHeader.getPath());
                               
                //404 error
                if(!requestedFileCheck.exists())
                {
                    System.out.println("You have entered the 404 code");
                    responseHeader.setCode("404");
                    responseHeader.setMessage("File Not Found");
               
                    //Write 404 page to client
                    if(cache.containsKey(configurator.get404()))               
                        requestedFile = cache.get(configurator.get404());
                    else
                    {
                        requestedFile = new CacheEntry(new File(configurator.get404()));
                        cache.put(configurator.get404(), requestedFile);
                    }
                    System.out.println(requestedFile.getFile().toString());
                }
                else
                {
                    requestedFile = new CacheEntry(requestedFileCheck);
                    cache.put(requestHeader.getPath(), requestedFile);
                }
            }
        }
       
        //Bad request
        else
        {
            responseHeader.setCode("400");
            responseHeader.setMessage("Bad Request");
           
            //write 400 page to client
            if(cache.containsKey(configurator.get400()))
                requestedFile = cache.get(configurator.get400());
            else
            {
                requestedFile = new CacheEntry(new File(configurator.get400()));
                cache.put(configurator.get400(), requestedFile);
            }
        }
       
        //Fill out responseHeader headers
        DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
        Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "GMT"));
        df.setCalendar(cal);
        Date now = new Date();
        javax.activation.MimetypesFileTypeMap someMimes = new javax.activation.MimetypesFileTypeMap();
        responseHeader.setDate(df.format(now));
        responseHeader.setServerName(configurator.getServerName());
        responseHeader.setContentType(someMimes.getContentType(requestedFile.getFile()));
        responseHeader.setContentLength(requestedFile.getFile().length());
       
        //write the header and the file to the client
        System.out.print(responseHeader.getCode());
        System.out.println(requestedFile.getFile().toString());
        toClient.write(responseHeader.getBytes());
        toClient.write(requestedFile.getBytes());
        toClient.write("\r\n\r\n".getBytes("US-ASCII"));
        toClient.flush();
       
        //print to log file
        String logEntry = client.getInetAddress().getHostAddress() + ":" + client.getPort() + " [" + df.format(now) + "] \"" + requestHeader.getMethod() + " " + requestHeader.getPath() + " " + responseHeader.getHTTPVersion() + "\" " + responseHeader.getCode() + " " + requestedFile.getFile().length() + "\n";
        OutputStream logOut = null;
        try
        {
            System.out.println("If you've gotten here, we're printing the log!");
            if(logFile.exists())
                logOut = new BufferedOutputStream(new FileOutputStream(logFile, true));
            else
                logOut = new BufferedOutputStream(new FileOutputStream(logFile, false));
            logOut.write(logEntry.getBytes());
        }
        catch (IOException ioe)
        {
            System.err.println(ioe);
            return;
        }
       
        //cleanup any open straems
        finally
        {
            if(logOut != null)
            {
                logOut.flush();
                logOut.close();
            }
        }
    }
}

No comments:

Post a Comment