The SSIResource
(w3c.jigsaw.ssi.SSIResource)
is a Jigsaw resource that provides a flexible way of generating part of the
content of a document from individual pieces. This may sound too general,
and that's because there is little constraint on the way the constituent
pieces are generated. For example, one use of the SSIResource is the traditional
one: the content of any resource can be embedded within any document exported
by the SSIResource, by using the include
command from the default
command registry. Some other of the default commands allow you to include
the size of the document, the time of day, the hit count, and other general
data.
One of the goals of this tutorial is to show that the SSIResource is useful beyond its traditional use, as a powerful way of creating documents with a dynamically generated content. It is assumed that you are familiar with the administration of Jigsaw in general.
The SSIResource will scan through the text of the file looking for a special
kind of HTML comment. If it finds something of the form
<!--#command par_1=val_1 par_2=val_2 ... par_n=val_n -->
,
it will interpret it as a command. par_1
... par_n
are the names of the parameters, and val_1
...
val_n
are their values. The values can optionaly be enclosed
in single or double quotes; otherwise they are delimited by ASCII white space.
For example, the string <!--#include
virtual="doc.html"-->
denotes a call to a command called
"include", with one parameter called "virtual" that has a value
of "doc.html".
Upon finding a command, the SSIResource will look it up in an object
called the command registry. The command registry returns the
command that is registered by that name. Then, it will call the
command's execute
method with the specified parameters, and
with other contextual data.
Command registries are objects of class
w3c.jigsaw.ssi.CommandRegistry.
Since this is an abstract class, a concrete implementation of one must be
available for SSIResource to work. One such implementation is supplied with
the distribution: it is
w3c.jigsaw.ssi.DefaultCommandRegistry,
which includes the bread-and-butter SSI commands. Commands are implementations
of the w3c.jigsaw.ssi.Command
interface. The SSIResource declares a registryClass
attribute,
which is set to the particular command registry to use in parsing a given
document.
Therefore, the way to extend the SSIResource is to create (either from scratch or by subclassing an existing one) a command registry that knows about the new commands that are being added. A good way to become familiar with these classes is too look at the code for DefaultCommandRegistry and its superclass, BasicCommandRegistry, and at the code for the default commands (in rough order of complexity): SampleCommand, CountCommand, ConfigCommand, FSizeCommand, FLastModCommand, EchoCommand, IncludeCommand.
With this in mind, let's implement a useful extension of SSIResource.
There is an existing Jigsaw resource (w3c.jigsaw.status.Statistics) that is used to display the internal statistics of the server. In what follows, we will mimic the functionality of this resource with an SSI command. There is an object that supplies all these statistics for us; its class is w3c.jigsaw.http.httpdStatistics and it can be obtained from the server. Our SSI command will query this object and emit the values. We'd like to be able to use it like this: <!--#stat data=<type> -->, where <type> specifies the particular statistic that is going to be inserted, and is one of:
serverLoad
freeThreads
idleThreads
totalThreads
hitCount
meanReqTime
maxReqTime
maxReqURL
minReqTime
minReqUrl
emittedBytes
Each of them will correspond to one of the methods in httpdStatistics.
stat
command
This command can be written in a very straightforward manner. All we have to do is:
This translates to the following java class, which will be called
StatCommand:
package w3c.jigsaw.tutorials ; import java.util.* ; import w3c.jigsaw.http.* ; import w3c.www.http.HTTP ; import w3c.jigsaw.ssi.* ; import w3c.util.* ; public class StatCommand implements Command { private static final String NAME = "stat" ; public final String getName() { return NAME ; } public Reply execute(SSIResource resource, Request request, ArrayDictionary parameters, Dictionary variables) { // Obtain the statistics from the server httpdStatistics stats = resource.getServer().getStatistics() ; // Get the parameter specifying the kind of statistic to emit. String data = (String) parameters.get("data") ; // If the parameter is not supplied, do nothing if(data == null) return null ; // Otherwise, compare it against the possible different keywords // (Since there are no "pointers to methods", this is the simplest way it // can be written) long result = -1 ; String urlResult = null ; if(data.equalsIgnoreCase("serverload")) { result = stats.getServerLoad() ; } else if(data.equalsIgnoreCase("freethreads")) { result = stats.getFreeThreadCount() ; } else if(data.equalsIgnoreCase("idlethreads")) { result = stats.getIdleThreadCount() ; } else if(data.equalsIgnoreCase("totalthreads")) { result = stats.getTotalThreadCount() ; } else if(data.equalsIgnoreCase("hitcount")) { result = stats.getHitCount() ; } else if(data.equalsIgnoreCase("meanreqtime")) { result = stats.getMeanRequestTime() ; } else if(data.equalsIgnoreCase("maxreqtime")) { result = stats.getMaxRequestTime() ; } else if(data.equalsIgnoreCase("maxrequrl")) { urlResult = stats.getMaxRequestURL().toExternalForm() ; } else if(data.equalsIgnoreCase("minreqtime")) { result = stats.getMinRequestTime() ; } else if(data.equalsIgnoreCase("minrequrl")) { urlResult = stats.getMinRequestURL().toExternalForm() ; } else if(data.equalsIgnoreCase("emittedbytes")) { result = stats.getEmittedBytes() ; } else return null ; // Make a reply with the datum and return it Reply reply = resource.createCommandReply(request, HTTP.OK) ; reply.setContent( urlResult == null ? Long.toString(result) : urlResult ) ; return reply ; } } |
The Command interface defines
two methods. The getName
method simply returns a String with
the name of the command. The execute
method is the one that
does the work. This method can be thought of as the get
method
in a resource: it takes, among other things, a
Request object, and it
produces a Reply object.
The SSIResource will insert the contents of the replies of each of the commands
(partial replies) into the main, global, content, and it will also
merge the relevant headers of the partial replies into the headers of the
global reply. Besides taking a request, the execute
method takes
these arguments as well:
w3c.jigsaw.ssi.SSIResource
resource
w3c.util.ArrayDictionary
parameters
java.util.Dictionary variables
The execute
method can also return
null
, which is interpreted as the absence of
output. There are some subtle differences between the execute
method and a regular resource get
method. In particular, care
must be taken if dealing with conditional requests. This example is simple
enough that this is not a concern.
Now that the command itself is finished, we need to make it part of a command registry, so that it can be actually used in documents.
Since we'd like to be able to use the "standard" SSI commands in adition
to our brand-new stat
command, it's not a bad idea to make our
new registry a subclass DefaultCommandRegistry. The way to do this is very
straightforward:
package w3c.jigsaw.tutorials ; import w3c.jigsaw.ssi.* ; public class MyCommandRegistry extends DefaultCommandRegistry { public MyCommandRegistry() { registerCommand(new StatCommand()) ; } } |
The constructor simply calls the registerCommand
method (defined
in
BasicCommandRegistry),
with a new instance of the command that we're adding.
We're now ready to use this command in a future document.
One way of using the newly-created command registry is to change the
registryClass
attribute defined for the .shtml extension
to "w3c.jigsaw.tutorials.MyCommandRegistry". After doing that,
Jigsaw will use the new registry when indexing new files with the
.shtml extension (or reindexing old files). Then we can create
a file that makes use of the new command, and place it in a Jigsaw-accesible
directory. For example, we could do this:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> <meta http-equiv="Refresh" content="5"> <title>Server Statistics</title> </head> <body> <ul> <li>hits: <!--#stat data=hitCount --> <li>bytes: <!--#stat data=emittedBytes --> </ul> <p>Request processing times: <table border> <tr> <th align="center"> min <th align="center"> avg <th align="center"> max </tr> <tr> <th align="center"> <!--#stat data=minReqTime --> <th align="center"> <!--#stat data=meanReqTime --> <th align="center"> <!--#stat data=maxReqTime --> </tr> </table> <p>Thread counts: <table border> <tr> <th align="center"> free <th align="center"> idle <th align="center"> total </tr> <tr> <th align="center"> <!--#stat data=freeThreads --> <th align="center"> <!--#stat data=idleThreads --> <th align="center"> <!--#stat data=totalThreads --> </table> <p>Current load: <!--#stat data=serverLoad --> </body> </html> |
The above document will produce exactly the same output that the Statistics resource would emit.
At this point we can compare two different approaches to generating HTML dynamically. The first one involves writing a new, specialized, resource. The approach illustrated in this tutorial consists of writing an SSI command and serving the document with the SSIResource. Doing it this way has these advantages:
One disadvantage of the SSI approach is the extra overhead incurred at serve-time of constructing the content from the pieces supplied by the commands. The SSIResource tries to avoid this overhead as much as possible. The most important optimization in this respect is the fact that the parsing of the document (i.e., scanning the text for commands, and reading the parameters) is done only when the file is modified. Even then, each command needs to check its parameters, which does add to serve-time overhead.
Antonio Ramírez
$Id: SSIResource.html,v 1.9 1997/02/04 22:04:40 abaird Exp $