Servlets, the Java equivalent of CGI applications, can deliver on many of Java's promises while dodging some of its worst limitations.
Jon Udell
For many months I wondered when and how Java would first appear on The BYTE Site. I was determined not to use Java in a gratuitous way; the Web certainly doesn't need any more scrolling marquees. Java would have to earn its keep by solving real problems. What broke the logjam was the alpha release of JavaSoft's Jeeves (
http://jeeves.javasoft.com/
) (aka
JavaSoft's Java Web Server), which can run Java extensions called servlets.
Like CGI programs, servlets are easy to write and easy to run, and they play to the entire installed base of browsers. Servlets can do things applets can't -- write to files, open socket
s -- and they can do them very quickly because they're invoked as threads in a demon process.
The truth is that I still haven't found a compelling reason to send Java applets over the wire to your browser. HTML assisted by JavaScript can handle a remarkably wide range of user-interface and data-collection chores -- not as prettily as Java, I'll grant, but a lot more efficiently. Client-side Java will really flower on next-generation computers and networks. But server-side Java is ready for prime time now.
My First Servlet: A URL Redirector
Way back in my February 1996 column, I showed how to track the use of individual links on a Web page. I'm still using that mechanism -- a Perl sc
ript that logs data and then returns a "Location:" header -- but I've grown increasingly aware of its shortcomings. Mostly it's just too slow.
In part that's because I've been unable to get the ISAPI version of NT Perl to cooperate with the O'Reilly WebSite server that handles most of our site's CGI work. But even when ISAPI Perl works, it's still not a panacea. "In-process Perl doesn't deliver the speedup you'd expect," observes Bob Denny, WebSite's creator, "because all that OLE crap has to get initialized every time."
Perl just isn't a good way to implement lightweight services. And it's terrible on NT, which lacks the fork mechanism that Unix-based Perl servers rely on for a kind of poor man's multithreading. A classic Unix socket server forks copies of itself to handle incoming requests, so the parent process can remain responsive to new requests. Perl can't do this on NT.
Unix partisans like to blame "brain-dead" NT for this. But there's another side to the story. Unix-style process-cl
oning is not a substitute for real lightweight multithreading, which is built into NT. Unfortunately, Perl isn't multithreaded and can't take full advantage of NT (or other threaded OSes).
Java, on the other hand, is an almost ideal way to build lightweight services. Given a Java-oriented Web server, you can create lightweight Web services, or
servlets
, that are automatically threaded and extremely responsive. And thanks to the Java frameworks that support servlets, they needn't be much more complicated than their Perl counterparts (see the listing
"URL Redirection in Perl and Java"
). The day I wrote my first servlet it went into production, and it has now been used by thousands of visitors to The BYTE Site.
Our site's inaugural Java deployment doesn't do anything flashy. It just streamlines some basic accounting tasks. If you've used that servlet, you almost certainly did not realize you were tapping a Java-based service. That's precisely why I say that Java is now
ready for real server-side work.
Deploying Servlets
For its first few weeks, my Java redirector ran as a Jeeves servlet. Now in beta, Jeeves is a full-blown Web server that supports user/group access controls, Secure Sockets Layer (SSL), and proxying, and it can also run servlets. To run Jeeves, you fire up the Java interpreter and load the Jeeves classes. The Web server appears on port 8080.
An administrative server simultaneously appears on port 9090. The Java applet that you use to manage Jeeves
looks sexy
, I'll admit, but I soon concluded that it's yet another example of gratuitous Java. Nothing that it does couldn't be done in HTML/JavaScript. Waiting for a dozen classes to load before being able to set a password on the server quickly grows tiresome. And since the Jeeves beta reset itself to the default administrative password every time I ran it, I had to do a lot of waiting.
Eventually I realized that I didn't need most of Jeeves; I only neede
d a platform for servlets. Jeeves was overkill, and all the extra stuff it can do was just causing headaches. Was the administrative applet adequately secured? Should Jeeve's CGI servlet be disabled to ward off possible attacks? There had to be a simpler way to run servlets.
Enter Acme.Serve, Jef Poskanzer's minimal Java Web server (
http://www.acme.com/java/software/Acme.Serve.Serve.html
). This brilliant contribution to the Web emulates the Jeeves servlet API, runs servlets handily, cooperates with version 1.1 of the Java Development Kit (JDK), and (unlike Jeeves) includes source code. Thanks, Jef! My redirector ran immediately under Acme.Serve, and I have been using it ever since. It was easy to modify Acme.Serve so that the server responds only to the handful of URLs that in
voke the servlets I choose to export.
I appreciated being able to tweak a few other things, too. For example, when the servlet logged the requesting browser's address, it wrote both a Domain Naming System (DNS) name and an IP address into the log. But I didn't want to log the DNS names. I don't want users to wait for reverse DNS lookups; it's my policy to do those lookups off-line in batch analysis. Adjusting the
getRemoteAddr() method was straightforward.
There are other ways to run servlets. The recently released first beta of the Java Web Server comes with a ServletRunner that will run a servlet without all of Jeeves's baggage. The World Wide Web Consortium recently announced that its Jigsaw (http://www.w3.org/pub/WWW/Jigsaw/
), the original Java Web server, will be compatible with JavaSoft's servlet API. There's also a servlet API in Netscape's Enterprise Server 3.0, although I found no examples of its use in the currently available beta version of that product and so have not yet tried it.
Making the Hard Things Easy
With servlet technology in hand, I next tackled a project that I ordinarily would have handled in Perl. The task: to write a service that would enable users to create quick polls, vote in polls, and check the results of polls. The resulting servlet, which is called Polls (
http://www.byte.com/art/download/polls.zip
), makes a fascinating counterpoint to the kinds of Perl applications I'm used to building.
Larry Wall, Perl's creator, likes to say that Perl aims to makes easy things easy and hard things possible. Java, on the othe
r hand, tends to make hard things easy, but easy things hard. You'll see what I mean as I describe how Polls works.
At the heart of Polls is a data structure that Perl hackers call a hash-of-hashes (HoH) -- that is, an associative array (i.e., a set of name-value pairs) whose values are in turn another set of associative arrays (see the figure
"The Polls Servlet's Central Data Structure"
). In Perl, as in Java, it's easy to grow this object on the fly. But Perl in a CGI context does not readily handle the following requirements:
-
Retain
the object in memory across multiple invocations of the application.
-
Protect
the object from concurrent use by multiple clients.
-
Retrieve
the object from disk at start-up and keep the in-memory version synched with the on-disk version as updates occur.
These are the hard things that become easy in a Java servlet. When the server instantiates the Polls servlet, its
class data (the HoH) hangs around indefinitely -- until either the server or the servlet restarts. A typical Perl solution would have to refresh its in-memory objects from disk (e.g., by doing a database query or reading in a structured text file) every time a client created a new poll or voted in a poll.
In Java, protecting the object from multiple concurrent voters is as easy as adding the synchronized keyword to the declaration of the
vote()
method. Saving and restoring the object are trivial tasks, too, thanks to the serialization technology in JDK 1.1. The poll data lives in a Java hash table, which implements the Serializable interface. That means you can simply open a
FileOutputStream
, hook an
ObjectOutputStream
to it, and call
Polls.writeObject(stream)
to save it to disk.
Restoring the in-memory object is just as easy to do. Adding the synchronized keyword to my
saveObjects()
method was all it took to guard the on-disk object store against corru
ption by multiple update threads.
What about full-fledged object databases? You want one of those if you're dealing with objects that are too large to hold conveniently in memory. Polls, however, is tiny and not likely to get much bigger. Each of the polls it manages is really just a namespace that defines a set of counters. It's the number of counters that determines the size of the data structure, not the number of votes tallied by each counter.
There are a lot of applications in this category. Group scheduling, for example, tends to generate fairly small amounts of complex object data. With nothing more than a servlet engine, the JDK 1.1, and a bit of ingenuity, you can create useful applications in this domain very quickly.
Making the Easy Things Hard
A few things that would have been trivial in Perl consumed most of the time I spent on the Polls servlet. First, there was the problem of sorting the results of each poll. In Perl, that takes just a few lines of code. You
can build an array of strings out of the values and keys of each poll and then do this:
print reverse sort @array;
I searched the Java API docs for quite a while before it dawned on me that there just isn't anything equivalent to a Smalltalk
OrderedCollection
in Java. (Try looking for the word
sort
in the index of any Java book. You won't find it.) This is a real shame. Java gives you incredible power to create and manage dynamic, thread-safe, persistent object data, but it has absolutely no tools to manipulate that data in the most elementary ways.
Of course, there are Smalltalk-style libraries for Java. The best of these looks to be the Java Generic Library (JGL,
http://www.objectspace.com/
). It's an outstanding piece of work that's freely available and does a
ll the sorting, filtering, and queueing that you'll ever need. It's also a huge chunk of code.
I decided not to kill my fly-size sorting problem with the hammer of JGL. A minimal
SortedStringVector
class was all my servlet needed, so I wrote one. But there should be a middle ground. The Java core should provide at least basic sorting.
Another gotcha is the chasm that divides primitive Java types (i.e.,
int
) from their object counterparts (i.e.,
Integer
). Each poll's hash table contains a set of keys (the names of the choices in that poll) and values (the count of votes for each choice). Both the keys and the values must be objects, not primitive types. But you cannot increment an
Integer
, so the
vote()
method has to unpack the
Integer
, increment its corresponding
int
, and then repackage it as an
Integer
to store it back in the hash table, as shown below.
Integer ObjectTally = (Integer)
hPoll.get ( "choice1" );
i
nt tally = ObjectTally
.intValue();
tally++;
hPoll.put ( "choice1"), new
Integer (tally) );
which in Perl would reduce to simply
$hPoll{'choice'}++;
Why can't you just say
ObjectTally++
? Java's not C++; it doesn't support operator overloading. And while I'm whining...What, no
printf
-style formatting? Excuse me? Writing Java routines to pad numbers with leading zeros seems like a very silly thing to do. Again, there are, of course, Java libraries that implement
printf
. But these implementations aren't in the language's core, and they won't be standard.
Did You Run Any Java Applets Today?
It's a peculiar moment in our industry's history. The Java buzz is intense. And yet when you look at the Web applications that people actually use every day to do their work, you invariably find that there are no Java applets in the mix. The universal client today is still the HTML browser. The universal client of tomorrow will be the H
TML/JavaScript browser.
Client-side Java is a glorious vision that will not change the way most people use the Internet anytime soon. Why not? It's just more than what the majority of today's computers and networks can readily push. So what are millions of people running every day? Server-based applications that feed the universal HTML client.
I build such applications every day, and I am wildly excited about how Java can help. You won't find dancing penguins on The BYTE Site. But behind the scenes, Java will be helping me run the show.
TOOLWATCH
Ntcrond 2.2................$25
#ifdef Software
Internet: http://www.ifdef.com
Tasks that NT's dim-witted scheduler struggles mightily with -- such as "run this command every hour at 10 minutes past the hour" -- are trivial matters for Unix's crontab. Here's a capable NT version that's threaded, runs as a service, and does its job nicely.
BOOKNOTE
The Java Programming Language........................$34.95
by Ken Arnold and James Gosling
Addison-Wesley
ISBN 0-201-63455-4
Along with David Flanagan's indispensable Java in a Nutshell, this authoritative guide has risen to the top of my heap of Java books. When you get curious about things like synchronization, thread scheduling, and class loading, you might as well go to the source -- James Gosling, Java's inventor -- for answers.
A Perl redirector, invoked as http://www.byte.com/cgi-bin/goto.pl?http://elsewhere.com.
Here's a classic CGI script. It logs the user's IP address and
redirects the user to another Web page. You can write this in just
four lines of Perl. But it's computationally expensive to run the
script.
require 'cgi-lib.pl'
open(LOG,">>goto.log");
print LOG "$ARGV[0]~$ENV{HTTP_REMOTE_ADDR}\n";
print "Location: $ARGV[0]\n";
A Java redirector, invoked as
http://www.byte.com:8080/gotoUrl?http://elsewhere.com.
Here's the same logic in Java. Thanks to the servlet API, it's only a
bit more complex than the four-line Perl script. And it's far more
efficient because the servlet runs as a thread dispatched by a Java
Web server.
import java.io.*;
import java.util.*;
import java.servlet.*;
import java.servlet.http.*;
public class gotoUrl extends HttpServlet {
public void service(HttpServletRequest
req, HttpServletR
esponse res)
throws ServletException, IOException
{
DataOutputStream log = new DataOutputStream
(new FileOutputStream("goto.log",true));
log.writeChars(req.getQueryString() + "~" +
req.getRemoteAddr() + "\n");
log.close();
res.sendRedirect(req.getQueryString());
}
}
illustration_link (11 Kbytes)

In Java, as in Perl, you can dynamically create complex nested data structures.
photo_link (26 Kbytes)

screen_link (18 Kbytes)

This applet, used to control the Java Web Server, looks spiffy. But the novelty soon wears off.
Jon Udell is BYTE's executive editor for new media. You can reach him by sending e-mail to jon@byte.com
.