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:
- 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.
- 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 insidegetInputStream( )
and return text/html. Binary data often uses one of the many app types. In some cases, you may be able to use theURLConnection.guessContentTypeFromName( )
orURLConnection.guessContentTypeFromStream( )
methods to determine the right MIME type. - Write a subclass of
URLConnection
that understands this protocol. It should implement theconnect( )
method and may override thegetContentType( )
,getOutputStream( )
, andgetInputStream( )
methods ofURLConnection
. It also needs a constructor that builds a newURLConnection
from aURL
. - Write a subclass of
URLStreamHandler
with anopenConnection( )
method that knows how to return a new instance of your subclass ofURLConnection
. Also provide agetDefaultPort( )
method that returns the well-known port for the protocol. If your URL is not hierarchical, overrideparseURL( )
andtoExternalForm( )
as well. - Implement the
URLStreamHandlerFactory
interface and thecreateStreamHandler( )
method in a convenient class.
Let's look at handlers for two more protocols, daytime and chargen, which will bring up different challenges.
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-network-dev-java-programming-language.html.gz is equivalent to daytime:///vision.poly.edu. This is similar enough to an http URL that the default The trick is that the page can be broken up into three different strings:
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 This Class declares two fields. The first is 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. Screenshot-2 demonstrates.
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:
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 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:
Second, we need to choose the content type to be returned by the chargen protocol handler's This Class has two fields. 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. Screenshot-3 shows the result.
A daytime Protocol Handler
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>
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;
} }
}
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);
}
}
Screenshot-2. HotJava using the daytime protocol handler
A chargen Protocol Handler
!"#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh
"#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
#$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
$%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk
%&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl
&'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm
'( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn
( )*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno
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( );
}
}
chargen://hostname:port
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;
} }
}
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);
}
}
Screenshot-3. HotJava using the chargen protocol handler