Find out how to easily replace the RMI and sockets networking layers with servlets
Play with the whiteboard!
Note: This applet will run only in the HotJava browser, the Windows 95/NT implementation of Internet Explorer 4.0, or in an AWT 1.1-enabled version of Netscape (see Resources for a link to the patch required to bring Netscape up to speed).
If you are not using any of these browsers, you can use appletviewer to view the applet.
The whiteboard we’re using was first developed back inNovember’s column. The whiteboard uses 1.1 AWT features, such as lightweight components to display objects as the user draws them.
In December’s column we added to the functionality of the previous month by creating a network of whiteboards that shared a common list of objects. All the whiteboards in the collaborative group could add and move the shared whiteboard objects. We developed both RMI- and sockets-based networking layers to enable communication among the whiteboards.
This month we’re going one step further: We’re going to use servlets to provide the same function that sockets and RMI provided last month.
Servlets are server extensions written in Java, usually for Web servers. A growing number of Web servers support them, including Apache, Java Web Server (JWS), O’Reilly’s WebSite, and Netscape’s various offerings.
Servlets are interesting because they don’t fork a new process for every request that comes in, which makes them much faster than CGI. Under some Web servers, such as JWS, servlets are even faster than Fast-CGI because there is no process task switch; the servlets run as threads within the server process itself.
Because Web-based servlets respond to HTTP methods such as GET
and POST
, servlet-based communication is able to get around firewalls, which block sockets and RMI. As an added bonus, it’s easy to use SSL to secure communications between SSL-enabled clients (Netscape, for example) and servlets running under an SSL-enabled Web server. SSL (or Secure Sockets Layer) is an encryption-based protocol used to protect data sent over the Internet from eavesdropping.
Servlet structure and life cycle
At a high level, servlets are just like applets, with the exception that they run in the server environment instead of the browser environment. Like applets, they have a definite life cycle, which is controlled by the environment. Unlike applets, however, only one instance of a servlet is actually created for each Web server. Each request to the servlet’s URL is passed into the same instance of the servlet.
Servlets are accessed from clients in the same manner as CGI scripts. For example, an HTTP GET
request to a URL like could return a bit of HTML containing the current time on the server.
The server loads the servlet when the first request is directed to it (unless the server allows pre-loading). The server then calls the servlet’s init ()
method. All other requests arriving before the init ()
method completes block until it does so.
Once the init ()
method completes, the servlet is ready to service requests via its service ()
method. The environment puts each request into its own thread, which then enters the servlet’s service ()
method.
The servlet continues servicing requests until it is unloaded. When the environment unloads the servlet (when the server shuts down, for example), it calls the servlet’s destroy ()
method.
Brief overview of the servlet API
The servlet API is provided in the
javax.servlet
and
javax.servlet.http
packages.
javax.servlet
is the base servlet API, and contains the interface Servlet
and its generic implementation, GenericServlet
. This package also contains ServletRequest
and ServletResponse
interfaces.
The most important method of GenericServlet
is service ()
, which is where request threads enter. Specializations of GenericServlet
such as HttpServlet
override this method to handle protocol-specific requests.
javax.servlet.http
specializes these classes and implements interfaces to handle HTTP requests. We’ll look at the HTTP servlet classes in more depth in this application.
A servlet for the whiteboard
The whiteboard, as you will remember from last time, used a distributed data structure called
ObservableList
to keep track of elements that it displayed. Distributed implementations using sockets and RMI simply subclassed
ObservableList
and overrode method implementations based on which type of networking was used. In both cases, a server provided a central
IDList
to keep track of elements shared among the clients.
This time, we’re going to do much the same thing, except we’ll extend ObservableList
to provide a servlet-based implementation called ServletList
. The servlet implementation will share methods employed by both networking models we used last month: the client will poll the server periodically to ask for updates as it did in the RMI approach, and it will pass specialized “message objects” to the server using object streams as in the sockets approach.
The client object streams will use the URL
and URLConnection
classes to send message objects using the HTTP POST
protocol, which allows a Web server extension to send binary data with a POST
request. The response to the POST
will be binary data as well, which an object stream will decode into a response object.
Class ServletList
extends
ObservableList
and uses a few of the message wrappers from the previous socket-based implementation to communicate with the server. One new message type,
, is necessary so that the client can alert the server that it needs an update. The server responds with an
InitMsg
containing the update. Let’s take a look.
package shoffner.step.jan;
import java.io.*;
import java.util.*;
import java.net.*;
import org.merlin.step.dec.*;
import org.merlin.step.dec.socket.*;
public class ServletList extends ObservableList implements Runnable {
public static final int UPDATE_DELAY = 10 * 1000;
URL server;
IDList list;
Thread processor;
public ServletList (String host, String loc) {
try {
server = new URL (" + host + "/" + loc);
} catch (MalformedURLException ex) {
ex.printStackTrace ();
}
list = new IDList ();
update ();
processor = new Thread (this);
processor.start ();
}
We import classes from the dec
and dec.socket
packages because we are going to use some of the data structures and socket message classes from last month. The constructor sets up a URL
to the host and a location supplied by the class that instantiates the ServletList
.
public Enumeration elements () {
return list.elements ();
}
public void addElement (Object element) {
Object id = queryServer (new AddElementMsg (element));
list.addElementWithID (id, element);
}
public void replaceElementAtEnd (Object oldE, Object newE) {
Object oldID = list.getIDOfElement (oldE);
Object id = queryServer (new ReplaceElementMsg (oldID, newE));
if (id != null)
list.replaceElementWithID (oldID, id, newE);
else
System.out.println ("Unknown id:" + oldID);
}
These three methods (elements ()
, addElement ()
, and replaceElementAtEnd ()
) are part of the public interface to the ServletList
. They override methods found in ObservableList
. The central feature of these methods is the call to queryServer ()
, which returns an Object
result from the server to the calling method. Notice that we’re wrapping the requests in objects to denote their type to the server.
public void run () {
while (true) {
try {
Thread.sleep (UPDATE_DELAY);
update ();
}
catch (Exception ignored) {
ignored.printStackTrace ();
}
}
}
public void stop () {
if (processor != null) {
processor.stop ();
processor = null;
}
}
void update () {
InitMsg i = (InitMsg) queryServer (new UpdateMsg (list.getUpdateCount ()));
IDList initList = i.getList ();
if (initList != null) {
list = initList;
fireUpdate ();
}
}
Here we see the update thread that periodically polls the server. If the server has a different updateCount
from that maintained by list
, the server sends back its own IDList
. We replace our local copy with IDList
and call fireUpdate ()
. Obviously, we could use a more sophisticated update mechanism here, but this approach is easy and adequate for our needs.
The run ()
method must not be allowed to exit while the client is active, or the client will stop polling for updates. Therefore, run ()
sits in a while
loop and catches exceptions that may be generated if the server goes down while the client is attempting an update.
Now we get to the interesting part of the client implementation:
Object queryServer (Object arg) {
URLConnection con;
ObjectOutputStream req = null;
ObjectInputStream res = null;
Object result = null;
try {
con = server.openConnection ();
con.setDoOutput (true);
req = new ObjectOutputStream (new BufferedOutputStream
(con.getOutputStream ()));
req.writeObject (arg);
req.flush ();
req.close ();
res = new ObjectInputStream (new BufferedInputStream
(con.getInputStream ()));
result = res.readObject ();
} catch (IOException ignored) {
ignored.printStackTrace ();
} catch (ClassNotFoundException ex) {
ex.printStackTrace ();
} finally {
try {
res.close ();
} catch (IOException ex) {}
}
return result;
}
}
The queryServer ()
method sends an object, arg
, to the servlet using the HTTP POST
method to access the servlet’s URL. Before the object can be sent, it must be serialized using ObjectOutputStream
. The server’s response will be read in from an ObjectInputStream
and returned by the method.
Interestingly, the queryServer ()
method does not require a servlet at the other end. Any Web server extension/CGI that can understand POST
, and can accept and return a serialized Java object will work fine. Alternatively, we could override the queryServer ()
method to make it access other types of message-oriented servers besides those dealing in serialized Java objects.
Class ServletListServlet
It’s servlet time!
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import org.merlin.step.dec.IDList;
import org.merlin.step.dec.socket.*;
import shoffner.step.jan.*;
public class ServletListServlet extends HttpServlet {
IDList list;
public void init (ServletConfig c) throws ServletException {
super.init (c);
list = new IDList ();
log ("ServletListServlet initialized.");
System.out.println ("ServletListServlet initialized.");
}
We first import all the necessary classes and declare that our servlet extends HttpServlet
so that it can make use of the specialized doPost ()
method. The next step is to override the default init ()
method. The log ()
statement writes our startup message to a Web server log file, and the println ()
method prints it to the terminal from which the Web server was started (assuming the terminal is available).
We could also override the generic destroy ()
method to make sure that any long-running connections finish before the servlet is unloaded. This approach is a good idea if the application is sensitive to connection death, as would be the case during a long-running data transfer, for example. Instead, we will leave it to the client to handle recovery if the servlet is unloaded. (The Web server could potentially die unexpectedly anyway, but that’s rare.)
public void doPost (HttpServletRequest httpReq, HttpServletResponse httpRes)
throws ServletException, IOException {
// handle request
ServletInputStream sis = httpReq.getInputStream ();
BufferedInputStream bis = new BufferedInputStream (sis);
ObjectInputStream ois = new ObjectInputStream (bis);
DListMsg msg = null;
Object response = null;
try {
msg = (DListMsg) ois.readObject ();
} catch (ClassNotFoundException ex) {
ex.printStackTrace ();
}
if (msg instanceof AddElementMsg) {
Object element = ((AddElementMsg) msg).getElement ();
list.addElement (element);
response = list.getIDOfElement (element);
} else if (msg instanceof ReplaceElementMsg) {
Object oldID = ((ReplaceElementMsg) msg).getID ();
Object element = ((ReplaceElementMsg) msg).getElement ();
response = list.replaceElement (oldID, element);
} else if (msg instanceof UpdateMsg) {
if (list.getUpdateCount () == ((UpdateMsg) msg).getUpdateCount ())
response = new InitMsg (null);
else
response = new InitMsg ((IDList) list.clone ());
} else {
System.out.println ("Unknown message:" + msg);
}
System.out.println (msg);
doPost ()
does the real work for the servlet. Its function is to accept an incoming POST
and generate a response. The request and response are passed into the method by the environment in the form of HttpServletRequest
and HttpServletResponse
, respectively, when the environment calls doPost ()
.
Once the msg
object is retrieved from the input stream associated with HttpServletRequest
, the message logic takes over and acts based on the type of the message.
Because many request threads may be executing within the service methods of a servlet, synchronization issues are quite important. The IDList
methods are already synchronized, so we don’t have to worry about anything here.
// send response
httpRes.setStatus (HttpServletResponse.SC_OK);
ServletOutputStream sos = httpRes.getOutputStream ();
BufferedOutputStream bos = new BufferedOutputStream (sos);
ObjectOutputStream oos = new ObjectOutputStream (bos);
oos.writeObject (response);
oos.flush ();
}
}
Once the message logic is through, it’s time to send a response. The first thing we do is set the status of the HttpServletResponse
to SC_OK
. Next, an ObjectOutputStream
is created to write response
as the body of the server’s response to the POST
, and the ObjectOutputStream
is flushed. Note that we do not close the streams; the environment does this automatically when the method exits.
And that’s all there is to it!
Setting up servlets
The first step to developing with servlets is to get the servlet development kit (JSDK) from JavaSoft and install it (instructions are included with the package).
To enable Apache for servlets, get the latest version of the server and install the servlet module. JavaSoft provides mod_servlet.c
with the JSDK, but for use with Apache 1.2.x you’ll have to patch it. The patch is available from Apache Week, a weekly guide for Apache users. There is also an alternate servlet module under development by the Apache group.
The Resources section includes links to all the items I’ve mentioned.
After you have installed the servlet module on your Web server, you need to configure it. Instructions for this are included with the installation instructions. Configuration will include the servlet directory’s alias and its physical location on the file system. This is strictly analogous to CGI setup.
Using the code
The complete source code for this article is located in the
section. After you compile the code (instructions for this in a moment), you must place the
ServletListServlet
class file in the servlet “bin” directory that you set up during server configuration. You can put the client HTML anywhere you wish in your Web tree.
Make sure to change the second argument in ServletWB.java
‘s "content = ..."
line to configure the ServletList
to your servlet bin alias before you compile. To compile follow these steps:
- Download the complete source, and unzip (or untar, depending on which configuration you’re using) the file.
- Change to the src directory.
-
Compile using the following command:
javac ServletListServlet.java orgmerlinstepdec*.java orgmerlinstepdecsocket*.java shoffnerstepjan*.java
(If you’re using Unix, be sure to use the forward slash and not the backslash in this command sequence.)
Because the servlet and the client will reside in different locations, it is necessary to make sure that each has a copy of the org
and shoffner
hierarchies. In the case of the client, the hierarchies belong in the same directory with the HTML file that contains the applet tag for ServletWBLauncher
. In the case of the servlet, the hierarchies belong in the servlet “bin” directory that you have defined in your servlet module configuration.
Also, the client must be served from the same Web server that provides the servlet or you’ll get a SecurityException
.
Conclusion
Servlets are great because they allow programmers to use Java to extend Web servers. This adds to the toolkit of any Java developer who desires to take advantage of HTTP to implement message-passing between clients and servers. Clients don’t have to be Java applications; in fact, servlets can do anything CGI scripts can do, including returning HTML to Web browsers.
In the case of the whiteboard, servlets are a useful alternative to RMI and sockets for a number of reasons, especially when the client is an applet. Because applet security policy requires an applet to connect back to the Web server, a servlet can be used in place of a standalone server process that needs extra setup. Servlets also work through firewalls.
Next month, to prolong your holiday hangover, we’ll network the whiteboard again…with CORBA!