Reference no: EM13346853
Socket programming in Java: TCP
Goal: In this project we will develop a Web server in two steps. In the end, you will have built a multi-threaded Web server that is capable of processing multiple simultaneous service requests in parallel. You should be able to demonstrate that your Web server replies to an HTTP GET request.
We are going to implement version 1.0 of HTTP, as defined in RFC 1945, where separate HTTP requests are sent for each component of the Web page. The server will be able to handle multiple simultaneous service requests in parallel. This means that the Web server is multi-threaded. In the main thread, the server listens to a fixed port. When it receives a TCP connection request, it sets up a TCP connection through another port and services the request in a separate thread. To simplify this programming task, we will develop the code in two stages. In the first stage, you will write a multi-threaded server that simply displays the contents of the HTTP request message that it receives. After this program is running properly, in the second stage you will add the code required to generate an appropriate response.
Stage I: Multi-Threaded Web Server: Receiving requests
In the following steps, we will go through the code for the first implementation of our Web Server. Wherever you see "?", you will need to supply a missing detail.
Multi-threaded architecture: Our first implementation of the Web server will be multi-threaded, where the processing of each incoming request will take place inside a separate thread of execution. This allows the server to service multiple clients in parallel, or to perform multiple file transfers to a single client in parallel. When we create a new thread of execution, we need to pass to the Thread's constructor an instance of some class that implements the Runnable interface. This is the reason that we define a separate class called HttpRequest. The structure of the Web server is shown below (WebServer.java):
import java.io.* ;
import java.net.* ;
import java.util.* ;
public final class WebServer
{
public static void main(String args[])
{
// Determine the port number.
?
// Establish the listen socket.
?
// Process HTTP service requests in an infinite loop.
while (true) {
// Listen for a TCP connection request.
?
// Construct an object to process the HTTP request message.
HttpRequest request = new HttpRequest( ? );
// Create a new thread to process the request.
Thread thread = new Thread(request);
// Start the thread.
thread.start();
}
}
Selecting the port number: Normally, Web servers process service requests that they receive through well-known port number 80. You can choose any port higher than 1024, but remember to use the same port number when making requests to your Web server from your browser. You can decide if you prefer to obtain the port number from the command line or hard-code the port number into the server code.
Accepting a TCP connection: Given a port number, we open a socket and wait for a TCP connection request. Because we will be servicing request messages indefinitely, we place the listen operation inside of an infinite loop. This means we will have to terminate the Web server by pressing ^C (Ctrl-C) on the keyboard.
Handling the TCP connection in a new thread: When a connection request is received, we create an HttpRequest object, passing to its constructor a reference to the Socket object that
represents our established connection with the client. In order to have the HttpRequest object handle the incoming HTTP service request in a separate thread, we first create a new Thread object, passing to its constructor a reference to the HttpRequest object, and then call the thread's start() method.
After the new thread has been created and started, execution in the main thread returns to the top of the message processing loop. The main thread will then block, waiting for another TCP connection request, while the new thread continues running. When another TCP connection request is received, the main thread goes through the same process of thread creation regardless of whether the previous thread has finished execution or is still running.
Structure of the HttpRequest class: This completes the code in main(). For the remainder of the exercise, it remains to develop the HttpRequest class. The structure of the HttpRequest class (HttpRequest.java) is shown below:
import java.io.* ;
import java.net.* ;
final class HttpRequest implements Runnable
{
// Constructor
publicHttpRequest( ? )
{
?
}
// Implement the run() method of the Runnable interface.
public void run()
{
try {
processRequest();
} catch (Exception e) {
System.out.println(e);
}
}
private void processRequest() throws Exception
{
// Get a reference to the socket's input and output streams.
InputStream is = ?;
DataOutputStreamos= ?;
// Set up input stream filters.
?
BufferedReaderbr= ?;
// Get the request line of the HTTP request message.
String requestLine= ?;
// Display the request line.
System.out.println();
System.out.println(requestLine);
// Get and display the header lines.
String headerLine = null;
while ((headerLine = br.readLine()).length() != 0) {
System.out.println(headerLine);
}
// Close streams and socket.
os.close();
br.close();
socket.close();
}
}
In order to pass an instance of the HttpRequest class to the Thread's constructor, HttpRequest must implement the Runnable interface, which simply means that we must define a public method called run() that returns void. The run() method is executed when we call thread.start() in WebServer.main, hence the processing of the request must be implemented in this method (or methods called by this method).
Creating socket streams: When processing a request, we should first obtain references to the input and output streams of the Socket object that represents our established connection with the client. (Note: It is convenient to make the input stream a BufferedReader. The output stream should be a DataOutputStream, as we might be sending binary data in response to some requests. If you do not feel comfortable with stream in Java, we recommend that you take a look at the Java I/O Tutorial.)
Reading the request: Now we are prepared to get the client's request message, which we do by reading from the socket's input stream. The first item available in the input stream will be the HTTP request line. (See Section 2.2 of the textbook, or slides for Lecture 3 for a description of this and the following lines.) Following this are the header lines. (Remember that we don't know ahead of time how many header lines the client will send.) In this part of the exercise we only need to display the request, we will implement processing it in final part. Finally, we should close the streams and the socket.
Stage II: Multi-Threaded Web Server: Serving requests
Now for the second part of the project, instead of simply terminating the thread after displaying the browser's HTTP request message, we will analyze the request and send an appropriate response. For simplicity, we are going to ignore the information in the header lines, and use only the file name contained in the request line. In fact, we are going to assume that the request line always specifies the GET method, and ignore the fact that the client may be sending some other type of request, such as HEAD or POST.
Parsing the request: To extract the file name from the request line, we can use the String.split method. We can ignore the method specification, which we have assumed to be "GET". The GET command precedes the filename with a slash, so once we extract the filename, we should prefix it with a "." (dot) so that the resulting pathname starts within the current directory.
Checking if the requested file exists: Now that we have the file name, we can open the file as the first step in sending it to the client. If the file does not exist, the FileInputStream() constructor will throw the FileNotFoundException. Instead of throwing this possible exception and terminating the thread, we will use the try/catch construction to handle this case appropriately: We should respond with an HTTP error message, rather than try to send a nonexistent file.
Creating a response: We can now send the response message. There are three parts to the response message: the status line, the response headers, and the entity body. See section 2.2 of the textbook for details. Note in particular, that the status line and response headers are terminated by the character sequence CRLF ("\r\n" in Java). In addition, the response header is terminated with a blank line (CRLF).
In the case of a request for a nonexistent file, we return 404 Not Found in the status line of the response message, and include an error message in the form of an HTML document in the entity body, e.g:
<HTML>
<HEAD><TITLE>Not Found</TITLE></HEAD>
<BODY>Not Found</BODY>
</HTML>
Determining the MIME type: When the file exists, we need to determine the file's MIME type and send the appropriate MIME-type specifier. For simplicity, we will ignore other header lines. To determine the type specifier, we can examine the extension of the file name. (Hint: Use the endsWith(...) method.) MIME types we should recognize include text/html, image/gif and image/jpeg.
If the file extension is unknown, we provide the type application/octet-stream.
Alternatively, we can use the MimetypesFileTypeMap.getContentType method.
Sending the requested file: The entity body should consist of the content of the requested file. To achieve this, we can read a block of bytes from the FileInputStream (hint: consider the read(byte[] b) method) into an intermediate byte array buffer, and write the buffer into the socket's output stream. We repeat this until we transfer all the bytes of the requested file.
Closing the connection: After sending the entity body, the work in this thread has finished, so we close the streams and socket before terminating.
Testing: This completes the second part of development of your Web server. Try running the server from your home directory, and create a blank html webpage in that same directory. Remember to include a port specifier in the URL of your GET command, so that the default port 80 is not used. When you connect to the running web server from the other host, examine the GET message requests that the web server receives, which are printed out on the command line of your server.