16.4 More Protocol Handler Examples and Techniques

     

Now that you've seen how to write one protocol handler, it's not at all difficult to write more. Remember the five basic steps of creating a new protocol handler:

  1. Design a URL for the protocol if a standard URL for that protocol doesn't already exist. As of mid-2004, the official list of URL schemes at the IANA (http://www.iana.org/assignments/uri-schemes) includes only 43 different URL schemes and reserves three more. For anything else, you need to define your own.

  2. Decide what MIME type should be returned by the protocol handler's getContentType( ) method. The text/plain content type is often appropriate for legacy protocols. Another option is to convert the incoming data to HTML inside getInputStream( ) and return text/html. Binary data often uses one of the many application types. In some cases, you may be able to use the URLConnection.guessContentTypeFromName( ) or URLConnection.guessContentTypeFromStream( ) methods to determine the right MIME type.

  3. Write a subclass of URLConnection that understands this protocol. It should implement the connect( ) method and may override the getContentType( ) , getOutputStream( ) , and getInputStream( ) methods of URLConnection . It also needs a constructor that builds a new URLConnection from a URL .

  4. Write a subclass of URLStreamHandler with an openConnection( ) method that knows how to return a new instance of your subclass of URLConnection . Also provide a getDefaultPort( ) method that returns the well-known port for the protocol. If your URL is not hierarchical, override parseURL( ) and toExternalForm( ) as well.

  5. Implement the URLStreamHandlerFactory interface and the createStreamHandler( ) method in a convenient class.

Let's look at handlers for two more protocols, daytime and chargen, which will bring up different challenges.

16.4.1 A daytime Protocol Handler

For a daytime protocol handler, let's say that the URL should look like daytime:///vision.poly.edu . We'll allow for nonstandard port assignments in the same way as with HTTP: follow the hostname with a colon and the port ( daytime:///vision.poly.edu:2082 ). Finally, allow a terminating slash and ignore everything following the slash. For example, daytime:///vision.poly.edu/index.html is equivalent to daytime:///vision.poly.edu . This is similar enough to an http URL that the default toExternalForm( ) and parseURL() methods will work.

Although the content returned by the daytime protocol is really text/plain, this protocol handler is going to reformat the data into an HTML page. Then it can return a content type of text/html and let the web browser display it more dramatically. The resulting HTML looks like this:

 <html><head><title>The Time at metalab.unc.edu</title></head><body> <h1>Fri Oct 29 14:32:07 1999</h1> </body></html> 

The trick is that the page can be broken up into three different strings:

  • Everything before the time

  • The time

  • Everything after the time

The first and the third strings can be calculated before the connection is even opened. We'll formulate these as byte arrays of ASCII text and use them to create two ByteArrayInputStream s. Then we'll use a SequenceInputStream to combine those two streams with the data actually returned from the server. Example 16-4 demonstrates . This is a neat trick for protocols such as daytime that return a very limited amount of data; it can be inserted in a single place in an HTML document. Protocols such as finger that return more complex and less predictable text might need to use a FilterInputStream that inserts the HTML on the fly instead. And of course, a third possibility is to simply return a custom content type and use a custom content handler to display it. This third option is explored in the next chapter.

Example 16-4. The DaytimeURLConnection class
 package com.macfaq.net.www.protocol.daytime; import java.net.*; import java.io.*; public class DaytimeURLConnection extends URLConnection {   private Socket connection = null;   public final static int DEFAULT_PORT = 13;   public DaytimeURLConnection (URL u) {     super(u);   }   public synchronized InputStream getInputStream( ) throws IOException {        if (!connected)  connect( );     String header = "<html><head><title>The Time at "       + url.getHost( ) + "</title></head><body><h1>";     String footer = "</h1></body></html>";     InputStream in1 = new ByteArrayInputStream(header.getBytes("8859_1"));       InputStream in2 = this.connection.getInputStream( );       InputStream in3 = new ByteArrayInputStream(footer.getBytes("8859_1"));           SequenceInputStream result = new SequenceInputStream(in1, in2);     result = new SequenceInputStream(result, in3);      return result;        }   public String getContentType( ) {     return "text/html";   }   public synchronized void connect( ) throws IOException {        if (!connected) {       int port = url.getPort( );       if ( port <= 0  port > 65535) {         port = DEFAULT_PORT;       }       this.connection = new Socket(url.getHost( ), port);       this.connected = true;     }    } } 

This class declares two fields. The first is connection , which is a Socket between the client and the server. The second field is DEFAULT_PORT , a final static int variable that holds the default port for the daytime protocol (port 13) and is used if the URL doesn't specify the port explicitly.

The constructor has no surprises . It just calls the superclass's constructor with the same argument, the URL u . The connect() method opens a connection to the specified server on the specified port (or, if no port is specified, to the default port); if the connection opens successfully, connect( ) sets the boolean variable connected to true . Recall from the previous chapter that connected is a protected field in URLConnection that is inherited by this subclass. The Socket that's opened by this method is stored in the connection field for later use by getInputStream( ) .

The getContentType( ) method returns a String containing a MIME type for the data. This method is called by the getContent( ) method of URLConnection to select the appropriate content handler. The getInputStream( ) method reformats the text into HTML, so the getContentType( ) method returns text/html .

The getInputStream( ) method builds a SequenceInputStream out of several string literals, the host property of url , and the actual stream provided by the Socket connecting the client to the server. If the socket is not connected when this method is called, the method calls connect( ) to establish the connection.

Next, you need a subclass of URLStreamHandler that knows how to handle a daytime server. This class needs an openConnection( ) method that builds a new DaytimeURLConnection from a URL and a getDefaultPort( ) method that returns the well-known daytime port 13. Since the daytime URL has been made similar to an http URL, we don't need to override parseURL() ; once we have written openConnection( ) , we're done. Example 16-5 shows the daytime protocol's URLStreamHandler .

Example 16-5. The DaytimeURLStreamHandler class
 package com.macfaq.net.www.protocol.daytime; import java.net.*; import java.io.*; public class Handler extends URLStreamHandler {   public int getDefaultPort( ) {     return 13;   }   protected URLConnection openConnection(URL u) throws IOException {     return new DaytimeURLConnection(u);   } } 

Since we've used the same package-naming convention here as for the previous finger protocol handler, no further changes to HotJava's properties need to be made to let HotJava find this. Just compile the files, put the classes somewhere in HotJava's class path , and load a URL that points to an active daytime server. Figure 16-2 demonstrates.

Figure 16-2. HotJava using the daytime protocol handler
figs/jnp3_1602.gif

16.4.2 A chargen Protocol Handler

The chargen protocol, defined in RFC 864, is a very simple protocol designed for testing clients . The server listens for connections on port 19. When a client connects, the server sends an endless stream of characters until the client disconnects. Any input from the client is ignored. The RFC does not specify which character sequence to send but recommends that the server use a recognizable pattern. One common pattern is rotating, 72-character carriage return/ linefeed delimited lines of the 95 ASCII printing characters, like this:

 !"#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh "#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi #$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij $%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk %&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl &'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm '( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn ( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno 

The big trick with this protocol is deciding when to stop. A TCP chargen server sends an unlimited amount of data. Most web browsers don't deal well with this. HotJava won't even attempt to display a file until it sees the end of the stream. Consequently, the first thing we'll need is a FilterInputStream subclass that cuts off the server (or at least starts ignoring it) after a certain amount of data has been sent. Example 16-6 is such a class.

Example 16-6. FiniteInputStream
 package com.macfaq.io; import java.io.*; public class FiniteInputStream extends FilterInputStream {   private int limit = 8192;   private int bytesRead = 0;      public FiniteInputStream(InputStream in) {     this(in, 8192);    }   public FiniteInputStream(InputStream in, int limit) {     super(in);     this.limit = limit;    }   public int read( ) throws IOException {          if (bytesRead >= limit) return -1;     int c = in.read( );     bytesRead++;     return c;         }      public int read(byte[] data) throws IOException {     return this.read(data, 0, data.length);    }      public int read(byte[] data, int offset, int length)     throws IOException {              if (data == null) throw new NullPointerException( );         else if ((offset < 0)  (offset > data.length)  (length < 0)           ((offset + length) > data.length)  ((offset + length) < 0)) {           throw new IndexOutOfBoundsException( );         }          else if (length == 0) {           return 0;         }     if (bytesRead >= limit) return -1;     else if (bytesRead + length > limit) {       int numToRead = bytesRead + length - limit;        int numRead = in.read(data, offset, numToRead);         if (numRead == -1) return -1;         bytesRead += numRead;       return numRead;      }     else { // will not exceed limit       int numRead = in.read(data, offset, length);       if (numRead == -1) return -1;         bytesRead += numRead;        return numRead;      }    }     public int available( ) throws IOException {     if (bytesRead >= limit) return 1;     else return in.available( );   } } 

Next, since there's no standard for the format of a chargen URL, we have to create one. Ideally, this should look as much like an http URL as possible. Therefore, we will implement a chargen URL like this:

 chargen://hostname:port 

Second, we need to choose the content type to be returned by the chargen protocol handler's getContentType() method. A chargen server returns ASCII text, so the getContentType( ) method should return the string text/plain . The advantage of the text/plain MIME type is that Java already understands it.

Example 16-7 is a ChargenURLConnection class that subclasses URLConnection . This class overrides the getContentType( ) and getInputStream() methods of URLConnection and implements connect( ) . It also has a constructor that builds a new URLConnection from a URL.

Example 16-7. The ChargenURLConnection class
 package com.macfaq.net.www.protocol.chargen; import java.net.*; import java.io.*; import com.macfaq.io.*; public class ChargenURLConnection extends URLConnection {   private Socket connection = null;      public final static int DEFAULT_PORT = 19;   public ChargenURLConnection(URL u) {     super(u);   }   public synchronized InputStream getInputStream( ) throws IOException {        if (!connected) this.connect( );     return new FiniteInputStream(this.connection.getInputStream( ));        }   public String getContentType( ) {     return "text/plain";   }      public synchronized void connect( ) throws IOException {        if (!connected) {       int port = url.getPort( );       if ( port < 1  port > 65535) {         port = DEFAULT_PORT;       }       this.connection = new Socket(url.getHost( ), port);       this.connected = true;     }    } } 

This class has two fields. connection is a Socket between the client and the server. The second field is DEFAULT_PORT , a final static int that contains the chargen protocol's default port; this port is used if the URL does not specify the port explicitly.

The class's constructor just passes the URL u to the superclass's constructor. The connect( ) method opens a connection to the specified server on the specified port (or, if no port is specified, to the default chargen port, 19) and, assuming the connection is successfully opened, sets the boolean field connected to true . The Socket that connect( ) opens is stored in the field connection for later use by getInputStream( ) . The connect() method is synchronized to avoid a possible race condition on the connected variable.

The getContentType( ) method returns a String containing a MIME type for the data. The data returned by a chargen server is always ASCII text, so this getContentType( ) method always returns text/plain .

The getInputStream( ) connects if necessary, then gets the InputStream from this.connection . Rather than returning it immediately, getInputStream( ) first chains it to a FiniteInputStream .

Now that we have a URLConnection , we need a subclass of URLStreamHandler that knows how to handle a chargen server. This class needs an openConnection() method that builds a new ChargenURLConnection from a URL and a getDefaultPort( ) method that returns the well-known chargen port. Since we defined the chargen URL so that it is similar to an http URL, we don't need to implement a parseURL( ) method. Example 16-8 is a stream handler for the chargen protocol.

Example 16-8. The chargen Handler class
 package com.macfaq.net.www.protocol.chargen; import java.net.*; import java.io.*; public class Handler extends URLStreamHandler {   public int getDefaultPort( ) {     return 19;   }   protected URLConnection openConnection(URL u) throws IOException {     return new ChargenURLConnection(u);   } } 

You can use HotJava to test this protocol handler. Run it and ask for a URL of a site running a chargen server, such as vision.poly.edu . Figure 16-3 shows the result.

Figure 16-3. HotJava using the chargen protocol handler
figs/jnp3_1603.gif



Java Network Programming
Java Network Programming, Third Edition
ISBN: 0596007213
EAN: 2147483647
Year: 2003
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net