All Categories :
Java
Chapter 12
Handling Dynamic Content
CONTENTS
This chapter introduces the HotJava browser and explains why it
is so groundbreaking, then introduces the HotJava source release,
used to modify HotJava itself. The HTTP server from previous chapters
gets a needed face-lift before being used to serve dynamic content
to HotJava. At the end of the chapter, you will actually write
and test two HotJava content handlers.
The developers at Sun had a problem; they had a new language,
Java, envisioned as dominating the World Wide Web (WWW), but they
didn't have a way to reach the public. People need to see demonstrations
before they can fully appreciate a new technology, and they need
to work with things before they will adopt them. How could Sun
convince people to use their distributed language without a demonstration
vehicle? The answer is: They couldn't. A Web browser that could
execute Java was needed, so the HotJava browser was developed.
HotJava is completely implemented in the Java language itself!
It was started to demonstrate interactive content (Java applets)
and to prove that Java was a viable language for advanced applications
(the browser).
Traditional Web browsers are a monolithic collection of protocol
handlers and display routines. Typically, the manufacturer equips
them to respond to a certain subset of the huge number of protocols
and data formats that make up the Internet. HotJava essentially
understands none of the protocols or data formats on the Internet.
This distinction is important. HotJava was not conceived to speak
any specific protocol; it was developed to speak them all.
HotJava implements a concept called dynamic content. When
you point HotJava at a URL, the browser searches for the code
to converse in whatever protocol is being used. If you entered
http://www.javasoft.com/
HotJava would load its HTTP protocol handler. If you entered
ftp://ftp.some.host/
HotJava would load its FTP protocol handler. What if it didn't
have an FTP handler? Well, it would ask the host whether it had
an FTP handler. If the host had one, HotJava would download it,
then use the new handler to fetch the original URL. HotJava upgrades
itself on the fly!
Protocol Handlers
HotJava actually does come with a great many protocol handlers
already installed-that is, already present on the local computer.
When a new protocol is needed, HotJava searches locally first;
only then does it resort to asking the remote host.
The distinction is that it makes no difference to HotJava whether
the handler is locally available or gotten from halfway around
the world. This dynamic behavior extends to display content as
well.
Content Handlers
Traditional Web browsers understand a small subset of display
formats. Most common are GIF, JPEG, and X11 bitmap. The content
type comes encoded in a Multipurpose Internet Mail Extension (MIME)
header. You experienced MIME content types when you worked with
the HTTP server. Remember how the server sent everything as Content-type:
text/html? This was a MIME content type. Servers are
configured to send each file format with a specific MIME type.
Typically, this is done by tying the suffix of a file to a specific
content type. Table 12.1 lists some common file extensions and
their MIME content types.
When HotJava sees a MIME content type, it tries to load the appropriate
handler. Again, if it can't find a local handler, it will ask
the remote server for one.
Table 12.1. Common file extensions and their MIME content
types.
| File Extension | MIME Content Type
|
| html |
text/html |
| htm |
text/html |
| txt |
text/plain |
| rtf |
text/rtf |
| ps |
text/postscript |
| doc |
text/msword |
| gif |
image/gif |
| jpg |
image/jpeg |
| mpg |
video/mpeg |
| wav |
audio/wav |
| au |
audio/ulaw |
| tar |
file/tar |
| arc |
file/arc |
| lha |
file/lharc |
| zip |
file/zip |
Netscape attempts to mimic this behavior with "plug-ins."
You can configure Netscape Navigator to run plug-in programs when
it encounters certain MIME content types. The one thing that Netscape
can't do is use the server to load one on the fly. Even if it
could, there are so many different platforms available, who knows
if it would load the correct executable for your specific architecture.
Java is portable, so you can compile it once and execute it everywhere
there's an interpreter present. If you're using the HotJava browser,
then you have a running Java interpreter already in action.
HotJava has a much more flexible security model than Netscape
Navigator. Instead of banning all file access and limiting socket
access, HotJava allows the severity of restrictions to be configured.
Network security has four modes:
- No access-Applets cannot open any external URLs or
socket connections.
- Applet host-Applets can open sockets only to the host
they were loaded from.
- Firewall-Applets can open sockets to the host they
were loaded from and to hosts within a configurable list or domain.
- Unrestricted-Applets can open sockets to anywhere.
In addition, you can also apply these modes to applet loading:
- No access-No Java applets can be loaded.
- Applet host-Only applets from the local file system
can be loaded.
- Firewall-Applets from hosts within a firewall can be
loaded.
- Unrestricted-Applets can be loaded from any host.
Accessing local file storage is also a configurable option. HotJava
uses two environment variables to control file access by applets:
- HOTJAVA_READ_PATH
- HOTJAVA_WRITE_PATH
Both these variables consist of a semicolon-separated list of
directories. Applets can access any files in these directories
or their subdirectories.
Firewall Security Model
The firewall security model is a powerful option. The corporate
world is only now becoming aware of the vast power inherent in
HTML. Corporate intranets are sprouting up everywhere; these corporate
nets offer full access for machines behind their firewall, but
limited or no access to the Internet at large. Allowing a browser
to have full access rights within a firewall offers the necessary
flexibility that corporations demand. The current Netscape security
model is too restrictive for business use. Real-world applications
need local storage and flexible connectivity options to be of
any use. Browsers will probably adopt this firewall model as business
demands for Java grow.
Unfortunately, HotJava is mired in the world of Java Alpha3. It
can't run applets written for version 1.0, but it still has instructive
use. The Java language is quite stable, so you'll find no differences
in the language syntax or semantics, but there are some major
differences in the class packages and libraries.
To begin with, there is no classes.zip file. All the Java classes
are contained in a directory tree that reflects the package name
of each class. Actually, if you peered inside the zipped class
file from version 1.0, you would find that this same tree has
been maintained in the compressed image. Figure 12.1 shows the
major packages within the directory hierarchy.
Figure 12.1 : Directory structory and major packages.
This layout should look familiar to you, with the exception of
the browser package. This package contains classes and interfaces
to HotJava's general functionality. In particular, the Applet
class is located within the browser package.
The protocol and content handler classes are under the following
directories:
classes/net/www/protocol
classes/net/www/content
When HotJava makes a request to a server for a specific handler,
it uses the same path as the local directories. For example, if
you encounter MIME type text/plain
and there's no plain.class within the local text subdirectory,
HotJava issues the following request:
GET /classes/net/www/content/text/plain.class
HTTP/1.0
Unfortunately, this request issued to the previous chapter's HTTP
server would go unanswered because HotJava is waiting for a socket
close notification that will never arrive due to a bug in the
JDK. It seems as though socket.close()
doesn't work under either Windows 95 or Windows NT. You need to
change HotJava's source code to use the Content-length
MIME parameter that the HTTP server dutifully places into the
outbound stream.
To alter the HotJava source, you must first have a thorough understanding
of the inner workings of the HotJava protocol handler; its principle
function is to provide the input stream:
public InputStream openStream(URL u);
The handler for HTTP constructs an instance of the HttpClient
class to perform its low-level work; this class extends NetworkClient.
Then the socket connection is opened and the request is sent.
The input stream is acquired and wrapped in a BufferedInputStream.
The HttpClient parses the response header and stores the results.
At this point, the constructor for HttpClient returns. The HTTP
protocol handler now processes the response. If the MIME header
contains a valid Content-length:
field, the input stream is further encapsulated in a MeteredStream.
The Stream class MeteredStream is the perfect place to make the
alterations.
The standard input stream hierarchy looks like this:
InputStream
FilterInputStream
BufferedInputStream
The following list shows the public methods of the FilterInputStream
class:
| FilterInputStream Members |
available()
close()
mark(int)
markSupported()
read()
read(byte[])
read(byte[], int, int)
reset()
skip(int)
|
This class is extended by BufferedInputStream. Buffered streams
offer a look-ahead buffer that enables a stream to be read in
large chunks, but processed a character at a time. They also provide
mark/reset capability. Frequently, applications will need to parse
a stream, looking for a specific string. If this string is not
found, then the application should reset the stream so other processes
can try to parse the same data. Marking a stream tells the BufferedInputStream
object to remember the current location within the buffer. If
a subsequent reset is called, the object will march backward in
its buffer to the previously stored location. New read requests
will be satisfied from the old location. The following list shows
the public methods of the BufferedInputStream class; all these
methods override the equivalent named method in the FilterInputStream
class:
| Public Methods of BufferedInputStream
|
available()
mark(int)
markSupported()
read()
read(byte[], int, int)
reset()
skip(int)
|
Start with the following InputStream:
"BufferedInputStreams are handy!"
Next, execute the following code snippet on the stream:
byte data[] = new byte[13];
is.read(data); // read 13
characters [BufferedInput]
mark(200); //
save this position for up to 200 characters
is.read(data, 0, 7); // read 7 more characters [Streams]
reset(); //
go back
byte rest[] = new byte[18];
is.read(rest); // read 18
more characters [Streams are handy!]
A MeteredStream extends FilterInputStream; you can find its source
code in the HotJava directory:
classsrc\net\www\html\MeteredStream.java
Metered Stream and HTTP
HotJava has a progress display, performed by the MeteredStream
class, that can show how a transfer is progressing. Whenever the
HTTP protocol handler detects a length parameter in the response
header, it will encapsulate the current input stream within a
MeteredStream:
String ct = http.getHeaderField("content-length");
int len = 0;
if (ct != null && (len = Integer.parseInt(ct)) != 0) {
is = new MeteredStream(is, len, url);
Since all subsequent reads will be done through the MeteredStream
class, this is the ideal location for the changes.
Now you simply need to make stream reads return end-of-stream
once the length parameter has been read. The following list shows
the overriding methods in the MeteredStream class:
| MeteredStream Public Methods |
read()
read(byte[], int, int)
skip(int)
close()
|
Both read routines must be changed so that there are no further
read attempts once the entire length of bytes has been returned:
public int read() {
if ( count >= expected ) return
-1; // add this line
int c = super.read();
if (c != -1) {
justRead(1);
}
return c;
}
public int read(byte b[], int off, int len) {
if ( count >= expected ) return
-1;
if ( count + len > expected ) len
= expected - count; // add both of these
Âlines
int n
= super.read(b, off, len);
if (n != -1) {
justRead(n);
}
return n;
}
In addition, mark and reset operations must be intercepted so
the counts can be updated. As you can see from the previous list
of public methods, these routines are absent, so add the following
two routines:
public synchronized void mark(int readlimit)
{
meterMark = count;
super.mark(readlimit);
}
public synchronized void reset() {
if ( meterMark
!= -1 )
count
= meterMark;
super.reset();
}
The class variable meterMark
also needs to be added to the class:
public
class MeteredStream extends FilterInputStream
{
// Class variables.
...
// Instance variables.
...
int meterMark = -1; //
add this line
...
}
That's it! Recompile MeteredStream and move the class file into
the classes/net/www/html subdirectory.
Compiling under HotJava is the same as compiling under the JDK;
the only difference is which version of javac you use. I prefer
not to alter my path and environment for HotJava. Simply issue
the command specifying the full path to HotJava's version of javac:
c:\hotjava\bin\javac MeteredStream.java
Assuming you made the alterations correctly, this will create
a MeteredStream.class file in the current directory. Rename the
original MeteredStream.class to MeteredStream.orig, then copy
your altered MeteredStream.class into its place. You have just
changed the source for HotJava! Now give it a try-run the server
from Chapters 9, "Java Socket Programming,"
10, "Native Methods and Java," or 11, "Building
a Live Data Applet," and point your new HotJava at the server.
The files transfer perfectly.
Before addressing content handlers, you need to add some enhancements
to the server.
The server used in Part IV, "Managing Live Data," was
useful, but it lacked many features of an actual HTTP server.
Specifically, there is no configuration file to map file extensions
to MIME content strings. In addition, some log information would
be helpful.
The first change is to the class name. Up to this point, the main
class has been called BasicWebServer. Change this to HTTPServer,
then perform a global search and replace to make the change. Now
the configuration and logging methods can be added.
Since the server is an application, it is free to read and even
write the disk. The format of the configuration file is very straightforward:
- port = #
- suffix = file_extension Content-type
- log = filename
New methods are added to handle reading the configuration data.
Listing 12.1 shows the new routines.
Listing 12.1. New routines for reading configuration files.
/**
* Read the config file.
*/
private void initialize()
{
String line =
null;
String configFile
= "server.cfg";
// setup defaults
HTTP_PORT = 80;
suffix = new Hashtable();
try
{
FileInputStream
inFile = new FileInputStream(configFile);
DataInputStream
is = new DataInputStream(inFile);
while
((line = is.readLine()) != null)
{
StringTokenizer
icmd = new StringTokenizer(line, " \t=");
addConfigEntry(icmd);
}
}
catch (FileNotFoundException
fnf)
{
suffix.put("html",
"text/html");
suffix.put("htm",
"text/html");
suffix.put("class",
"text/plain");
suffix.put("gif",
"image/gif");
}
catch (IOException
ioe)
{
System.out.println("Error
reading config file: " + ioe);
}
}
/**
* Add a config entry.
* @param icmd contains the tokenized
line.
*/
private void addConfigEntry(StringTokenizer
icmd)
{
if ( icmd.hasMoreTokens()
)
{
String
param = null;
String
command = icmd.nextToken();
if
( icmd.hasMoreTokens() )
{
param
= icmd.nextToken();
if
( command.equalsIgnoreCase("PORT") )
{
HTTP_PORT
= Integer.valueOf(param).intValue();
if
(debug)
System.out.println("Monitoring
port " + HTTP_PORT);
}
else
if ( command.equalsIgnoreCase("SUFFIX") )
{
if
( icmd.hasMoreTokens() )
{
String
param2 = icmd.nextToken();
suffix.put(param,
param2);
if
(debug)
{
System.out.print(
"Adding
suffix: '" + param + "'");
System.out.println(" '"
+ param2 + "'");
}
}
}
else
if ( command.equalsIgnoreCase("LOG") )
{
openLog(param);
}
else
{
System.out.print("Error:
Unknown cfg entry: ");
System.out.println(command);
}
}
}
}
/**
* Open a log file
* @param logfile contains the filename
to open
*/
public void openLog(String logfile)
{
try
{
logFile
= new PrintStream( new FileOutputStream(logfile) );
logging
= true;
}
catch (IOException
ioe)
{
System.out.println("Error
opening log file: " + ioe);
}
}
The StringTokenizer is instructed to parse equal sign characters
out of the stream. This allows the configuration file to omit
the equal sign and still function as expected. Only one config
entry per line is allowed because readLine()
is used to retrieve the entries.
File extension to MIME type mapping is contained in a hash table
called suffix. The file extension
is used as the key. If a configuration file contains multiple
entries for an extension, only the last one will be stored. Hash
table put operations overwrite
duplicate entries.
There are many third-party tools available to analyze server log
files, provided they are in a "common log format":
hostname identd authuser [date] "request"
status length
The first entry, hostname,
is the name of the host this connection was received from. If
the name cannot be established, this field will display the numeric
IP address of the caller. The next two fields contain information
for "login" and authorized user names. The HTTP server
does not support remote identification dialogs, so these fields
will appear as a single dash (-).
The date field has this format:
DD/MMM/YYY:HH:MM:SS GGGG
All these fields are evident except GGGG, which represents the
number of time zones away from GMT.
The request field is the first line from the request header. status
is the HTTP response code that was sent, typically 200, and length
is the total number of file bytes sent in the response. Here is
a sample log entry:
some.name.com - - [25/Mar/1996:08:48:23
-0400] "GET / HTTP/1.0" 200 342
The 200 indicates that a response containing 342 bytes was sent.
HTTP requests are already stored in the HTTPrequest class, so
it's reasonable to also store the log information in this class.
If the server is ever made to work concurrently, the log information
will have to travel with each request, so just add it there now:
/**
* This class maintains all of the information from a HTTP request
*/
public class HTTPrequest
{
...
public StringBuffer log;
/**
* Create an instance of this class
*/
public HTTPrequest()
{
...
log = null;
}
...
}
A StringBuffer is used because the string will grow and be altered.
Java String objects are immutable-they can never be altered after
they are created. If you use the plus operator to grow a String,
you are actually creating a new String:
String first = new String("This
is the first string.");
first += " This is the second string";
The first variable now contains
a completely different string, constructed from the first string
and the addition of the second string. The original contents of
the variable are lost.
The log string will need some tweaking to bring it in line with
the common log format. That is why a StringBuffer class is used.
After a request is parsed, a new routine is added to HTTPrequest
to format the initial portions of the log entry:
/**
* Log a complete request.
*/
public void logRequest()
{
Date current = new Date();
log = new StringBuffer("");
if ( clientSocket == null )
log.append("-");
else
log.append(clientSocket.getInetAddress().getHostName());
log.append(" - - ");
log.append(formatDate(current));
log.append(" \"" + firstLine
+ "\" ");
}
The Date Class
The Date class is part of the java.util package. It provides full
support for both date and time. Six different constructors for
the class, shown in the following list, offer a great deal of
flexibility in how a date is specified.
| Date Class Constructors |
Date()
Date(long totalSecs)
Date(int yr, int mth, int day)
Date(int yr, int mth, int day, int hrs, int min)
Date(int yr, int mth, int day, int hrs, int min, int sec)
Date(String s)
|
The first constructor, the most commonly used, constructs a Date
class reflecting the current date and time. The other constructors
are used mainly to construct Date objects that are either forward
or backward in time. This could be done for comparison purposes:
Date c = new Date();
Date p = new Date( c.getYear() - 1, c.getMonth(), c.getDate()
);
Date n = new Date( c.getYear() + 1, c.getMonth(), c.getDate()
);
if ( p.before( c ) )
System.out.println("Date " +
p + " is before Date " + c);
if ( n.after( c ) )
System.out.println("Date " +
n + " is after Date " + c);
The Date class provides three useful comparison functions:
- boolean before(Date d)
- boolean after(Date d)
- boolean equals(Date d)
Several public methods get and manipulate Date class variables.
Table 12.2 lists the remaining public methods of the Date class.
Table 12.2. Public methods of the Date class.
| Method | Synopsis
|
| int getYear()
| Year since 1900 |
| void setYear(int y)
| Year since 1900 |
| int getMonth()
| Month (0-11) |
| void setMonth(int m)
| Month (0-11) |
| int getDate()
| Day of month (1-31) |
| void setDate(int d)
| Day of month (1-31) |
| int getDay()
| Day of week (0-6)[0=Sunday] |
| int getHours()
| (0-23) |
| void setHours(int h)
| (0-23) |
| int getMinutes()
| (0-59) |
| void setMinutes(int m)
| (0-59) |
| int getSeconds()
| (0-59) |
| void setSeconds(int s)
| (0-59) |
| long getTime()
| Milliseconds since 1970 |
| void setTime(long totalSecs)
| Milliseconds since 1970 |
| int hashCode()
| Return the object's hash code |
| String toString()
| Uses UNIX ctime conventions |
| String toLocaleString()
| Uses locale conventions |
| String toGMTString()
| Uses Internet GMT conventions |
| int getTimezoneOffset()
| Minutes from GMT |
Creating Common Log Date Format
None of the toString() variants
in the Date class match common log format. The following routines
use a Date class to create the correct common log date format:
/**
* Return the passed date as a common
log format String.
* @param d contains the date to convert
*/
public String formatDate(Date d)
{
return "["
+ formatFor2(d.getDate()) + "/" + getMonthName(d) +
"/"
+ (d.getYear() + 1900) +
":"
+ formatFor2(d.getHours()) +
":"
+ formatFor2(d.getMinutes()) +
":"
+ formatFor2(d.getSeconds()) +
"
" + getTimezone(d) + "]";
}
/**
* return a String of the passed int. The
String will
* be formatted to take up at least two
places.
*/
public String formatFor2(int n)
{
String ret;
if ( n < 10
)
ret
= new String("0" + n);
else
ret
= new String("" + n);
return ret;
}
/**
* Return the timezone formatted for common
log format.
* @param d contains the date to convert
*/
public String getTimezone(Date d)
{
String ret;
int tz = d.getTimezoneOffset();
int lf = tz %
60; //
check for remainders
tz /= 60; //
change to hours
// The polarity
of getTimezoneOffset is backwards.
// Positive values
mean you are behind GMT.
// Negative values
mean GMT is behind you.
if ( tz > 0
)
ret
= "-";
else if ( tz <
0 )
ret
= "+";
else
ret
= "";
ret += formatFor2(tz)
+ formatFor2(lf);
return ret;
}
/**
* Return a String representing the month
name in
* common log format.
* @param d contains the date to convert
*/
public String getMonthName(Date d)
{
switch ( d.getMonth()
)
{
case 0: return
"Jan";
case 1: return
"Feb";
case 2: return
"Mar";
case 3: return
"Apr";
case 4: return
"May";
case 5: return
"Jun";
case 6: return
"Jul";
case 7: return
"Aug";
case 8: return
"Sep";
case 9: return
"Oct";
case 10: return
"Nov";
case 11: return
"Dec";
}
return "-";
}
}
Integers will print in their current precision. Common log format
calls for two-digit numbers. The method formatFor2()
will add a zero at the beginning if the passed integer is less
than 10.
Notice the polarity switch of getTimezoneOffset().
Common log format as well as most other time-zone representations
use hours away from GMT (locale - GMT).
| Note |
The getTimezoneOffset() method returns the number of minutes GMT is away from your locale (GMT - locale). Normal convention is to use the number of minutes your locale is away from GMT (locale - GMT). Be careful when displaying time-zone information.
|
Now the HTTPrequest contains the request and the initial parts
of the log string. The remaining two pieces of log information,
status and length,
are filled in by the send routines.
A nice feature of many servers is that they send a standard HTML
file for error states. For instance, file error404.html is returned
when a request is not found, which enables you to configure what
is sent for various error conditions. Previously, the status of
a transmission was always 200 OK.
Now, negative responses also need to use the services of sendFile().
Status and the description string need to be passed in by the
caller. This has another advantage because log information is
written in the sendFile()
routine, which means that negative responses are also recorded.
/**
* Send a negative (404 NOT FOUND) response
* @param request the HTTP request to
respond to.
*/
private void sendNegativeResponse(HTTPrequest
request)
{
try
{
String
fileToGet = "error404.html";
FileInputStream
inFile = new FileInputStream(fileToGet);
if
(debug & level < 4)
{
System.out.print("DEBUG:
Sending -rsp ");
System.out.print(fileToGet
+ " " + inFile.available());
System.out.println("
Bytes");
}
sendFile(request,
inFile, 404, "Not Found", fileToGet);
inFile.close();
}
catch (FileNotFoundException
fnf)
{
System.out.println("Error:
No error404.html file for -rsp");
}
catch (ProtocolException
pe)
{
}
catch (IOException
ioe)
{
System.out.println("Unknown
file length: " + ioe);
}
}
/**
* Send the passed file
* @param request the HTTP request instance
* @param inFile the opened input file
stream to send
*/
private void sendFile(HTTPrequest request,
FileInputStream
inFile,
int
status, String describe,
String
fileToGet)
{
DataOutputStream
outbound = null;
String type =
null;
try
{
//
Acquire an output stream
outbound
= new DataOutputStream(
request.clientSocket.getOutputStream());
//
Send the response header
int
period = fileToGet.lastIndexOf('.');
if
( period != -1 && period != fileToGet.length() )
type
= (String)suffix.get(fileToGet.substring(period + 1));
if
(type == null)
type
= "text/plain";
if
(debug & level < 4)
{
System.out.println("DEBUG:
Type " + type);
}
outbound.writeBytes("HTTP/1.0
" + status + " " + describe + "\r\n");
outbound.writeBytes("Content-type:
" + type + "\r\n");
outbound.writeBytes("Content-Length:
" + inFile.available() + "\r\n");
outbound.writeBytes("\r\n");
request.log.append(status
+ " " + inFile.available());
System.out.println(request.log.toString());
if
( logging )
logFile.print(request.log.toString()
+ "\r\n");
//
Added to allow Netscape to process header properly
//
This is needed because the close is not recognized
sleep(5000);
//
If not a HEAD request, send the file body.
//
HEAD requests only solicit a header response.
if
(!request.method.equals("HEAD"))
{
byte
dataBody[] = new byte[1024];
int
cnt;
while
((cnt = inFile.read(dataBody)) != -1)
outbound.write(dataBody,
0, cnt);
}
//
Cleanup
outbound.flush();
outbound.close();
request.inbound.close();
}
catch (IOException
ioe)
{
System.out.println("IOException
while sending file: " + ioe);
}
}
Notice how the MIME content type is derived from the file's extension.
Using a hash table for this information allows a rapid lookup.
If the extension is not found, or if the file has no extension,
the file is sent as type text/plain.
Now that the server can send various MIME content types, it's
time to investigate how to use HotJava content handlers. This
exercise is small in scope, but large in know-how.
HotJava comes equipped with four content handlers, in addition
to its standard HTML handler:
- text/plain
- image/gif
- image/x_xbitmap
- image/x_xpixmap
This exercise adds two new types, text/fee
and text/foo. Since these
types aren't within the local system, HotJava will try to load
them from the server:
GET /classes/net/www/content/text/fee.class
HTTP/1.0
GET /classes/net/www/content/text/foo.class HTTP/1.0
A HotJava content handler consists of a single class that extends
the ContentHandler class. Its class name must be the same as the
MIME sub-type that it handles. In addition, it must have the same
path it would have if it were local to HotJava.
The code below implements the text/fee
content handler:
package net.www.content.text;
import net.www.html.ContentHandler;
import net.www.html.URL;
import java.io.InputStream;
/**
* A content handler for text/fee objects
*/
public class fee extends ContentHandler
{
/**
* Read in an ASCII text file and append
an
* ID string to the end.
* @param is holds the input stream for
the content
* @param u holds the URL for the object
*/
public Object getContent(InputStream is,
URL u)
{
StringBuffer sb
= new StringBuffer();
int c;
while ((c = is.read())
>= 0)
{
sb.appendChar((char)c);
}
sb.append("\n\n\nThis
is a fee ASCII text file.\n");
sb.append("Rendered
by the fee content handler!\n");
is.close();
return sb.toString(); //
return a string object
}
}
This handler expects an ASCII text file to be its input. It will
continue reading until the end of the stream. When the stream
is finished, this handler adds an identification string to the
end of the text.
All content handlers return some type of object to the browser.
The browser uses instanceof
calls to figure out how to display the object. Obviously, HotJava
can handle string data. More complex types, such as images, would
return an instance of the Observable class. Inspecting the GIF
content handler shows that it returns a GIFImage, which descends
from DIBitmap, which itself descends from class Observable.
Compile the code by invoking HotJava's version of javac:
c:\hotjava\bin\javac fee.java
Now add the new content type to the server config file: server.cfg.
suffix= fee text/fee
Finally, run the server and then HotJava. Remember to use the
server's new name: HTTPServer. Point the browser at the server
test file: test.fee.
http://your_host/test.fee
You should see the display shown in Figure 12.2.
Figure 12.2 : Rendition of test fee using the fee content handler.
HotJava downloaded the new handler, installed it, and used it
to render the new type! You can check this by looking at the output
of the server:
merlin - - [25/Mar/1996:16:07:56 -0400]
"GET /test.fee HTTP/1.0" 200 120
merlin - - [25/Mar/1996:16:08:09 -0400]
"GET
/classes/net/www/content/text/fee.class HTTP/1.0" 200 834
Did you notice the strange boxes at the end of each text line
in the HotJava display? Those are carriage returns. HotJava doesn't
know how to display these, so it uses a box.
Copy the fee.java file to foo.java. Edit the new foo handler so
that it removes carriage returns whenever it sees them.
package net.www.content.text;
import net.www.html.ContentHandler;
import net.www.html.URL;
import java.io.InputStream;
/**
* A content handler for text/foo objects
*/
public class foo extends ContentHandler
{
/**
* Read in an ASCII text file and append
an
* ID string to the end.
* @param is holds the input stream for
the content
* @param u holds the URL for the object
*/
public Object getContent(InputStream is,
URL u)
{
StringBuffer sb
= new StringBuffer();
int c;
while ((c = is.read())
>= 0)
{
if
( c == '\r' ) continue; // remove carriage
returns
sb.appendChar((char)c);
}
sb.append("\n\n\nThis
is a foo ASCII text file.\n");
sb.append("Rendered
by the foo content handler!\n");
is.close();
return sb.toString(); //
return a string object
}
}
First, compile the new foo handler:
c:\hotjava\bin\javac foo.java
Add the new "foo" suffix to the server configuration
file:
suffix= foo text/foo
Next, restart the server and HotJava. Point the browser to the
following statement:
http://your_host/test.foo
The boxes have disappeared! You should see the display shown in
Figure 12.3.
Figure 12.3 : Rendition of test foo using the foo content handler.
The server log shows the following:
merlin - - [25/Mar/1996:16:10:17 -0400]
"GET /test.foo HTTP/1.0" 200 120
merlin - - [25/Mar/1996:16:10:24 -0400]
"GET
/classes/net/www/content/text/foo.class HTTP/1.0" 200 845
Again, the handler was dynamically loaded by HotJava.
This chapter covers the dynamic Web browser HotJava. After some
initial background, the HotJava class hierarchy is reviewed and
the HotJava concept of dynamic content handlers is introduced.
To demonstrate dynamic content, HotJava's source is patched to
enable it to operate with the HTTP server from previous chapters.
The server itself receives a face-lift that brings it more in
line with a standard HTTP server. In light of the changes, its
name is changed to HTTPServer. You add a configuration file and
logging.
You learn about the MIME content types as well as how servers
use file extensions to decide what content type to send for a
specific file.
The Date class is reviewed, and the common log format is introduced.
Finally, you learn the basics of writing a HotJava content handler.

Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.