(Originally published in JavaWorld, December 1998)
Summary
Sun has just released the specification for Servlet API 2.1. Regular
JavaWorld contributor and servlet aficionado Jason Hunter
explains the differences between 2.0 and 2.1, discusses the reasons
for the changes, and shows you how to write servlets using 2.1.
Plus, for the servlet developer on the run, Jason provides the
"2.0 to 2.1 Cheat Sheet," a quick-list of changes to Servlet
API 2.1. (3,800 words with sidebar)
By Jason Hunter
On November 6, Sun Microsystems released the specification for
Servlet API 2.1. (See Resources for a link to the formal spec.)
This is the first time the Servlet API has had an official specification,
and it's the first new release of the Servlet API since announcements
of version 2.0 were stuffed in our stockings back in December 1997.
This article describes what has changed from version 2.0, explains
why the changes were made, and demonstrates how to write servlets
using 2.1. To keep the article to a reasonable length, I'm going
to assume you're familiar with the classes and methods of Servlet
API 2.0. If that's not the case, you can peruse the Resources section for links to sites that will help get
you up to speed.
Comparing Servlet API 2.1 to 2.0, how does the new version differ
from the previous one?
- The API has been "cleaned up" to make method names more consistent
and predictable
- Servlets can now delegate request handling to other server components
- Servlets can now share information using their
ServletContext
- There's a new way to abstract servlet resources
- Servlets now have more control over session management
Before we begin our examination of these differences, let me point
out that version 2.1 has been released as a specification only.
No Web server yet supports 2.1. Even the reference servletrunner
implementation is still some weeks away, and commercial Web server
support may be even further away release-wise. But on the bright
side, servletrunner
has been entirely rewritten and,
when released, should be far more robust than its previous versions.
Cleaning house
One of the nicest features of Java is its consistency of naming
conventions and design. The Servlet API 2.1 includes a number of
small "housecleaning" API changes designed to maintain that consistency
-- both internally and with other Java APIs. The following is a
rundown of these small changes:
More consistent logging
To begin with, the method ServletException.log(Exception
e, String msg)
, used to log servlet events, has been deprecated
and replaced with log(String message, Throwable t)
.
This one deprecation fixes two problems. First, it moves the optional
Exception
parameter to the end of the argument list,
as is the custom in Java. Second, it allows the log()
method to take a Throwable
object, a more general type
of exception. This should make the API more predictable and robust.
In addition, the method log(String message, Throwable t)
has been added to the GenericServlet
class. This lets
you call log()
directly without first having to get
a handle to a ServletContext
. Previously, in 2.0, GenericServlet
only supported the one-argument log(String msg)
method.
Now it conveniently supports both log()
methods.
Removed redundancy
The ServletRequest.getRealPath(String path)
method,
used to determine the actual filesystem location for a given URL
path, has been deprecated in favor of a method by the same name
in ServletContext
. API 2.0 included both methods and
defined them to perform the same task. Redundancy, though good when
it comes to kidneys, isn't good in an API. Accordingly, getRealPath()
in ServletRequest
has been removed. Why wasn't the
method in ServletContext
deprecated instead? Because
path mapping rules depend on a servlet's context, not on any individual
client request.
More consistent URLs
So, which way do you abbreviate Uniform Resource Locator, Url
or URL? The Java API tends to choose the uppercase presentation,
as in: java.net.URL. The Servlet API, well, it seems it couldn't
decide. In 2.0, some methods, like getRequestURL()
,
choose uppercase while others, like encodeUrl()
, choose
lowercase. In 2.1, URL is always uppercase. Every method containing
the lowercase presentation has been deprecated and replaced. The
affected methods are HttpServletRequest.isRequestedSessionIdFromUrl()
,
HttpServletResponse.encodeUrl()
, and HttpServletResponse.encodeRedirectUrl()
.
Easier initialization
One of the things you have to remember when writing servlets with
version 2.0 of the API is that anytime you override init(ServletConfig
config)
you must first call super.init(config)
.
It's annoying, but it has to be done in order to give the GenericServlet
superclass a chance to save a reference to the config
and perform other preparatory work.
Version 2.1 removes the need for this irritating task. You can
now override a no-argument init()
method and avoid
the mandatory super.init(config)
call. You'll never
even miss the ServletConfig
parameter because GenericServlet
itself implements the ServletConfig
interface: a servlet
can call ServletConfig
methods directly on itself.
This change lets you write an init()
method as simple
as this:
public void init() throws ServletException {
String greeting = getInitParameter("greeting"); // a
ServletConfig method
}
Behind the scenes, the GenericServlet
class supports
the
no-arg init()
method with code similar to this:
public class GenericServlet implements Servlet, ServletConfig {
ServletConfig _config = null;
public void init(ServletConfig config) throws ServletException
{
_config = config;
log("init called");
init();
}
public void init() throws ServletException { }
public String getInitParameter(String name) {
return _config.getInitParameter(name);
}
// etc...
}
Notice the Web server still calls a servlet's init(ServletConfig
config)
method at initialization time. The change in 2.1
is that GenericServlet
now passes on this call to the
no-arg init()
, which you can override without worrying
about the config
.
If backward compatibility is a concern, you should continue to
override init(ServletConfig config)
and call super.init(config)
.
Otherwise you may end up wondering why your no-arg init()
method is never called.
A simpler way to get a session
Version 2.1 also adds a no-arg version of getSession()
to the HttpServletRequest
class. This is a convenience
method that has the same behavior as getSession(true)
,
the call used most often when getting a user's session. Using it
saves you the four keystrokes t-r-u-e.
More consistent setting of status codes
Another change is that the method HttpServletResponse.setStatus(int
sc, String sm)
has been deprecated in favor of setStatus(int
sc)
and sendError(int sc, String msg)
. This
modification was necessary in order for the API to conform to the
desirably simple rule that setStatus()
sets the response's
status code and nothing more, while sendError()
sets
the status code and, additionally, provides a server-generated explanation
of the error in the servlet's response. Following this rule, there
is no point in having a setStatus()
that accepts a
message.
More well-defined parameter receiving
Finally, something only a true servlet aficionado would notice!
Version 2.1 has clarified what happens when getParameter(String
name)
is called on a parameter with multiple values -- as
happens when two form fields have the same name. It must now return
the same thing as the first value in the array returned by getParameterValues(String
name)
. Previously, the behavior was left unspecified, causing
some servers to return a comma-separated list of parameter values
(i.e., value1, value2, value3). In 2.1, you may not know for sure
which value you'll get, but you at least know it will be a legitimate
value, not some server-specific hybrid.
Limiting exposure
A few additional methods have been deprecated in 2.1, not because
of API clean-up, but because in 2.0 they exposed more of the underlying
Web server implementation than was necessary or proper. Here's a
rundown of methods deprecated in version 2.1:
No direct servlet references
The first of these is ServletContext.getServlet(Stringname)
,
called to get a reference to another servlet instance loaded in
the Web server. This method has been deprecated and defined to return
null
in 2.1 because direct access to another servlet
instance opens up too many opportunities for error. The ServletContext.getServletNames()
method has been deprecated as well. The reasoning goes that servlets
may be destroyed by the Web server at any time, so nothing but the
server should hold a direct reference to a servlet. Also, on a Web
server that supports load balancing where servlets are distributed
across a number of servers, it may be difficult even to return a
local servlet reference.
The getServlet()
method has always been known to be
dangerous, and Sun has officially recommended against using it,
but it survived until now because there wasn't a good alternative
for interservlet communication. The method gets the official boot
now because servlets have a new way to collaborate using a shared
ServletContext
, as we'll see later.
No more session forgery
Also deprecated in 2.1 is the HttpSession.getSessionContext()
method. In 2.0, this method returned an HttpSessionContext
object that could be used for perusing the current sessions (and
corresponding session IDs) managed by the server. This method wasn't
useful for much except debugging -- but that's not why it's deprecated.
The reason is that session IDs must be carefully guarded. They're
kind of like social security numbers. Any unscrupulous individual
with access to another user's session ID can, with a forged cookie,
join the session of that user. Thus, in 2.1, getSessionContext()
now returns an empty HttpSessionContext
containing
no session information. The entire HttpSessionContext
class has been deprecated as well.
On to the enhancements
Enough with the removal of functionality! Let's take a look at what's
been added to the Servlet API 2.1. First, the little things:
What API is this, anyway?
The API adds two methods you can use to determine which Servlet
API version your Web server supports. These methods are ServletContext.getMajorVersion()
and ServletContext.getMinorVersion()
. For API 2.1,
getMajorVersion()
returns 2, and getMinorVersion()
returns 1.
Nested exceptions
ServletException
, the exception thrown by init()
,
doGet()
, and doPost()
to indicate a servlet
error, has been enhanced to support a "root cause" exception. This
lets ServletException
act as a "wrapper" around any
type of exception, giving the Web server a way to know what "root"
exception caused the ServletException
to be thrown.
To support this ability, ServletException
has two new
constructors: ServletException(Throwable rootCause)
and ServletException(String message, ThrowablerootCause)
.
The following code snippets demonstrate how you can use a nested
exception. First, here's servlet code that catches an InterruptedException
and throws a ServletException
, according to the 2.0
API. Notice that the server catching the ServletException
won't be able to examine the underlying InterruptedException
or view its stack trace.
try {
thread.sleep(60000);
}
catch (InterruptedException e) {
throw new ServletException(e.getMessage()); //
no type or stack trace
}
In 2.1 you can pass on the underlying exception:
try {
thread.sleep(60000);
}
catch (InterruptedException e) {
throw new ServletException(e); // includes
full underlying exception
}
The server can retrieve the underlying exception by calling e.getRootCause()
.
The call returns null
if there is no nested exception.
My personal advice: Ignore this new feature and deal with exceptions
yourself. You'll have to write a little extra code, but by handling
the exception yourself you can guarantee consistent and proper error
handling.
How to let someone else do the work
A more exciting addition to 2.1 is the ability for servlets to
programmatically delegate request handling to other components on
the server. This serves two purposes. First, a servlet can forward
an entire request, doing some preliminary processing and then passing
off the request to another component. This ability may remind you
of the "NameTrans" feature in Netscape's NSAPI. Second, a servlet
can include in its response a bit of content generated by another
component, essentially creating a programatic server-side include.
This delegation ability gives servlets more flexibility, and allows
for better abstraction. Using delegation, a servlet can construct
its response as a collection of content generated by various Web
server components, all located using the Web server's URI
(a fancy word for URL) namespace. This functionality is especially
important to JavaServer Pages, where it often happens that one servlet
preprocesses a request, then hands off the request to a .jsp
page for completion.
To support request delegation, 2.1 includes a new interface called
RequestDispatcher
. A servlet gets a RequestDispatcher
using the getRequestDispatcher(String uripath)
method
of its ServletContext
. This method returns a RequestDispatcher
that can dispatch to the component found at the given URL.
RequestDispatcher
has two methods, forward(ServletRequest
req, ServletResponse res)
and include(ServletRequest
req, ServletResponse res)
. The forward()
method
hands off the entire request to the delegate. To ensure the delegate
has complete control over the response, forward()
must
be called before the first servlet gets the ServletOutputStream
or PrintWriter
for the response. The include()
method adds the delegate's output to the calling servlet's response,
but leaves the calling servlet in control. This method may be called
at any time. The delegate, however, because it may be used anywhere
in a page, has no ability to change the status code or HTTP headers
sent in the response.
The following code shows how a servlet can include content from
another component.
// Show an item in an online catalog
out.println("Feast your eyes on this beauty:");
RequestDispatcher dispatcher = getServletContext()
.getRequestDispatcher("/servlet/CatalogDisplay?item=156592391X");
dispatcher.include(req, res);
out.println("And, since I like you, it's 20% off!");
A servlet can pass information to the delegate using a query string
as shown above, or it can put "attributes" in the request using
ServletRequest
's new setAttribute(String name,
Object object)
method. For example,
// Show an item in an online catalog
RequestDispatcher dispatcher = getServletContext()
.getRequestDispatcher("/servlet/CatalogDisplay");
out.println("Feast your eyes on this beauty:");
req.setAttribute("item", new Book("156592391X"));
dispatcher.include(req, res);
out.println("Or how about this one:");
req.setAttribute("item", new Book("0395282659"));
dispatcher.include(req, res);
out.println("And, since I like you, it's 20% off!");
CatalogDisplay
can receive the "item" request attribute
by calling req.getAttribute("item")
, or it can receive
the names of all the request attributes using req.getAttributeNames()
.
Using attributes instead of parameters gives you the ability to
pass objects instead of simple strings.
Shared attributes
To further help servlets cooperate on tasks, the Servlet API 2.1
includes a new method for servlets to share information. Previously,
it was possible for servlets to share information, but they had
to use homegrown mechanisms like a Singleton object or shared files.
In 2.1, servlets have been given the ability to share information
by setting and getting attributes in their ServletContext
.
Several methods have been added to ServletContext
to support the setting and getting of attributes. There's setAttribute(String
name, Object object)
, which sets an attribute. Another method
is Object getAttribute(String name)
, which gets an
attribute, and a third is EnumerationgetAttributeNames()
,
which gets a list of attribute names. (Actually, the getAttribute()
method has existed since Servlet API 1.0, but until now it could
only read attributes hard coded into the server.) Finally, there's
removeAttribute(String name)
, which removes an attribute.
Using these methods, a servlet can easily share information with
any other servlets that live in its ServletContext
.
This functionality helps tremendously when load balancing, because
servlets may find themselves spread across a number of back-end
servers, making it especially challenging for servlets to use a
homegrown mechanism to share information.
How servlets are divided into ServletContext
groups
depends on the server configuration. Historically, Web servers have
put all servlets in the same context. However, now that each context
has a "shared state," it's likely servers will begin to segment
servlets into individual contexts, with each context thought of
as a single "Web application."
To support communication between contexts, there's a new method,
ServletContext.getContext(String uripath)
. This method
returns the ServletContext
for the given URI, subject
to security constraints. You can use this method to share information
with server components outside your context.
Resource abstraction
Another new feature in Servlet API 2.1 is resource abstraction,
which allows servlets to access a resource without knowing where
the resource resides. This abstraction makes servlets into mobile
objects that can be moved between servers -- a useful ability when
load balancing.
In 2.1, all resources are abstracted into URLs on the server.
Getting an abstract resource
A servlet gains access to an abstract resource using ServletContext.getResource(String
uripath)
. This method returns a URL
that can
be used to investigate the specified resource and read its content.
How the URI path parameter maps to an actual resource (file, database
entry, or other) is determined by the Web server. The one restriction
is that the URI should not be an active resource (servlet, CGI program,
and so on). For active resources you should use a RequestDispatcher
.
To demonstrate how to read from an abstract resource, the following
code fetches and prints the server's /includes/header.html
file:
URL url = getServletContext().getResource("/includes/header.html");
out.println(url.getContent());
The header.html file should be found somewhere under the server's
document root. It may exist on a server machine other than the one
hosting the servlet, but conveniently that doesn't matter.
Using the returned URL object you can investigate the attributes
of the abstract resource. Here's code that examines the Web server's
front page.
URL url = getServletContext().getResource("/"); // front
page
URLConnection con = url.openConnection();
con.connect();
int contentLength = con.getContentLength();
String contentType = con.getContentType();
long expiration = con.getExpiration();
long lastModified = con.getLastModified();
// etc...
Remember, the content served for the /
URI path is
entirely determined by the server.
Getting an abstract resource as a stream
The 2.1 API also includes a convenient method, called getResourceAsStream(String
uripath)
, for reading resources as a stream. This method
returns an InputStream
, which lets you type
InputStream in = getServletContext().getResourceAsStream("/")
instead of
URL url = getServletContext().getResource("/");
InputStream in = url.openStream();
The method doesn't save much typing, but perhaps it can help people
who aren't familiar with URL objects.
Writing to an abstract resource
Here's a useful tip: You can also use getResource()
to write output, for those resources that permit it. The trick is
to get the URL
's corresponding URLConnection
and write to the connection's OutputStream
. For example:
URL url = getServletContext().getResource("/custom.log");
URLConnection con = url.openConnection();
con.setDoOutput(true);
OutputStream out = con.getOutputStream();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
pw.println("That wasn't so hard.");
pw.close();
out.close();
Backward compatibility for resources
For backward compatibility and to ease the transition to servlets
for CGI programmers, 2.1 continues to include methods for file access,
such as getPathTranslated()
. Just remember that anytime
you access a resource using a File
object you're tying
yourself to a particular machine.
Sessions
Servlet developers have been given more control over HttpSession
lifecycles in 2.1. Previously, the interval before a session timed
out had to be set using administration tools. You can now set the
duration of a session programmatically by calling HttpSession.setMaxInactiveInterval(int
interval)
. This method takes an int
representing
the number of seconds of inactivity that need to occur before the
session is timed out and invalidated. A negative value indicates
the session should never expire. The current interval can be retrieved
using getMaxInactiveInterval()
.
Future directions: deployment descriptors
Not included in the 2.1 spec, but coming soon, are servlet deployment
descriptors. These descriptors, modeled after a similar concept
in Enterprise JavaBeans, are a way to simplify the installation
of servlets and Web content into a Web server.
Deployment descriptors are expected to allow servlets, support
classes, and even content to be packaged in a jar along with a complete
declaration of their server configuration requirements. For example,
the jar can report where the content should reside, which of the
contained classes should be loaded as servlets, what names the servlets
should be registered under, what default init
parameters
the servlets should have, and what ServletContext
the
servlets should live in. Essentially, with deployment descriptors
you'll be able to install an entire "Web application" using just
one jar.
Conclusion
The 2.1 API includes a number of changes that make servlet programming
easier and more consistent. In exact numbers: version 2.1 offers
1 new class, 24 new methods, 1 deprecated class, and 9 deprecated
methods. It's definitely not a large revision, and the core functionality
remains the same, but by moving to 2.1 you'll be able to take advantage
of consistent APIs and new abilities for request delegation, information
sharing, resource abstraction, and session management.
About the author
Jason Hunter works as the chief technology officer of K&A Software, where he specializes in
Java training and consulting. He is author of the book Java
Servlet Programming (O'Reilly) and publisher of the Web site
http://www.servlets.com/. He belongs to the
working group responsible for Servlet API development (and has his
fingerprints all over the 2.1 specification). If by some miracle
you don't find him at work, he's probably out hiking in the mountains.
To be notified when new articles are added to the site, subscribe here.