Servlets
Servlets
Client access from the Internet or corporate intranets
is a sure way to allow many users to access data and resources easily. This type
of access is based on clients using the World Wide Web standards of Hypertext
Markup Language (HTML) and Hypertext Transfer Protocol (HTTP). The Servlet API
set abstracts a common solution framework for responding to HTTP requests.
Traditionally, the way to handle a problem such as
allowing an Internet client to update a database is to create an HTML page with
text fields and a “submit” button. The user types the appropriate
information into the text fields and presses the “submit” button.
The data is submitted along with a URL that tells the server what to do with the
data by specifying the location of a Common Gateway Interface (CGI) program that
the server runs, providing the program with the data as it is invoked. The CGI
program is typically written in Perl, Python, C, C++, or any language that can
read from standard input and write to standard output. That’s all that is
provided by the Web server: the CGI program is invoked, and standard streams
(or, optionally for input, an environment variable) are used for input and
output. The CGI program is responsible for everything else. First it looks at
the data and decides whether the format is correct. If not, the CGI program must
produce HTML to describe the problem; this page is handed to the Web server (via
standard output from the CGI program), which sends it back to the user. The user
must usually back up a page and try again. If the data is correct, the CGI
program processes the data in an appropriate way, perhaps adding it to a
database. It must then produce an appropriate HTML page for the Web server to
return to the user.
It would be ideal to go to a completely Java-based
solution to this problem—an applet on the client side to validate and send
the data, and a servlet on the server side to receive and process the data.
Unfortunately, although applets are a proven technology with plenty of support,
they have been problematic to use on the Web because you cannot rely on a
particular version of Java being available on a client’s Web browser; in
fact, you can’t rely on a Web browser supporting Java at all! In an
intranet, you can require that certain support be available, which allows a lot
more flexibility in what you can do, but on the Web the safest approach is to
handle all the processing on the server side and deliver plain HTML to the
client. That way, no client will be denied the use of your site because they do
not have the proper software installed.
Because servlets provide an excellent solution for
server-side programming support, they are one of the most popular reasons for
moving to Java. Not only do they provide a framework that replaces CGI
programming (and eliminates a number of thorny CGI problems), but all your code
has the platform portability gained from using Java, and you have access to all
the Java APIs (except, of course, the ones that produce GUIs, like Swing).
The basic servlet
The architecture of the servlet API is that of a
classic service provider with a service( ) method through which all
client requests will be sent by the servlet container software, and life cycle
methods init( ) and destroy( ), which are called only
when the servlet is loaded and unloaded (this happens rarely).
public
interface
Servlet {
public
void
init(ServletConfig config)
throws
ServletException;
public
ServletConfig getServletConfig();
public
void
service(ServletRequest req,
ServletResponse res)
throws
ServletException, IOException;
public
String getServletInfo();
public
void
destroy();
}
getServletConfig( )’s sole purpose
is to return a ServletConfig object that contains initialization and
startup parameters for this servlet. getServletInfo( ) returns a
string containing information about the servlet, such as author, version, and
copyright.
The GenericServlet class is a shell
implementation of this interface and is typically not used. The
HttpServlet class is an extension of GenericServlet and is
designed specifically to handle the HTTP protocol— HttpServlet is
the one that you’ll use most of the time.
The most convenient attribute of the servlet API is
the auxiliary objects that come along with the HttpServlet class to support it.
If you look at the service( ) method in the Servlet
interface, you’ll see it has two parameters: ServletRequest and
ServletResponse. With the HttpServlet class these two object are
extended for HTTP: HttpServletRequest and HttpServletResponse.
Here’s a simple example that shows the use of
HttpServletResponse:
//:
c15:servlets:ServletsRule.java
import
javax.servlet.*;
import
javax.servlet.http.*;
import
java.io.*;
public
class
ServletsRule
extends
HttpServlet {
int
i = 0; // Servlet
"persistence"
public
void
service(HttpServletRequest req,
HttpServletResponse res)
throws
IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.print("<HEAD><TITLE>");
out.print("A server-side
strategy");
out.print("</TITLE></HEAD><BODY>");
out.print("<h1>Servlets Rule!
" + i++);
out.print("</h1></BODY>");
out.close();
}
}
///:~
ServletsRule is about as simple as a servlet
can get. The servlet is initialized only once by calling its init( )
method, on loading the servlet after the servlet container is first booted
up. When a client makes a request to a URL that happens to represent a servlet,
the servlet container intercepts this request and makes a call to the
service( ) method, after setting up the HttpServletRequest
and HttpServletResponse objects.
The main responsibility of the service( )
method is to interact with the HTTP request that the client has sent, and to
build an HTTP response based on the attributes contained within the request.
ServletsRule only manipulates the response object without looking at what
the client may have sent.
After setting the content type of the response (which
must always be done before the Writer or OutputStream is
procured), the getWriter( ) method of the response object produces a
PrintWriter object, which is used for writing character-based response
data (alternatively, getOutputStream( ) produces an
OutputStream, used for binary response, which is only utilized in more
specialized solutions).
The rest of the program simply sends HTML back to the
client (it’s assumed you understand HTML, so that part is not explained)
as a sequence of Strings. However, notice the inclusion of the “hit
counter” represented by the variable i. This is automatically
converted to a String in the print( )
statement.
When you run the program, you’ll notice that the
value of i is retained between requests to the servlet. This is an
essential property of servlets: since only one servlet of a particular class is
loaded into the container, and it is never unloaded (unless the servlet
container is terminated, which is something that only normally happens if you
reboot the server computer), any fields of that servlet class effectively become
persistent objects! This means that you can effortlessly maintain values between
servlet requests, whereas with CGI you had to write values to disk in order to
preserve them, which required a fair amount of fooling around to get it right,
and resulted in a non-cross-platform solution.
Of course, sometimes the Web server, and thus the
servlet container, must be rebooted as part of maintenance or during a power
failure. To avoid losing any persistent information, the servlet’s
init( ) and destroy( ) methods are automatically called
whenever the servlet is loaded or unloaded, giving you the opportunity to save
data during shutdown, and restore it after rebooting. The servlet container
calls the destroy( ) method as it is terminating itself, so you
always get an opportunity to save valuable data as long as the server machine is
configured in an intelligent way.
There’s one other issue when using
HttpServlet. This class provides doGet( ) and
doPost( ) methods that differentiate between a CGI “GET”
submission from the client, and a CGI “POST.” GET and POST vary only
in the details of the way that they submit the data, which is something that I
personally would prefer to ignore. However, most published information that
I’ve seen seems to favor the creation of separate doGet( ) and
doPost( ) methods instead of a single generic service( )
method, which handles both cases. This favoritism seems quite common, but
I’ve never seen it explained in a fashion that leads me to believe that
it’s anything more than inertia from CGI programmers who are used to
paying attention to whether a GET or POST is being used. So in the spirit of
“doing the simplest thing that could possibly work,”
I will just use the service( ) method in these
examples, and let it care about GETs vs. POSTs. However, keep in mind that I
might have missed something and so there may in fact be a good reason to use
doGet( ) and doPost( ) instead.
Whenever a form is submitted to a servlet, the
HttpServletRequest comes preloaded with all the form data, stored as
key-value pairs. If you know the names of the fields, you can just use them
directly with the getParameter( ) method to look up the values. You
can also get an Enumeration (the old form of the Iterator) to the
field names, as is shown in the following example. This example also
demonstrates how a single servlet can be used to produce the page that contains
the form, and to respond to the page (a better solution will be seen later, with
JSPs). If the Enumeration is empty, there are no fields; this means no
form was submitted. In this case, the form is produced, and the submit button
will re-call the same servlet. If fields do exist, however, they are
displayed.
//:
c15:servlets:EchoForm.java
//
Dumps the name-value pairs of any HTML form
import
javax.servlet.*;
import
javax.servlet.http.*;
import
java.io.*;
import
java.util.*;
public
class
EchoForm
extends
HttpServlet {
public
void
service(HttpServletRequest req,
HttpServletResponse res)
throws
IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
Enumeration flds = req.getParameterNames();
if(!flds.hasMoreElements())
{
// No form submitted -- create
one:
out.print("<html>");
out.print("<form
method=\"POST\""
+
"
action=\"EchoForm\">");
for(int
i = 0; i < 10; i++)
out.print("<b>Field"
+ i + "</b>
" +
"<input
type=\"text\""+
"
size=\"20\"
name=\"Field" + i +
"\"
value=\"Value"
+ i +
"\"><br>");
out.print("<INPUT TYPE=submit
name=submit"+
"
Value=\"Submit\"></form></html>");
}
else
{
out.print("<h1>Your form
contained:</h1>");
while(flds.hasMoreElements())
{
String field= (String)flds.nextElement();
String value= req.getParameter(field);
out.print(field + " =
" + value+
"<br>");
}
}
out.close();
}
}
///:~
One drawback you’ll notice here is that Java
does not seem to be designed with string processing in mind—the formatting
of the return page is painful because of line breaks, escaping quote marks, and
the “+” signs necessary to build String objects. With a
larger HTML page it becomes unreasonable to code it directly into Java. One
solution is to keep the page as a separate text file, then open it and hand it
to the Web server. If you have to perform any kind of substitution to the
contents of the page, it’s not much better since Java has treated string
processing so poorly. In these cases you’re probably better off using a
more appropriate solution (Python would be my choice; there’s a version
that embeds itself in Java called JPython) to generate the response page.
Servlets and multithreading
The servlet container has a pool of threads that it
will dispatch to handle client requests. It is quite likely that two clients
arriving at the same time could be processing through your
service( ) at the same time. Therefore the service( )
method must written in a thread-safe manner. Any access to common resources
(files, databases) will need to be guarded by using the synchronized
keyword.
The following simple example puts a synchronized
clause around the thread’s sleep( ) method. This will
block all other threads until the allotted time (five seconds) is all used up.
When testing this you should start several browser instances and hit this
servlet as quickly as possible in each one—you’ll see that each one
has to wait until its turn comes up.
//:
c15:servlets:ThreadServlet.java
import
javax.servlet.*;
import
javax.servlet.http.*;
import
java.io.*;
public
class
ThreadServlet
extends
HttpServlet {
int
i;
public
void
service(HttpServletRequest req,
HttpServletResponse res)
throws
IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
synchronized(this)
{
try
{
Thread.currentThread().sleep(5000);
}
catch(InterruptedException
e) {
System.err.println("Interrupted");
}
}
out.print("<h1>Finished
" + i++ +
"</h1>");
out.close();
}
}
///:~
It is also possible to synchronize the entire servlet
by putting the synchronized keyword in front of the
service( ) method. In fact, the only reason to use the
synchronized clause instead is if the critical section is in an execution
path that might not get executed. In that case, you might as well avoid the
overhead of synchronizing every time by using a synchronized clause.
Otherwise, all the threads will have to wait anyway so you might as well
synchronize the whole method.
Handling sessions with servlets
HTTP is a “sessionless” protocol, so you
cannot tell from one server hit to another if you’ve got the same person
repeatedly querying your site, or if it is a completely different person. A
great deal of effort has gone into mechanisms that will allow Web developers to
track sessions. Companies could not do e-commerce without keeping track of a
client and the items they have put into their shopping cart, for example.
There are several methods of session tracking, but the
most common method is with persistent “cookies,” which are an
integral part of the Internet standards. The HTTP Working Group of the Internet
Engineering Task Force has written cookies into the official standard in RFC
2109 (ds.internic.net/rfc/rfc2109.txt or check
www.cookiecentral.com).
A cookie is nothing more than a small piece of
information sent by a Web server to a browser. The browser stores the cookie on
the local disk, and whenever another call is made to the URL that the cookie is
associated with, the cookie is quietly sent along with the call, thus providing
the desired information back to that server (generally, providing some way that
the server can be told that it’s you calling). Clients can, however, turn
off the browser’s ability to accept cookies. If your site must track a
client who has turned off cookies, then another method of session tracking (URL
rewriting or hidden form fields) must be incorporated by hand, since the session
tracking capabilities built into the servlet API are designed around cookies.
The Cookie class
The servlet API (version 2.0 and up) provides the
Cookie class. This class incorporates all the HTTP header details and
allows the setting of various cookie attributes. Using the cookie is simply a
matter of adding it to the response object. The constructor takes a cookie name
as the first argument and a value as the second. Cookies are added to the
response object before you send any content.
Cookie
oreo =
new
Cookie("TIJava",
"2000");
res.addCookie(oreocookie);
Cookies are recovered by calling the
getCookies( ) method of the HttpServletRequest object, which
returns an array of cookie objects.
Cookie[]
cookies = req.getCookies();
You can then call getValue( ) for each
cookie, to produce a String containing the cookie contents. In the above
example, getValue("TIJava") will produce a String containing
“2000.”
The Session class
A session is one or more page requests by a client to
a Web site during a defined period of time. If you buy groceries online, for
example, you want a session to be confined to the period from when you first add
an item to “my shopping cart” to the point where you check out. Each
item you add to the shopping cart will result in a new HTTP connection, which
has no knowledge of previous connections or items in the shopping cart. To
compensate for this lack of information, the mechanics supplied by the cookie
specification allow your servlet to perform session tracking.
A servlet Session object lives on the server
side of the communication channel; its goal is to capture useful data about this
client as the client moves through and interacts with your Web site. This data
may be pertinent for the present session, such as items in the shopping cart, or
it may be data such as authentication information that was entered when the
client first entered your Web site, and which should not have to be reentered
during a particular set of transactions.
The Session class of the servlet API uses the
Cookie class to do its work. However, all the Session object needs
is some kind of unique identifier stored on the client and passed to the server.
Web sites may also use the other types of session tracking but these mechanisms
will be more difficult to implement as they are not encapsulated into the
servlet API (that is, you must write them by hand to deal with the situation
when the client has disabled cookies).
Here’s an example that implements session
tracking with the servlet API:
//:
c15:servlets:SessionPeek.java
//
Using the HttpSession class.
import
java.io.*;
import
java.util.*;
import
javax.servlet.*;
import
javax.servlet.http.*;
public
class
SessionPeek
extends
HttpServlet {
public
void
service(HttpServletRequest req,
HttpServletResponse res)
throws
ServletException, IOException {
// Retrieve Session Object before
any
// output is sent to the
client.
HttpSession session = req.getSession();
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("<HEAD><TITLE>
SessionPeek ");
out.println("
</TITLE></HEAD><BODY>");
out.println("<h1> SessionPeek
</h1>");
// A simple hit counter for this
session.
Integer ival = (Integer)
session.getAttribute("sesspeek.cntr");
if(ival==null)
ival =
new
Integer(1);
else
ival =
new
Integer(ival.intValue() + 1);
session.setAttribute("sesspeek.cntr",
ival);
out.println("You have hit this page
<b>"
+ ival + "</b>
times.<p>");
out.println("<h2>");
out.println("Saved Session Data
</h2>");
// Loop through all data in the
session:
Enumeration sesNames =
session.getAttributeNames();
while(sesNames.hasMoreElements())
{
String name =
sesNames.nextElement().toString();
Object value = session.getAttribute(name);
out.println(name + " =
" + value +
"<br>");
}
out.println("<h3> Session
Statistics </h3>");
out.println("Session ID:
"
+ session.getId() +
"<br>");
out.println("New Session:
" + session.isNew()
+
"<br>");
out.println("Creation Time:
"
+ session.getCreationTime());
out.println("<I>("
+
new
Date(session.getCreationTime())
+
")</I><br>");
out.println("Last Accessed Time:
" +
session.getLastAccessedTime());
out.println("<I>("
+
new
Date(session.getLastAccessedTime())
+
")</I><br>");
out.println("Session Inactive
Interval: "
+ session.getMaxInactiveInterval());
out.println("Session ID in Request:
"
+ req.getRequestedSessionId() +
"<br>");
out.println("Is session id from
Cookie: "
+ req.isRequestedSessionIdFromCookie()
+
"<br>");
out.println("Is session id from
URL: "
+ req.isRequestedSessionIdFromURL()
+
"<br>");
out.println("Is session id valid:
"
+ req.isRequestedSessionIdValid()
+
"<br>");
out.println("</BODY>");
out.close();
}
public
String getServletInfo() {
return
"A session tracking
servlet";
}
}
///:~
Inside the service( ) method,
getSession( ) is called for the request object, which returns the
Session object associated with this request. The Session object
does not travel across the network, but instead it lives on the server and is
associated with a client and its requests.
getSession( ) comes in two versions: no
parameter, as used here, and getSession(boolean). getSession(true)
is equivalent to getSession( ). The only reason for the
boolean is to state whether you want the session object created if it is
not found. getSession(true) is the most likely call, hence
getSession( ).
The Session object, if it is not new, will give
us details about the client from previous visits. If the Session object
is new then the program will start to gather information about this
client’s activities on this visit. Capturing this client information is
done through the setAttribute( ) and getAttribute( )
methods of the session object.
java.lang.Object
getAttribute(java.lang.String)
void
setAttribute(java.lang.String name,
java.lang.Object value)
The Session object uses a simple name-value
pairing for loading information. The name is a String, and the value can
be any object derived from java.lang.Object. SessionPeek keeps
track of how many times the client has been back during this session. This is
done with an Integer object named sesspeek.cntr. If the name is
not found an Integer is created with value of one, otherwise an
Integer is created with the incremented value of the previously held
Integer. The new Integer is placed into the Session object.
If you use same key in a setAttribute( ) call, then the new object
overwrites the old one. The incremented counter is used to display the number of
times that the client has visited during this session.
getAttributeNames( ) is related to
getAttribute( ) and setAttribute( ); it returns an
enumeration of the names of the objects that are bound to the Session
object. A while loop in SessionPeek shows this method in action.
You may wonder how long a Session object hangs
around. The answer depends on the servlet container you are using; they usually
default to 30 minutes (1800 seconds), which is what you should see from the
ServletPeek call to getMaxInactiveInterval( ). Tests seem to
produce mixed results between servlet containers. Sometimes the Session
object can hang around overnight, but I have never seen a case where the
Session object disappears in less than the time specified by the inactive
interval. You can try this by setting the inactive interval with
setMaxInactiveInterval( ) to 5 seconds and see if your
Session object hangs around or if it is cleaned up at the appropriate
time. This may be an attribute you will want to investigate while choosing a
servlet container.
Running the servlet examples
If you are not already working with an application
server that handles Sun’s servlet and JSP technologies for you, you may
download the Tomcat implementation of Java servlets and JSPs, which is a free,
open-source implementation of servlets, and is the official reference
implementation sanctioned by Sun. It can be found at
jakarta.apache.or
Follow the instructions for installing the Tomcat
implementation, then edit the server.xml file to point to the location in
your directory tree where your servlets will be placed. Once you start up the
Tomcat program you can test your servlet programs.
This has only been a brief introduction to servlets;
there are entire books on the subject. However, this introduction should give
you enough ideas to get you started. In addition, many of the ideas in the next
section are backward compatible with servlets.
|