![]() |
Distributed Systems Lab 2003 |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
![]() |
Lab 4: Adding an HTML user interfaceGeneral ExplanationsOverviewThis is the last time: if you have not yet read the overview description , do it now! Immediately! Stante pede! Allez! Hemen! In the last lab example, you develop an HTTP component to make your ShareMe system available via HTTP and a Web browser. An HTTP server is a multi-threaded socket program which accepts incoming HTTP requests and spawns a new thread for every request. To make the server's architecture more flexible, we introduce the concept of a request handler (HTTPServerEntry in the ShareMe terminology). Such a server entry (request handler) is a class that knows how to produce an HTML page for a request with a given prefix. Document and script prefices are common examples (i.e., each request with the document prefix results in looking up the specified file in the file system; each request with the script prefix results in the execution of the specified script). Figure 1 shows the conceptual architecture of the HTTP component. A Web browser sends an HTTP request to the HTTP server component (Step #1) which in turn spawns a new thread (Step #2). The request handler thread determines the corresponding server entry, forwards the request to it and retrieves the resulting IDocument object. Finally, an HTML page is created and the HTTP response is sent to the client (Step #3). Figure 1 . Architectural overview of the ShareMe HTTP component. In the ShareMe system we conceptually have three request handlers (HTTPServerEntries):
Technically, however, we use only two server entries: the document entry which delivers files (both the HTML pages as well as the shareable files and which is provided in the lab package) and the ShareMe entry (which you must implement) to initiate a search. To support you in implementing the HTTP component, we provide a set of helper classes. The following enumeration gives a brief description:
In the first task of this lab example, you'll create a skeleton for the HTTP server component which will run in a separate thread to avoid blocking the rest of the ShareMe system. Then, the server is extended to spawn new threads for every request. The request handler thread also has to understand the HTTP protocol. Then, the ShareMe server entry is added to integrate the ShareMe functionality with the HTTP component. Finally, the integration of the HTTP component with your ShareMe implementation adds the missing link. Reading SuggestionsRecall what you read about threads, multi-threading and inner classes. Check the thread and inner classes sections of the Java Tutorial for reference. Read the WWW tutorial and the HTTP tutorial carefully! Make sure that you understand everything discussed there! It contains all details about WWW and HTTP that you'll need for the lab. Deliverable (Submission)Execute the following command line (in a single line!) in the $HOME/src directory of your lab account. Recall that your submissions are only accepted if they are done from your lab account! tar cvfz - `find . -name "*.java"` | uuencode submission.tgz | mail -s 4 rnabgabe@dslab.tuwien.ac.at Task DescriptionsTask 4.1: A skeleton for the HTTP serverSince we want to integrate the HTTP server component with the ShareMe system, it is a key requirement that the HTTP component runs in its own thread. Thus, as several times before, you have to create a new thread. The HTTP component's class is called at.ac.tuwien.infosys.rnue.implementation.http.HTTPServerImpl and implements the IHTTPServer interface which in turn extends Runnable making the class a thread. In the constructor you get the HTTP port as parameter (of type java.lang.Integer and create a ServerSocket with the port. Set the timeout of the socket to IConstants.HTTP_SERVER_TIMEOUT . Next, we implement the run() method. In a loop - which again is controlled by a boolean flag - create a new Socket variable and accept requests. Later on, you will spawn a new thread for each request, at the moment we only accept it and do nothing else. Remember to catch the InterruptedIOException and ignore it, since this only means that the socket timed out. For all other exceptions, your system should print an error message to System.err , ignore this request, and continue operation. After the loop, the ServerSocket has to be closed. Finally, we need a stop() method which simply resets the flag controlling the loop. Next, you implement a mechanism to register and unregister request handlers (i.e., IHTTPServerEntry objects). Such a request handler is associated with a prefix. Based on the prefix, the server decides which request handler will handle the request. The handler with the longest prefix that matches the incoming request will be chosen. The handler then has to decide what the request means (e.g., load the specified HTML file from the disk, return the current date, calculate a prime number or anything else). In the lab we use a simple Hashtable object to store IHTTPServerEntries with their associated prefixes as keys. Thus, create a new Hashtable in the constructor, and complete the implementation of the register() and unregister() methods of the IHTTPServer interface to add/remove new server entries. Throw exceptions if any paramters are null or the entry to be removed does not exist. Task 4.2: Make the server multi-threadedIn the first step we ignored what happens with an incoming request. Now we implement the request handling. Actually, every request is handled by a separate thread (which means that the server itself runs in a thread and spawns new threads for every request). The RequestHandlerThread , as the garbage collector in lab 1 , is an inner class of the HTTPServerImpl . So far, we implemented the Runnable interface or used a TimerTask to make a class a thread. This time we directly extend the Thread class. The constructor takes a Socket as parameter and gets the input and output streams of the socket reference passed as parameter. The actual request handling is done in the run() method of the class. Hint: Make sure you read and understood the HTTP tutorial ! You have to know how HTTP requests are structured, the difference between HTTP 1.0 and HTTP 1.1, and the parts of an HTTP response. Read the first line of the HTTP request and parse it to retrieve the method, the requested path and the protocol. The StringTokenizer class can help here. Continue with the request header. If the protocol is HTTP 1.1, check if the required Host line is present (Simply check whether "Host:" is there.). For our sample implementation, we only accept requests using the GET method (but both HTTP 1.0 and HTTP 1.1 requests!). You can use the HTTPException and SimpleDocument class to handle errors. If you receive a GET request, you have to get the appropriate handler. The path has to be decoded since the browser encodes special characters for unambiguous and error-free transmission (use the URLDecoder class with "UTF-8" character encoding). Simply check the keys in the hashtable storing all handlers (you can access the instance variables of the enclosing class in your inner class!); the handler with the longest prefix that matches the beginning of the requested path is chosen. Get the IHTTPServerEntry (i.e., the handler class) from the hashtable and invoke getDocument() to get the result document. Make sure that you return an error document with the status code 500 then the execution of the IHTTPServerEntry crashes. Now you should have either the result document or an error document (i.e., a SimpleDocument instance). The final step is to send the document back to the client. To achieve this, it is best to use a private (or protected) method. In this method, you have to create the header of the response (including the document headers such as the last-modified date, the content lenght, the caching info and the content type as well as the server headers such as the date, the server, and the connection; consult the HTTP tutorial for details) and send it to the client. Use always HTTP/1.1 as protocol for your response. Then append the content of the document before closing the stream. Watch out, that you don't add spaces, CRLFs or other characters to the stream when you append the content. If you do so, the content length given in the header and the actual content length don't match (e.g don't use writeln() to write to the stream). Hint: Do not forget to use the RFC1123DateFormatter: to format ALL dates in the header! Finally, we go back to the IHTTPServer.run() method, create a new thread for each request and start it using the start() method. Using the HTTPServerTester class, you can now test your HTTP server component. The server tester instantiates your implementation and registers a DocumentEntry request handler with the document base and file base specified in your properties file and the IConstants.DOCUMENT_BASE prefix. If your implementation works correctly, you should be able to start the server using the command line shown below and test it using a browser to access any file which is located in the document base directory. java at.ac.tuwien.infosys.rnue.helpers.http.HTTPServerTester -c {property-file} Of course, you can use any HTTP client (e.g Mozilla, MS Internet Explorer) to test your HTTP component but it is highly recommended to use telnet for testing. HTTP clients are commonly very tolerant if several lines in the HTTP response are missing. Hence, if your implementation works with Mozilla & CO that does not mean your HTTP component works correctly. So, use telnet for testing, at least we do! If you do not know how to test your implementation using telnet take a look at the examples in the HTTP tutorial . Task 4.3: Add the ShareMe functionalityYou now have a working HTTP server which can already be used to deliver files. However, we have to integrate the server with our ShareMe implementation to be able to search and download files using a Web browser. For this purpose, you create the at.ac.tuwien.infosys.rnue.implementation.http.ShareMeEntry class which implements the IHTTPServerEntry interface. In the constructor this request handler gets an IShareMe instance and the getDescription() method returns a short textual description of your handler. The interesting part takes place in the getDocument() method. If the path parameter of the method starts with the IConstants.SHAREME_CGI fixed prefix, we continue to process the request; otherwise we return an error document (i.e., again an instance of the SimpleDocument class). If you receive a valid request, you have to get the parameters of the HTTP request (i.e., everything after the '?' of the path string). Then you must parse the parameter string and extract the key/value pairs. The pairs are separated by '&' from each other. Check if a key (parameter) called IConstants.SHAREME_PARAMNAME exists. Then extract the value (the keys are separated from the value by '='). If no such parameter is present, return an error document. The value of the IConstants.SHAREME_PARAMNAME paramter is used as search string for the ShareMe search. Thus use the IShareMe instance and start the search. When you receive the ISearchResult return value, all that is left is to create an IDocument from it and return it to the client. To do this, we create a new class called SearchResultDocument in the at.ac.tuwien.infosys.rnue.implementation.http package which implements the IDocument interface. The ISearchResult is passed to the constructor as parameter which creates the document's content (i.e., the HTML page) from it. Simply loop through all owners in the result and for each entry print the list of files corresponding to the search query. Make sure you use the information stored in the file list and files to generate the correct download URL. The generated HTML page should consist of a list of all peers that answered your request and a list of links for each peer to download the offered file. Futher you have to implement the remaining methods of IDocument : most of them are straight forward. The content type is the text/html MIME type and the caching info must contain the appropriate line(s) as described in the HTTP tutorial . To Implement the getContent() method you can use the String.getBytes() method and the ByteArrayInputStream class. You cannot use the HTTPServerTester for testing your ShareMeEntry . The HTTPServerTester only registers two DocumentEntry request handlers for the IConstants.DOWNLOAD_PREFIX and the IConstants.DOC_PREFIX . No ShareMeEntry is registered. To test the search functionality you have to finish the implementation of Lab4 and start your ShareMe peer. Finally, we concentrate on the HTTP responses of our ShareMe implementation. Table 1 gives an overview of requests and the expected HTTP response. All requests in the table use the HTTP 1.0 protocol but the HTTP component should also produce the same result for HTTP 1.1 requests. Assume that the following server entries are registered at your HTTP component:
Table 1 . ShareMe HTTP response Task 4.4: Integrate the server with your ShareMe implementationTo integrate the HTTP server component you have to extend the IShareMe.start() method and create a new instance of your server component. Then register a new request handler of type DocumentEntry using the IConstants.DOC_PREFIX prefix. Another DocumentEntry is registered with the IConstants.DOWNLOAD_PREFIX ; this handler is responsible for the actual downloads of files. Use the values from the property file for the file base ( IConstants.FILE_BASE ) and the document base ( IConstants.DOCUMENT_BASE ). Last, register an instance of the at.ac.tuwien.infosys.rnue.implementation.http.ShareMeEntry class using the IConstants.CGIBIN_PREFIX . When this is done, you can create a new Thread object with the HTTP component and start it. Don't forget in the IShareMe.stop() method to add a line to stop the HTTP server component. Finally, create an HTML form in your document base which has an input field for the search string. It must use the IConstants.CGIBIN_PREFIX and IConstants.SHAREME_CGI for the requested path in the action attribute (you must use GET as the method for your form - check your previous implementation work in this task!), and the IConstants.SHAREME_PARAMNAME as name of the input field. The file base you specify in your property file identifies the directory containing the files which shall be shared. If you are not sure if your HTTP component works correctly you can compare your HTTP response with the response form our ShareMe peers. Our peers can be found at: London labsrv01.dslab.tuwien.ac.at:30003 Beijing labsrv01.dslab.tuwien.ac.at:31003 Redmond (this is the buggy one ;-) labsrv02.dslab.tuwien.ac.at:32003 Hawaii labsrv02.dslab.tuwien.ac.at:33003 Happy file sharing! |
![]() |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |
![]() |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Powered by MyXML |
Last update on:
2003-03-13
© 2001 Distributed Systems Group |