Interview Questions

Example : An HTTP Redirector

Java Network Programming - Sockets for Servers


(Continued from previous question...)

Example : An HTTP Redirector

Another simple but useful application for a special-purpose HTTP server is redirection. In this section, we develop a server that redirects users from one web site to another--for example, from cnet.com to home.cnet.com. Example reads a URL and a port number from the command-line, opens a server socket on the port, then redirects all requests that it receives to the site indicated by the new URL, using a 302 FOUND code. Chances are this server is fast enough not to require multiple threads. Nonetheless, threads might be mildly advantageous, especially on a high-volume site on a slow network connection. And this server does a lot of string processing, one of Java's most notorious performance bottlenecks.

Example: An HTTP Redirector

    import java.net.*;
    import java.io.*;
    import java.util.*;
     
public class Redirector
 implements Runnable {
     
      private int port;
      private String newSite;
      
public Redirector
(String site, int port) {
        this.port = port;
        this.newSite = site;
      }
     
      public void run(  ) {
        
        try {
          
ServerSocket server =
 new ServerSocket(this.port); 
 System.out.println
 ("Redirecting connections on port " 
 + server.getLocalPort(  ) + " to " 
 + newSite);
            
          while (true) {
            
            try {
  Socket s = server.accept(  );
Thread t = new RedirectThread(s);
              t.start(  );
            }  // end try
            catch (IOException e) {   
            }
            
          } // end while
          
        } // end try
        catch (BindException e) {
 System.err.println
("Could not start server. Port Occupied");
        }         
        catch (IOException e) {
          System.err.println(e);
        }         
     
      }  // end run
     
class RedirectThread extends Thread {
            
        private Socket connection;
            
        RedirectThread(Socket s) {
          this.connection = s;    
        }
            
        public void run(  ) {
          
          try {
            
 Writer out = new BufferedWriter(
 new OutputStreamWriter(
 connection.getOutputStream(  ), "ASCII"
                          )
                         );
 Reader in = new InputStreamReader(
 new BufferedInputStream( 
 connection.getInputStream(  )
                         )
                        );
                        
 // read the first line only; that's 
all we need
StringBuffer request = new StringBuffer(80);
while (true) {
int c = in.read(  );
if (c == '\r' || c == '\n' || c == -1) break;
request.append((char) c);
 }
 //If this is HTTP 1.0 or later send 
// a MIME header

String get = request.toString(  );
int firstSpace = get.indexOf(' ');
int secondSpace = get.indexOf
(' ', firstSpace+1);

String theFile = get.substring
(firstSpace+1, secondSpace);

if (get.indexOf("HTTP") != -1)
{
out.write("HTTP1.0 302 FOUND\r\n");
Date now = new Date(  );
 out.write("Date: " + now + "\r\n");
out.write("Server: Redirector 1.0\r\n");
out.write
("Location: " + newSite + theFile + "\r\n");        
out.write
("Content-type: text/html\r\n\r\n");                 
out.flush(  );                
            }
// Not all browsers support redirection
// so we need to produce HTML that says
// where the document has moved to.


out.write("<HTML><HEAD><TITLE>
Document moved</TITLE></HEAD>\r\n");
out.write("<BODY><H1>
   Document moved</h2>\r\n");
out.write("The document " + theFile  
+ " has moved to\r\n<A HREF=\""
 + newSite + theFile + "\">" 
             + newSite  + theFile 
 + "</A>.\r\n 
 Please update your bookmarks<P>")
 ;
out.write("</BODY></HTML>\r\n");
            out.flush(  );
     
          } // end try
          catch (IOException e) {
          }
          finally {
            try {
if (connection != null) connection.close( );
            }
            catch (IOException e) {}  
          }     
      
        }  // end run
        
      }
     
public static void main(String[] args) 
{
     
        int thePort;
        String theSite;
        
        try {
          theSite = args[0];
          // trim trailing slash
          if (theSite.endsWith("/")) {
theSite = theSite.substring
(0, theSite.length(  )-1);
          }
        }
        catch (Exception e) {
          System.out.println(
"Usage: java Redirector 
http://www.newsite.com/ port");
          return;
        }
        
        try {
 thePort = Integer.parseInt(args[1]);
        }  
        catch (Exception e) {
          thePort = 80;
        }  
          
Thread t = new Thread(new Redirector
(theSite, thePort));
        t.start(  );
      
      }  // end main
     
    }

To start the redirector on port 80 and 
redirect incoming requests to 
http://www.yousite.com/xml,
you would type:

D:\JAVA\JNP2\examples\11>
java Redirector http://www.yousite.com/xml

Redirecting connections o
 n port 80 to http://www.yousite.com/xml

 If you connect to this server via Telnet,
  this is what you'll see:

    % telnet macfaq.dialup.cloud9.net 80
    Trying 168.100.203.234...
    Connected to macfaq.dialup.cloud9.net.
    Escape character is '^]'.
    GET / HTTP 1.0
    HTTP 1.0 302 FOUND
    Date: Wed Sep 08 11:59:42 PDT 1999
    Server: Redirector 1.0
    Location: http://www.yousite.com/xml/
    Content-type: text/html
     
<HTML><HEAD><TITLE>Document moved<
/TITLE></HEAD>

    <BODY><H1>Document moved</h2>
    The document / has moved to
<A HREF="http://www.yousite.com/xml/">
http://www.yousite.com/xml/</A>.
     Please update your bookmarks<P>
     </BODY></HTML>
    Connection closed by foreign host.

If, however, you connect with a reasonably modern web browser, you should be sent to http://www.yousite.com/xml with only a slight delay. You should never see the HTML added after the response code; this is provided to support older browsers that don't do redirection automatically.

The main( ) method provides a very simple interface that reads the URL of the new site to redirect connections to and the local port to listen on. It uses this information to construct a Redirector object. Then it uses the resulting Runnable object (Redirector implements Runnable) to spawn a new thread and start it. If the port is not specified, Redirector listens on port 80. If the site is omitted, Redirector prints an error message and exits.

The run( ) method of Redirector binds the server socket to the port, prints a brief status message, and then enters an infinite loop in which it listens for connections. Every time a connection is accepted, the resulting Socket object is used to construct a RedirectThread. This RedirectThread is then started. All further interaction with the client takes place in this new thread. The run( ) method of Redirector then simply waits for the next incoming connection.

The run( ) method of RedirectThread does most of the work. It begins by chaining a Writer to the Socket's output stream, and a Reader to the Socket's input stream. Both input and output are buffered. Then the run( ) method reads the first line the client sends. Although the client will probably send a whole MIME header, we can ignore that. The first line contains all the information we need. This line looks something like this:

GET /directory/filename.html HTTP 1.0

It is possible that the first word will be POST or PUT instead or that there will be no HTTP version. The second "word" is the file the client wants to retrieve. This must begin with a slash (/). Browsers are responsible for converting relative URLs to absolute URLs that begin with a slash; the server does not do this. The third word is the version of the HTTP protocol the browser understands. Possible values are nothing at all (pre-HTTP 1.0 browsers), HTTP 1.0 (most current browsers), or HTTP 1.1.

To handle a request like this, Redirector ignores the first word. The second word is attached to the URL of the target server (stored in the field newSite) to give a full redirected URL. The third word is used to determine whether to send a MIME header; MIME headers are not used for old browsers that do not understand HTTP 1.0. If there is a version, a MIME header is sent; otherwise, it is omitted.

Sending the data is almost trivial. The Writer out is used. Since all the data we send is pure ASCII, the exact encoding isn't too important. The only trick here is that the end-of-line character for HTTP requests is \r\n--a carriage return followed by a linefeed.

The next lines each send one line of text to the client. The first line printed is:

HTTP 1.0 302 FOUND

This is an HTTP 1.0 response code that tells the client to expect to be redirected. The second line is a Date: header that gives the current time at the server. This line is optional. The third line is the name and version of the server; this is also optional but is used by spiders that try to keep statistics about the most popular web servers. (It would be very surprising to ever see Redirector break into single digits in lists of the most popular servers.) The next line is the Location: header, which is required for this server. It tells the client where it is being redirected to. Last is the standard Content-type: header. We send the content type text/html to indicate that the client should expect to see HTML. Finally, a blank line is sent to signify the end of the header data.

Everything after this will be HTML, which is processed by the browser and displayed to the user. The next several lines print a message for browsers that do not support redirection, so those users can manually jump to the new site. That message looks like:

<HTML><HEAD><TITLE>Document moved</TITLE></HEAD>
<BODY><H1>Document moved</h2>
The document / has moved to
<A HREF="http://www.yousite.com/xml/">http://www.yousite.com/xml/</A>.
Please update your bookmarks<P></BODY></HTML>


Finally, the connection is closed and the thread dies.
A full-fledged HTTP server

Enough with special-purpose HTTP servers. This section develops a full-blown HTTP server, called JHTTP, that can serve an entire document tree, including images, applets, HTML files, text files, and more. It will be very similar to the SingleFileHTTPServer, except that it pays attention to the GET requests. This server is still fairly lightweight; after looking at the code, we'll discuss other features you might want to add.

Since this server may have to read and serve large files from the filesystem over potentially slow network connections, we'll change its approach. Rather than processing each request as it arrives in the main thread of execution, we'll place incoming connections in a pool. Separate instances of a RequestProcessor class will remove the connections from the pool and process them.

(Continued on next question...)

Other Interview Questions