Java SE 8 Documentation

Mock Exam

In this assignment, an internal logic for a trivial web server has to be implemented. The server uses a text-based protocol so it is about getting some textual queries (requests) that has to be processed in order to produce the corresponding answers (responses). The parts of this assignment aim to implement classes related to each of the processing steps.

Note that the classes have to placed in the http and http.method packages. There are some supplementary materials available, where all the referenced files could be found.

Testing

There are separate test cases for each part. Those are .java files that are all manually compilable and runnable with the help of the attached .jar file. For example, under Windows, the test cases for the first part could be built and run in the following way:

> javac -cp .;tests-zhttpd.jar tests/StatusTest.java
> java -cp .;tests-zhttpd.jar tests/StatusTest

The same test cases are used by the Test class, which is responsible for running all the tests together. Under Windows, this could be run as follows.

> java -cp .;tests-zhttpd.jar Test

Under Linux, everything could be built and run in the same way, but the semicolons in the parameter of the -cp flag have to be replaced with colons.

Detailed Description

http.Status (2 points)

The responses generated by the server are assigned with a status, so the client can tell learn about the result: if the query was successful or not due to some error. Because status is an elementary part of a response that will be used later on, represent it with an enumeration type.

Each status has the following public and read-only attributes:

  • code: A positive integer (int), a unique identifier of the status.
  • reason: Textual description of the status (String).

moreover:

  • A constructor that takes and sets the initial (and permanent) values for the attributes.

For the most frequently used statuses, define the following constants:

  • Status.OK: code = 200, reason = "OK",
  • Status.BAD_REQUEST: code = 400, reason = "Bad Request",
  • Status.NOT_FOUND: code = 404, reason = "Not Found",
  • Status.ERROR: code = 500, reason = "Internal Server Error",
  • Status.NOT_IMPLEMENTED: code = 501, reason = "Not Implemented".

Test cases: tests/StatusTest.java

http.Message (3 points)

The communication between the server and the client is technically implemented by an exchange of messages, so define an abstract class that represents messages. The class must not yet be instantiated as messages will need to be specialized further based on their types. They can be either requests or responses. But this class will be useful for taking care of managing the associated headers.

In result, the class has to have the following:

  • addHeader(): A method that takes the name and value (as String) for a header and stores them in a java.util.Map for being able to search for them on a later occasion. Header names are case insensitive so they should be stored with all uppercase letters.

  • getHeader(): A method that could be used for getting the store value for a header. If no such header is stored, return null.

Test cases: tests/MessageTest.java

http.Response (3 points)

A specialization of messages is the response produced by the server that should be defined as a derived class of http.Message. Complement the inherited attributes and methods with the following ones:

  • status: The public and read-only status of the response (of type http.Status).

  • body: The body of the response (as a String). This should be public and accessible from anywhere.

  • A constructor that can be used for setting the status and the body for the fresh object instances.

  • Another constructor that can be used for setting the status alone and creates an empty body (sets it null).

  • An override for the toString() method that puts together the textual representation for the response by the protocol, which is the along the following lines:

     HTTP/1.1 {status} {reason}
     {header1}: {value1}
     {header2}: {value2}
     ...
     {headerN}: {valueN}
    
     {body}

    where {status} is the code of the status, {reason} is the textual description of the status, {headerX} and {valueX} are the corresponding header names and their respective values with X in between 1 and N (assuming that there are n headers), and {body} is the body of the message. The body may be optional, so if its value is null then it should not be there.

    For example (the order of headers does not matter):

     HTTP/1.1 200 OK
     CONNECTION: close
     CONTENT-LENGTH: 8
    
     01234567

    or:

     HTTP/1.1 500 Internal Server Error
     CONNECTION: close
     <blank line>

Test cases: tests/ResponseTest.java

http.NotFoundResponse (2 points)

With the help of deriving http.Response define another class that represents a specialized response that can be used when a requested file could not be found.

It should have the following textual representation (where the order of the headers does not matter):

HTTP/1.1 404 Not Found
CONNECTION: close
CONTENT-LENGTH: 33
CONTENT-TYPE: text/plain

The requested page was not found.

Note that all it needs is just to fill in the attributes of an http.Response instance with the appropriate constants.

Test cases: tests/NotFoundResponseTest.java

http.Server (part 1) (1 point)

Let us now start with the definition of the class that holds the internal logic of the server. It requires the definition of the following:

  • root: A public and read-only attribute, which is a text (String) that specifies the path where the web pages to serve can be found. For example, that could be a "www" directory from the supplementary materials.

  • host: A public and read-only attribute, which is a text (String) that specifies the host name of the web site to serve. For example, that is going to be "localhost:8000".

  • A constructor that can be used for setting the initial (and permanent) values for the attributes.

  • resolveMethod(): A public class-level method that can tell the method of handling the actual request (of type http.Method) from its name (String). Since there have not been yet any methods defined, this method should consistently return null for any invocation.

http.Method

In parallel, start with the definition of the interface that describes the processing methods. For the moment, it should have the following method:

  • response(): A query method without any parameters that can be used for getting the result of the run of the method as an http.Response object.

Test cases: tests/ServerTest1.java

http.Request (4 points)

Implement another type of messages, a class that represents requests. In this case, the http.Message base class has to be extended with the following components:

  • uri: A public and read-only text (String) field that stores the path of the requested file relative to the root of the hosted web site, such as "/index.html".

  • server: A public and read-only field that refers to the server object (of type http.Server) that receives the request. That will be needed when the request is processed by the given method.

  • method: A reference to the method (of type http.Method) that will process the request.

  • A hidden constructor that can set the initial (and permanent) values for the previously mentioned fields by its parameters.

  • fromString(): A public class-level method can build a Request object for a Server reference and an input (as String). The input is expected to have the following format:

     {method} {uri} HTTP/1.1

    where {method} is the method of the request given as a text, which could be "GET" later on (other methods are not supported for now), {uri} is the relative path of the requested file.

    For example:

     GET /index.html HTTP/1.1

    The processing method should be always determined from the value of {method} (so use Server.resolveMethod() for that). When input does not comply the described format, simply return a null reference.

  • response(): A method without any parameters where the request itself is processed with the method specified inside the object (execute(this), see below), and an http.Response object is returned as the response (that is the response() method that was mentioned in the http.Method interface before). If the method could not be resolved (that is, it is null), return a "Not Iplemented" response.

http.Method

However, in order to define the response() method above, the http.Method interface has to be extended with the following signature:

  • execute(): A method without any return value (void) that processes the received http.Request object.

Test cases: tests/RequestTest.java

http.method.Get (5 points)

Implement the "GET" processing method through the http.Method interface. The purpose of this method is to return meta information on the requested file through headers and send the contents of the file in the body. Obviously, that is only done if the requested file exists, otherwise it should generate a "Not Found" (of type http.NotFoundResponse) message. In addition to this, it should verify if the "Host" header matches with the name of the served web site.

For example, for the following valid request (where it is assumed that the "index.html" is present in the root of the directory that stores the files of the web site):

GET /index.html HTTP/1.1
Host: localhost:8000
<blank line>

the following valid response should be given (where the files in the supplementary materials are used, and the order of headers does not matter):

HTTP/1.1 200 OK
CONNECTION: close
CONTENT-LENGTH: 128
CONTENT-TYPE: text/html

<html>
<title>WWW Test Page</title>
<body>
<p>It works!</p>
<a href="sub.html">Cliquez ici, s'il vous plait</a>
</body>
</html>

Note that the file names in the requests should be interpreted as names relative to the root of the web site directory. The size of the files can be easily learned with the java.io.File class. It can be always assumed that the type of files is "text/html". Add the "Connection: close" header to the response, it will come handy later on.

If some exception is raised during the processing that is not a java.io.FileNotFoundException, wrap and return the message of the exception as an "Internal Error" (as http.Response). Of course, if the request triggers a java.io.FileNotFoundException, the result should be "Not Found" (of type http.NotFoundResponse).

Test cases: tests/GetTest.java

http.Server (part 2) (4 points)

Finish the class that implements the server's internal logic through the following steps:

  • resolveMethod(): Finish the previously started method so it returns a fresh http.Get instance on the "GET" parameter. In any other case, just return a null.

  • serve(): A method without any parameters or return value, and its purpose is to read a request from the standard input and send the resulting response to the standard output.

    The steps of the associated processing are as follows:

    • A request starts when the line read from the input is not blank. So until a non-blank is found, ignore the blank ones and continue reading the input.

    • Try to put a request together from the first non-blank line (with the help of the Request.fromString() method). On success, read the headers (one per line, in the previously specified format) until the next blank line.

      For example, a syntactially valid request looks like this (see the attached request.txt):

         GET /index.html HTTP/1.1
         Host: localhost:8000
         User-Agent: Mozilla/5.0 (X11; FreeBSD i386; rv:30.0) Gecko/20100101 Firefox/30.0
         Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
         Accept-Language: hu,en-us;q=0.7,en;q=0.3
         Accept-Encoding: gzip, deflate
         Connection: keep-alive
         Cache-Control: max-age=0
         <blank line>
    • If the input could not be parsed as a request, return a "Bad Request" message (of type http.Response).

Test cases: tests/ServerTest2.java

With the help of the Main.java source file, it is possible to try how the server can answer each of the requests. It can be built and used in the following way:

$ javac http/method/Get.java
$ javac Main.java
$ java Main www localhost:8000
GET /index.html HTTP/1.1
Host: localhost:8000
<blank line>
...

or the last step could be done with the request.txt as well:

$ java Main www localhost:8000 < request.txt
...

Bonus

Note that the complete version of the program can be also tested with the Zhttpd.java file, which makes it possible to communicate with a web browser. First, all the sources have to be built, then the resulting program should be launched from the same directory:

$ javac http/method/Get.java
$ javac Zhttpd.java
$ java Zhttpd www localhost:8000

Then socket 8000 is opened on the actual machine that could be connected by an arbitrary web browser by setting the following URL in the address bar:

http://localhost:8000/index.html

Have fun!

Grading

  • 0 — 6: insufficient (1)
  • 7 — 10: sufficient (2)
  • 11 — 14: mediocre (3)
  • 15 — 18: good (4)
  • 19 — 24: excellent (5)

Front page
Back