To demonstrate a complete protocol handler, let’s write one for the finger protocol, which is defined in RFC 1288 and was introduced in Chapter 10. Finger is a relatively simple protocol compared to JDK-supported protocols such as HTTP and FTP. The client connects to port 79 on the server and sends a list of usernames followed by a carriage return/linefeed pair. The server responds with ASCII text containing information about each of the named users or, if no names were listed, a list of the currently logged in users. For example:
% telnet rama.poly.edu 79
Trying 128.238.10.212...
Connected to rama.poly.edu.
Escape character is '^]'.
Login Name TTY Idle When Where
jacola Jane Colaginae *pts/7 Tue 08:01 208.34.37.104
marcus Marcus Tullius pts/15 13d Tue 17:33 farm-dialup11.poly.e
matewan Sepin Matewan *pts/17 17: Thu 15:32 128.238.10.177
hengpi Heng Pin *pts/10 Tue 10:36 128.238.18.119
nadats Nabeel Datsun pts/12 56 Mon 10:38 128.238.213.227
matewan Sepin Matewan *pts/8 4 Sun 18:39 128.238.10.177
Connection closed by foreign host.
Or, requesting information about a specific user:
%telnet rama.poly.edu 79
Trying 128.238.10.212... Connected to rama.poly.edu. Escape character is '^]'.marcus
Login Name TTY Idle When Where marcus Marcus Tullius pts/15 13d Tue 17:33 farm-dialup11.poly.e
Since there’s no standard for the format of a finger URL, we will start by creating one. Ideally, this should look as much like an http URL as possible. Therefore, we will implement a finger URL like this:
finger://hostname:port/username
Second, we need to determine the content type returned by the finger
protocol’s getContentType( )
method. New
protocols such as HTTP use MIME headers to indicate the content type;
in these cases, you do not need to override the default
getContentType( )
method provided by the
URLConnection
class. However, since most protocols
precede MIME, you often need to specify the MIME type explicitly or
use the static methods URLConnection.guess Content TypeFromName(String name)
and
URLConnection.guessContent TypeFromStream(InputStream in)
to make an educated guess. This example doesn’t need
anything so complicated, however. A finger server returns ASCII text,
so the getContentType( )
method should return the
string text/plain
. The
text/plain
MIME type has the advantage that Java
already understands it. In the next chapter, you’ll learn how
to write content handlers that let Java understand additional MIME
types.
Example 16.2 is a
FingerURLConnection
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-2. The FingerURLConnection Class
package com.macfaq.net.www.protocol.finger; import java.net.*; import java.io.*; public class FingerURLConnection extends URLConnection { private Socket connection = null; public final static int DEFAULT_PORT = 79; public FingerURLConnection(URL u) { super(u); } public synchronized InputStream getInputStream( ) throws IOException { if (!connected) this.connect( ); InputStream in = this.connection.getInputStream( ); return in; } 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); OutputStream out = this.connection.getOutputStream( ); String names = url.getFile( ); if (names != null && !names.equals("")) { // delete initial / names = names.substring(1); names = URLDecoder.decode(names); byte[] result; try { result = names.getBytes("ASCII"); } catch (UnsupportedEncodingException e) { result = names.getBytes( ); } out.write(result); } out.write(' '), out.write(' '), out.flush( ); this.connected = true; } } }
This class has two fields. connection
is a
Socket
between the client and the server. Both the
getInputStream( )
method and the connect( )
method need access to this field, so it can’t be a
local variable. The second field is DEFAULT_PORT
,
a final static int
, that contains the finger
protocol’s default port; this port is used if the URL does not
specify the port explicitly.
The class’s constructor holds 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, then to the default finger port, 79. It
sends the necessary request to the finger server. If any usernames
were specified in the file part of the URL, they’re sent.
Otherwise, a blank line is sent. Assuming the connection is
successfully opened (no exception is thrown), it sets the
boolean
field connected
to
true
. Recall from the previous chapter that
connected
is a protected field in
java.net.URLConnection
, which is inherited by this
subclass. The Socket
that connect( )
opens is stored in the field
connection
for later use by
getInputStream( )
. The connect( )
and getInputStream( )
methods are
synchronized to avoid a possible race condition on the
connected
variable.
The getContentType( )
method returns a
String
containing a MIME type for the data. This
is used by the getContent( )
method of
java.net.URLConnection
to select the appropriate
content handler. The data returned by a finger server is almost
always ASCII text or some reasonable approximation thereof, so this
getContentType( )
method always returns
text/plain
. The getInputStream( )
method returns an InputStream
, which
it gets from the Socket
that
connect
created. If the connection has not already
been established when getInputStream( )
is called,
the method calls connect( )
itself.
Once you have a URLConnection
, you need a subclass
of URLStreamHandler
that knows how to handle a
finger server. This class needs an openConnection( )
method that builds a new
FingerURLConnection
from a URL. Since we defined
the finger URL so that it is
similar to an http URL, we
don’t need to implement a parseURL( )
method. Example 16.3 is a stream handler for the
finger protocol. For the moment, we’re going to use Sun’s
convention for naming protocol handlers, so we call this class
Handler
and place it in the package
com.macfaq.net.www.protocol.finger
.
Example 16-3. The Finger Handler Class
package com.macfaq.net.www.protocol.finger; import java.net.*; import java.io.*; public class Handler extends URLStreamHandler { public int getDefaultPort( ) { return 79; } protected URLConnection openConnection(URL u) throws IOException { return new FingerURLConnection(u); } }
You can use HotJava to test this protocol handler. Add the following
line to your .hotjava/properties
file or some
other place from which HotJava will load it:
java.protocol.handler.pkgs=com.macfaq.net.www.protocol
Some (but not all) versions of HotJava may also allow you to set the property from the command line:
% hotjava -Djava.protocol.handler.pkgs=com.macfaq.net.www.protocol
You also need to make sure that your classes are somewhere in
HotJava’s class path. Note that HotJava does not normally use
the CLASSPATH environment variable to look for classes, so just
putting them someplace where the JDK or JRE can find them may not be
sufficient. Using HotJava 3.0 on Windows with the JDK 1.3b1, I was
able to put my classes in the
jdk1.3/jre/lib/classes
folder. Your mileage may
vary depending on what version of HotJava you’re using with
which version of the JDK on which platform.
Run it, and ask for a URL of a site running finger, such as utopia.poly.edu. Figure 16.1 shows the result.
3.133.109.30