can it be done before 31?
Computer Networking MCIS 6163 Project 1 Simple Web Server & Client Instructor: Sajib Datta Fall 2021 "What I cannot create, I do not understand." Richard P Feynman Objectives (A) To understand Client-Server communication via sockets (B) To gain exposure to the basic operations of a Web Server and Client (C) To explore basic structures of HTTP messages Due Date November 5, 2021 11:59 PM1 Project Description (A) You will be developing a multi-threadedWeb server which interacts with any standard Web Clients ( You may use any web browser of your choice to test the functionality however you should also submit the a client as given in (B) below ). The Web server and Web client communicate using a text-based protocol called HTTP (Hypertext Transfer Protocol) (B) Build a single threaded Web Client on your own which interacts with your Web Server, and downloads a file from the server (C) Display the essential connection parameters of connection for both the Web client ( on the server side ) and for the Web Server ( on the client side ) 1 All Submissions should be completed through BlackBoard 1 Guidelines and Requirements MCIS 6163 Project 1 Specification Specifications - Server The server being multi-threaded, should be able to handle multiple requests concurrently. The main thread ( server ), listens to a specified port like the standard port for HTTP (8080). Upon receiving a HTTP request, the server sets up a TCP connection to the requesting client and serves the request in a separate thread using a new port. After sending the response back to the client, it closes the connection. For this exercise you may choose any browser of your choice for testing. ( Internet Explorer or FireFox or Chrome )2. However you should submit a client program as per the the Section Specifications - Client. The server is assumed to work withHTTP GET messages. If the requested file exists at the server, it responds with a “HTTP/1.1 200 OK” together with the requested page to the client, otherwise it sends a corresponding error message, “HTTP/1.1 404 Not Found” or “HTTP/1.1 400 Bad Request”. • If running the server program using command line, the syntax should be server_code_name < port_number=""> • You must test your Web server implementation on your local machine using a Web browser. You need to specify the used port number within the URL. If omitting the port number portion, i.e., 8080, the browser should use the default port 8080. To cite an example, http ∶ //localhost ∶ 8080/index.html • You should display/log the request and header lines of request messages on the server for the purpose of debugging. Specifications - Client • The client should be able to initiate a connection to the server, via a socket and request any page on the server. Upon receipt of the response message from the server, the client extracts and displays/logs the message status3, and then retrieves the page content from the corresponding message body. • The requested file need not be HTML, even a text file would suffice 4. • You may execute the client program using command line, with the follow- ing syntax, 2 Caveat: Some of the browsers need some additional setting changes for enabling complete functionality 3 ’HTTP 200 OK’ or ’404 Bad Request’ 4 But the format of the request should strictly be HTTP as discussed in class © Sajib Datta 2 Guidelines and Requirements MCIS 6163 Project 1 client_code < server_ipaddress="">< port_no="">< requested_file_name=""> (a) Server_IPaddress: The IP address or name of the Web server, e.g., 127.0.0.1 or localhost for the server running on the local machine. (b) port_no: The port on which the server is listening to contnections from clients. If the port number is not entered, the default port 8080 should be used. (c) requested_file_name: The name of the requested file, which may include the path to the file. Specifications - Connection Parameters You should be able to extract the following information from the connection objects, (a) Calculate and Display RTT for the client request5. (b) Print the relevant server details on client side . The examples could be Host Name of the server, socket family, socket type, protocol, timeout and get peer name 6. (c) Print the relevant client details on server side . The examples could be Host Name of the client, socket family, socket type, protocol, timeout and get peer name 7. Notes (a) This is an individual project. (b) You can use the programming language of your choice 8. (c) You may use the skeleton code for the server provided in the textbook’s companion website for reference. You may also want to refer to the text- book, chapter 2, section 2.2.3, for more details on HTTP message format and section 2.7, for socket programming. (d) The source codes should be well documented to make it easier for the GRADER to follow. 5 Refer Slide 2 - 25 of class lecture 6 Print a minimum of 4 out of 6 7 Print a minimum of 4 out of 6 8 You may get more help with Java or Python. Our best choice for you will be Python © Sajib Datta 3 Guidelines and Requirements MCIS 6163 Project 1 Submission Guidelines • Submit a single zipped file with the naming convention, < your_sau_id=""> _ < your_name=""> .zip • Your submission should have the following items to be considered for eval- uation, (a) Source codes of the Web server and client (b) Any additional files required to run your codes (c) –Very Important– readme.txt file with instructions on how to compile and run your codes. You must mention the IDE as well as any packages that are required to run the codes. (d) –Very Important– Provide ample amount of comments in the code to make it more readable and sustainable. • Do NOT include any runnable executable (binary) program. • Make sure your name and your SAU ID are also listed in the readme file and in comments at the beginning of your source files. • Make sure that submissions of the zipped file is through BlackBoard9. • No Late submission will be accepted. Additional Requirements/Instructions (a) Please email your Instructor for any doubts and clarifications regarding the project 1. (b) Complete documentation and instructions for running the codes are rec- ommended. (c) If you are using any code from some external source or book, you MUST mention it explicitly in the codes as well as the readme file. Otherwise, it will be considered plagiarism and your project will not be evaluated. 9 Please strictly follow the naming convention of the zipped file © Sajib Datta 4 Guidelines and Requirements MCIS 6163 Project 1 (d) You can discuss with other classmates on steps/algorithms to implement the project. However, the source codes must be written by your- self . Grading Rubric (25 points) (i) The server works correctly with requests from a Web browser (3.5 points) (ii) The server can serve multiple requests at the same time (multithreaded implementation) (4 points) (iii) The client sends/receives messages to/from the server correctly (5 points) (iv) The client extracts the status and content of messages from the server correctly (4 points) (v) Extracting and displaying connection parameters (1.5 points) (vi) Calculate and Display Round Trip Time (RTT). (2.5 points) (vii) Proper closing of the ports with exception handling. (2 points) (viii) Display/log of proper messages on the server as well as on the client. (1.5 points) (ix) Code documentation and Readme file. (1 points) Wish you all a good luck © Sajib Datta 5 6/9/2015 Programming Assignment 1: Building a MultiThreaded Web Server 1/6 Programming Assignment 1: Building a MultiThreaded Web Server In this lab we will develop a Web server in two steps. In the end, you will have built a multithreaded Web server that is capable of processing multiple simultaneous service requests in parallel. You should be able to demonstrate that your Web server is capable of delivering your home page to a Web browser. 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 multithreaded. 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 multithreaded server that simply displays the contents of the HTTP request message that it receives. After this program is running properly, you will add the code required to generate an appropriate response. As you are developing the code, you can test your server from a Web browser. But remember that you are not serving through the standard port 80, so you need to specify the port number within the URL that you give to your browser. For example, if your machine's name is host.someschool.edu, your server is listening to port 6789, and you want to retrieve the file index.html, then you would specify the following URL within the browser: http://host.someschool.edu:6789/index.html If you omit ":6789", the browser will assume port 80 which most likely will not have a server listening on it. When the server encounters an error, it sends a response message with the appropriate HTML source so that the error information is displayed in the browser window. Web Server in Java: Part A 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. Our first implementation of the Web server will be multithreaded, 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: import java.io.* ; import java.net.* ; import java.util.* ; public final class WebServer { public static void main(String argv[]) throws Exception { . . . } } final class HttpRequest implements Runnable { . . . } Normally, Web servers process service requests that they receive through wellknown 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. http://www.rfc-editor.org/rfc/rfc1945.txt 6/9/2015 Programming Assignment 1: Building a MultiThreaded Web Server 2/6 public static void main(String argv[]) throws Exception { // Set the port number. int port = 6789; . . . } Next, 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 on the keyboard. // Establish the listen socket. ? // Process HTTP service requests in an infinite loop. while (true) { // Listen for a TCP connection request. ? . . . } 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. // 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(); 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. This completes the code in main(). For the remainder of the lab, it remains to develop the HttpRequest class. We declare two variables for the HttpRequest class: CRLF and socket. According to the HTTP specification, we need to terminate each line of the server's response message with a carriage return (CR) and a line feed (LF), so we have defined CRLF as a convenience. The variable socket will be used to store a reference to the connection socket, which is passed to the constructor of this class. The structure of the HttpRequest class is shown below: final class HttpRequest implements Runnable { final static String CRLF = "\r\n"; Socket socket; // Constructor public HttpRequest(Socket socket) throws Exception { this.socket = socket; } 6/9/2015 Programming Assignment 1: Building a MultiThreaded Web Server 3/6 // Implement the run() method of the Runnable interface. public void run() { . . . } private void processRequest() throws Exception { . . . } } 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. Most of the processing will take place within processRequest(), which is called from within run(). Up until this point, we have been throwing exceptions, rather than catching them. However, we can not throw exceptions from run(), because we must strictly adhere to the declaration of run() in the Runnable interface, which does not throw any exceptions. We will place all the processing code in processRequest(), and from there, throw exceptions to run(). Within run(), we explicitly catch and handle exceptions with a try/catch block. // Implement the run() method of the Runnable interface. public void run() { try { processRequest(); } catch (Exception e) { System.out.println(e); } } Now, let's develop the code within processRequest(). We first obtain references to the socket's input and output streams. Then we wrap InputStreamReader and BufferedReader filters around the input stream. However, we won't wrap any filters around the output stream, because we will be writing bytes directly into the output stream. private void processRequest() throws Exception { // Get a reference to the socket's input and output streams. InputStream is = ?; DataOutputStream os = ?; // Set up input stream filters. ? BufferedReader br = ?; . . . } Now we are prepared to get the client's request message, which we do by reading from the socket's input stream. The readLine() method of the BufferedReader class will extract characters from the input stream until it reaches an endofline character, or in our case, the endofline character sequence CRLF. The first item available in the input stream will be the HTTP request line. (See Section 2.2 of the textbook for a description of this and the following fields.) // Get the request line of the HTTP request message. String requestLine = ?; // Display the request line. System.out.println(); System.out.println(requestLine); 6/9/2015 Programming Assignment 1: Building a MultiThreaded Web Server 4/6 After obtaining the request line of the message header, we obtain the header lines. Since we don't know ahead of time how many header lines the client will send, we must get these lines within a looping operation. // Get and display the header lines. String headerLine = null; while ((headerLine = br.readLine()).length() != 0) { System.out.println(headerLine); } We don't need the header lines, other than to print them to the screen, so we use a temporary String variable, headerLine, to hold a reference to their values. The loop terminates when the expression (headerLine = br.readLine()).length() evaluates to zero, which will occur when headerLine has zero length. This will happen when the empty line terminating the header lines is read. (See the HTTP Request Message diagram in Section 2.2 of the textbook) In the next step of this lab, we will add code to analyze the client's request message and send a response. But before we do this, let's try compiling our program and testing it with a browser. Add the following lines of code to close the streams and socket connection. // Close streams and socket. os.close(); br.close(); socket.close(); After your program successfully compiles, run it with an available port number, and try contacting it from a browser. To do this, you should enter into the browser's address text box the IP address of your running server. For example, if your machine name is host.someschool.edu, and you ran the server with port number 6789, then you would specify the following URL: http://host.someschool.edu:6789/ The server should display the contents of the HTTP request message. Check that it matches the message format shown in the HTTP Request Message diagram in Section 2.2 of the textbook. Web Server in Java: Part B Instead of simply terminating the thread after displaying the browser's HTTP request message, we will analyze the request and send an appropriate response. 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. We extract the file name from the request line with the aid of the StringTokenizer class. First, we create a StringTokenizer object that contains the string of characters from the request line. Second, we skip over the method specification, which we have assumed to be "GET". Third, we extract the file name. // Extract the filename from the request line. StringTokenizer tokens = new StringTokenizer(requestLine); tokens.nextToken(); // skip over the method, which should be "GET" String fileName = tokens.nextToken(); // Prepend a "." so that file request is within the current directory. fileName = "." + fileName; Because the browser precedes the filename with a slash, we prefix a dot so that the resulting pathname starts within the current directory. 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 6/9/2015 Programming Assignment 1: Building a MultiThreaded Web Server 5/6 possible exception and terminating the thread, we will use a try/catch construction to set the boolean variable fileExists to false. Later in the code, we will use this flag to construct an error response message, rather than try to send a nonexistent file. // Open the requested file. FileInputStream fis = null; boolean fileExists = true; try { fis = new FileInputStream(fileName); } catch (FileNotFoundException e) { fileExists = false; } There are three parts to the response message: the status line, the response headers, and the entity body. The status line and response headers are terminated by the character sequence CRLF. We are going to respond with a status line, which we store in the variable statusLine, and a single response header, which we store in the variable contentTypeLine. 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. // Construct the response message. String statusLine = null; String contentTypeLine = null; String entityBody = null; if (fileExists) { statusLine = ?; contentTypeLine = "Content‐type: " + contentType( fileName ) + CRLF; } else { statusLine = ?; contentTypeLine = ?; entityBody = "" + "Not Found" + "Not Found"; } When the file exists, we need to determine the file's MIME type and send the appropriate MIMEtype specifier. We make this determination in a separate private method called contentType(), which returns a string that we can include in the content type line that we are constructing. Now we can send the status line and our single header line to the browser by writing into the socket's output stream. // Send the status line. os.writeBytes(statusLine); // Send the content type line. os.writeBytes(?); // Send a blank line to indicate the end of the header lines. os.writeBytes(CRLF); Now that the status line and header line with delimiting CRLF have been placed into the output stream on their way to the browser, it is time to do the same with the entity body. If the requested file exists, we call a separate method to send the file. If the requested file does not exist, we send the HTMLencoded error message that we have prepared. // Send the entity body. if (fileExists) { sendBytes(fis, os); fis.close(); } else { os.writeBytes(?); 6/9/2015 Programming Assignment 1: Building a MultiThreaded Web Server 6/6 } After sending the entity body, the work in this thread has finished, so we close the streams and socket before terminating. We still need to code the two methods that we have referenced in the above code, namely, the method that determines the MIME type, contentType(), and the method that writes the requested file onto the socket's output stream. Let's first take a look at the code for sending the file to the client. private static 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[1024]; int bytes = 0; // Copy requested file into the socket's output stream. while((bytes = fis.read(buffer)) != ‐1 ) {