ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

How Java Web Servers Work
Pages: 1, 2, 3

The Request Class

The Request class represents an HTTP request. An instance of this class is constructed by passing the InputStream object obtained from a Socket that handles the communication with the client. Call one of the read methods of the InputStream object to obtain the HTTP request raw data.



The Request class has two public methods: parse and getUri. The parse method parses the raw data in the HTTP request. It doesn't do much--the only information it makes available is the URI of the HTTP request, which it obtains by calling the private method parseUri. The parseUri method stores the URI in the uri variable. Invoke the public getUri method to return the URI of the HTTP request.

To understand how the parse and parseUri methods work, you need to know the structure of an HTTP request, which is defined in RFC 2616(.pdf).

An HTTP request contains three parts:

  • Request line
  • Headers
  • Message body

For now, we are only interested in the first part of the HTTP request, the request line. A request line begins with a method token, is followed by the request URI and the protocol version, and ends with carriage-return linefeed (CRLF) characters. Elements in the request line are separated by a space character. For instance, the request line for a request for the index.html file using the GET method is:

GET /index.html HTTP/1.1

The parse method reads the whole byte stream from the socket's InputStream passed to the Request object, and stores the byte array in a buffer. It then populates a StringBuffer object called request using the bytes in the buffer byte array, and passes the String representation of the StringBuffer to the parseUri method.

The parse method is given in Listing 1.2.

Listing 1.2. The Request class' parse method

public void parse() {
    // Read a set of characters from the socket
    StringBuffer request = new StringBuffer(2048);
    int i;
    byte[] buffer = new byte[2048];

    try {
        i = input.read(buffer);
    }
    catch (IOException e) {
        e.printStackTrace();
        i = -1;
    }

    for (int j=0; j<i; j++) {
        request.append((char) buffer[j]);
    }

    System.out.print(request.toString());
    uri   = parseUri(request.toString());
}

The parseUri method then obtains the URI from the request line. Listing 1.3 shows the parseUri method. The parseUri method searches for the first and the second spaces in the request and obtains the URI from there.

Listing 1.3. The Request class' parseUri method

private String parseUri(String requestString) {
    int index1, index2;
    index1 = requestString.indexOf(' ');

    if (index1 != -1) {
        index2 = requestString.indexOf(' ', index1 + 1);
        if (index2 > index1)
           return requestString.substring(index1 + 1, index2);
    }

    return null;
}

The Response Class

The Response class represents an HTTP response. Its constructor accepts an OutputStream object, such as the following:

public Response(OutputStream output) {
    this.output = output;
}

A Response object is constructed by the HttpServer class' await method by passing the OutputStream object obtained from the socket.

The Response class has two public methods: setRequest and sendStaticResource. The setRequest method is used to pass a Request object to the Response object. It is as simple as the code in Listing 1.4.

Listing 1.4. The Response class' setRequest method

public void setRequest(Request request) {
    this.request = request;
}

The sendStaticResource method is used to send a static resource, such as an HTML file. Its implementation is given in Listing 1.5.

Listing 1.5. The Response class' sendStaticResource method

public void sendStaticResource() throws IOException {
    byte[] bytes        = new byte[BUFFER_SIZE];
    FileInputStream fis = null;

    try {
        File file  = new File(HttpServer.WEB_ROOT, request.getUri());
        if (file.exists()) {
            fis    = new FileInputStream(file);
            int ch = fis.read(bytes, 0, BUFFER_SIZE);

            while (ch != -1) {
                output.write(bytes, 0, ch);
                ch = fis.read(bytes, 0, BUFFER_SIZE);
            }
        }
        else {
            // file not found
            String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
                "Content-Type: text/html\r\n" +
                "Content-Length: 23\r\n" +
                "\r\n" +
                "<h1>File Not Found</h1>";
            output.write(errorMessage.getBytes());
        }
    }
    catch (Exception e) {
        // thrown if cannot instantiate a File object
        System.out.println(e.toString() );
    }
    finally {
        if (fis != null)
            fis.close();
    }
}

The sendStaticResource method is very simple. It first instantiates the java.io.File class by passing the parent and child paths to the File class' constructor.

File file = new File(HttpServer.WEB_ROOT, request.getUri());

It then checks if the file exists. If it does, the sendStaticResource method constructs a java.io.FileInputStream object by passing the File object. It then invokes the read method of the FileInputStream and writes the byte array to the OutputStream output. Note that in this case, the content of the static resource is sent to the browser as raw data.

if (file.exists()) {
    fis    = new FileInputStream(file);
    int ch = fis.read(bytes, 0, BUFFER_SIZE);

    while (ch != -1) {
        output.write(bytes, 0, ch);
        ch = fis.read(bytes, 0, BUFFER_SIZE);
    }
}

If the file does not exist, the sendStaticResource method sends an error message to the browser.

String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
    "Content-Type: text/html\r\n" +
    "Content-Length: 23\r\n" +
    "\r\n" +
    "<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());

Compiling and Running the Application

To compile and run the application, you first need to extract the .zip file containing the application for this article. The directory you extract the .zip file into is called the working directory and will have three sub-directories: src/, classes/, and lib/. To compile the application, type the following from the working directory:

javac -d . src/ex01/pyrmont/*.java

The -d option writes the results to the current, not the src/, directory.

To run the application, type the following from the working directory:

java ex01.pyrmont.HttpServer

To test the application, open your browser and type the following in the URL or Address box:

http://localhost:8080/index.html

You will see the index.html page displayed in your browser, as in Figure 1.

output from the web server
Figure 1. The output from the web server

On the console, you can see something like the following:

GET /index.html HTTP/1.1
Accept: */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) 
Host: localhost:8080
Connection: Keep-Alive

GET /images/logo.gif HTTP/1.1
Accept: */*
Referer: http://localhost:8080/index.html
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) 
Host: localhost:8080
Connection: Keep-Alive

Summary

In this article, you have seen how a simple web server works. The application accompanying this article consists of only three classes and is not fully functional. Nevertheless, it serves as a good learning tool.

Budi Kurniawan is a senior J2EE architect and author.


Return to ONJava.com.