People are becoming increasingly accustomed to "finding things on the Web." The pages they view may be static or may be produced dynamically by a variety of technologies such as CGI scripts, Active Server Pages, Java servlets, Java Server Pages, etc. This chapter looks at how servlets can be written that use Jini services to generate content.
Along with email, the Web is the most widely used internet service used by most people. The explosive growth in the number of Web pages over the years has led people to just "look for it on the Web."
Initially all Web pages were static HTML documents, existing as files on an HTTP server. CGI (Common Gateway Interface) was introduced as a means of generating content dynamically. A CGI script is a program called by the HTTP server to handle a document request, and the program is expected to write a valid HTML (or other) document to its standard output which is returned to the user.
CGI scripts may be written in any language, but gain their name because the languages used are often scripting languages such as one of the Unix shells, Perl, Python, etc. However, any language can be used such as C, C++, Eiffel - just as long as a single program can be called which will process the request.
Java is not good for CGI scripts, because it requires calling a runtime
engine such as jre
with an argument that is the class
to load so it can call the main()
method. That is, starting
a Java program requires calling the runtime with an additional
argument, and CGI is not designed to deal with such situations.
Rather clumsily,
to use a Java class as CGI script requires writing a shell script or
DOS batch file that calls the Java runtime with class name as argument.
CGI scripts have other drawbacks: each invocation of a CGI script runs as a separate process. This causes runtime overheads, as it may involve loading and starting a runtime interpreter such as the Java, Perl, tcl or shell interpreters. These are all large programs with substantial initialisation processing, and having to repeat this for each CGI call can waste large amounts of time.
Loadable modules are employed to reduce these overheads. These are loaded into an HTTP server and run as part of that server. This means that startup is only done once rather than on each invocation. In addition, the module can have access to the internals of the HTTP server and can use this to optimise calls even more. By running only once instance of the module, there are also other benefits such as easy sharing of state information across different calls to the module.
Java servlets are just one method of embedding an interpreted language into an HTTP server. The language in this case is Java, and the particular API's exposed are now part of the Java standards. Why a servlet is of more interest than other systems is that it can allow us to access Jini services and use them within the servlet, which would be impossible say, for Perl modules.
A servlet typically extends HttpServlet
. It will
usually override the methods
public void init();
public void doGet(HttpServletRequest request,
HttpServletResponse response);
throws IOException, ServletException
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException;
The init()
method is called when the servlet is first
loaded, and can be used to perform once-off initialisation.
The doGet()
and doPost()
methods are
called from HTTP GET
and POST
requests,
respectively. Usually you implement one of these in full, and just
make the other one call the first.
The request
and response
parameters are
used to get information from the HTTP request and to pass information
back as an HTML document. In particular, output of the HTML
reply document is done by getting a PrintWriter
from
response
and writing to it:
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
out.println("<title>" + "Jini Service Finder" + "</title>");
out.println("</head>");
out.println("<body>");
// etc
A Java servlet is called from a servlet engine such as the Jakarta Tomcat engine which is part of the Apache server project. This engine can be run as a module within an Apache HTTP server, or can be run as a standalone HTTP server which also interprets servlet calls.
The remainder of this section deals with the Jakarta Tomcat server in standalone mode, but much is applicable to running it as an Apache module. Some of this section may be relevant to other servlet engines. We shall discuss Jakarta Tomcat version 3.1 - due to problems with security managers, earlier versions of Tomcat will not work with Jini.
Tomcat must be run with a security manager. This is done under Unix by calling
tomcat.sh start -security
with a similar batch file for MSDOS. Unfortunately, this fails
due to a bug in the shell script. In line 124 of the script occurs
if [ "$1" = "-security" ] ; then
echo Starting with a SecurityManager
$JAVACMD $TOMCAT_OPTS -Djava.security.manager -Djava.security.policy==${TOMCAT_HOME}/conf/tomcat.policy -Dtomcat.home=${TOMCAT_HOME} org.apache.tomcat.startup.Tomcat "$@"
The Java runtime then complains about an unrecognised option
-security
. A shift
operation is missing to
lose this option:
if [ "$1" = "-security" ] ; then
echo Starting with a SecurityManager
shift # lose the -security option
$JAVACMD $TOMCAT_OPTS -Djava.security.manager -Djava.security.policy==${TOMCAT_HOME}/conf/tomcat.policy -Dtomcat.home=${TOMCAT_HOME} org.apache.tomcat.startup.Tomcat "$@"
This runs Java with a security manager - and a security policy file.
The policy file supplied with Tomcat will run Java servlets, but not
Jini clients. The policy file needs to be extended with the extra
permissions needed by Jini clients. You have the choice of either
adding the extra security options to the tomcat.policy
file, or pointing to another file with the correct permissions.
You may not have the necessary permissions to modify the script files. For example, if the system supervisor has installed Tomcat then you won't have write permission on these files. Certain other configuration files control things like the port Tomcat listens on, and you won't be able to modify those either. So you may need to negotiate changes with your sysadmin, or run a private copy of Tomcat.
Tomcat uses port 8080 by default. The HTTP server started
by reggie
also uses port 8080. If you are using Tomcat
and the reggie
lookup service on the same machine then
one of these needs to be set to use a different port.
To change Tomcat, you need to modify
$TOMCAT_HOME/conf/server.xml
.
A servlet that acts as a Jini client must know the Jini libraries.
It is enough to put the Jini jar files in the CLASSPATH
before Tomcat is run. Tomcat will add its own entries to the class path.
If your servlet client installs Jini event listeners in Jini services that
it discovers, then the client must set the
java.rmi.server.codebase
property to include the listener's
class files, so that the service can install the listener properly.
Most clients will be aware that they are installing an event listener,
but not always: if the ServiceDiscoveryManager
is used,
then it installs event listeners. The Sun implementation needs the class
net/jini/lookup/ServiceDiscoveryManager$LookupCacheImpl$LookupListener_Stub.class
to be accessible from an HTTP server.
Tomcat does not set the java.rmi.server.codebase
property.
The startup script needs to be modified again to add this to the
Java runtime command.
Once appropriate security permissions have been set up, a Java servlet
can act as a Jini client. The "brute force" way is to run everything
from each of the HTTP request methods. So in doGet()
we might look for lookup services, then query them for a desired service,
fetch the service and then finally invoke the service. This works: it is
just awfully slow and will often take several minutes (which seems
like hours in internet time...).
The tricks to get workable clients are to minimise repeated work, and to run caching mechanisms in separate threads, so that information is available immediately when an HTTP request comes in. This means
init()
method. Then store it in a field available
to every HTTP request or in a session object.
registrar.notify()
which will be called asynchronously when changes occur.
ServiceDiscoveryManager
which
will use its own thread to keep the cache up to date. (But then
you have to remember to modify the startup script to set the
java.rmi.server.codebase
property.)
The file classifier can be turned into a simple Web servlet that illustrates some of the techniques to use servlets and to make them appear responsive rather than horribly slow. A Web interface to a file classifier will firstly display a form for entry of data such as
<form action="...">
File Name <input type="textfield" name="filename">
<input type="Submit">
</form>
When this form is submitted, the servlet will get the filename
field and call the file classifier service for this filename.
The result will be returned as an HTML document.
We will use a single servlet both for the initial form and for the
reply. The initial form could have been done from a static HTML page,
but making it into a servlet call means that we will invoke the
method init()
if this is the first time this servlet
has been called. That is, the first time that a user shows interest
in the service, we can start processing for this service.
The ServiceDiscoveryManager
is of great use here.
We can use it to start building a cache of services
which runs in a separate thread.
So init()
can start the cache and return immediately.
The call to doGet()
can determine if the
filename
property is set. If not, it returns the initial
form entry page. If set, then it can query the cache for a service
and use it. By this stage, there is a strong chance that the cache
will have something in it. To get to this stage, the following
activities will have occurred
init()
will have started cache building in a separate thread
doGet()
will have returned a form to the user
doGet()
will be able to use a non-empty cache of services
without delay
There are of course a variety of complexities that can be added to this
A servlet based on the above discussion is
/**
* FileClassifierServlet.java
*/
package servlet;
import common.FileClassifier;
import common.MIMEType;
import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.core.lookup.ServiceItem;
import net.jini.lease.LeaseRenewalManager;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceID;
import net.jini.lookup.LookupCache;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class FileClassifierServlet extends HttpServlet {
private LookupCache cache;
public void init() {
ServiceDiscoveryManager clientMgr = null;
System.setSecurityManager(new RMISecurityManager());
try {
LookupDiscoveryManager mgr =
new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
null /* unicast locators */,
null /* DiscoveryListener */);
clientMgr = new ServiceDiscoveryManager(mgr,
new LeaseRenewalManager());
} catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
Class [] classes = new Class[] {FileClassifier.class};
ServiceTemplate template = new ServiceTemplate(null, classes,
null);
try {
cache = clientMgr.createLookupCache(template,
null, /* no filter */
null /* no listener */);
} catch(Exception e) {
e.printStackTrace();
System.exit(1);
}
}
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String docType =
"<!DOCTYPE HTML PUBLIC \"-//W3C/DTD HTML 4.0 " +
"Transitional//EN\"\n";
out.println(docType +
"<HTML>\n" +
"<head><title>File Classifier</title></head>\n" +
"<body>\n" +
"<h1> File Classifier </h1>");
String fileName = request.getParameter("filename");
if (fileName == null || fileName.length() == 0) {
showFirstPage(out);
} else {
handleForm(fileName, out);
}
out.println("</BODY> </HTML>");
}
public void showFirstPage(PrintWriter out) {
out.println("<form action=\"http://localhost:8088/jini/servlet/servlet.FileClassifierServlet\">\n" +
"File Name <input type=\"textfield\" name=\"filename\">\n" +
"<br>\n" +
"<input type=\"Submit\">\n" +
"</form>\n");
}
public void handleForm(String fileName, PrintWriter out) {
out.println("<h1>Filename: " + fileName + "</h1>\n");
ServiceItem item = null;
item = cache.lookup(null);
if (item == null) {
out.println("<P>No service- try again later</P>");
return;
}
FileClassifier classifier = (FileClassifier) item.service;
// out.println(" Found classifier. ");
MIMEType type = null;
try {
type = classifier.getMIMEType(fileName);
} catch(Exception e) {
out.println("<pre>" + e.toString() + "</pre>");
return;
}
// out.println(" Found type. " + type);
if (type == null) {
out.println("<P>Unknown file type</P>");
} else {
out.println("<P>Type is: " + type.toString() + "</P>");
}
}
} // FileClassifierServlet
The servlet class FileClassifierServlet
needs to be located where
the servlet engine can locate it. There is now a standard naming
convention for locating servlet classes under a complex directory
structure. If the servlet is meant to be accessed from, say, the url
http://www.jini.monash.edu.au:8088/jini/servlet/servlet.FileClassifierServlet
where the servlet engine is Tomcat, then the class file
needs to be copied to
$TOMCAT_HOME/webapps/jini/WEB-INF/classes/servlet/servlet/FileClassifierServlet.class
(There is an unfortunate repitition of the servlet
part here.
The first occurrence distinguishes between servlets and jsp's. The
second occurrence is part of the package name, and reflects the package
naming structure we have used in this book.)
If you found this chapter of value, the full book is available from APress or Amazon . There is a review of the book at Java Zone