0servlet Basics
0servlet Basics
0servlet Basics
JDBC Database
JNI Legacy Application
RMI
Java Application
SOAP
... Web Service
Client (End User) Web Server
...
(Servlets/JSP)
Figure 3–1 The role of Web middleware.
In principle, servlets are not restricted to Web or application servers that handle
HTTP requests but can be used for other types of servers as well. For example, serv-
lets could be embedded in FTP or mail servers to extend their functionality. In prac-
tice, however, this use of servlets has not caught on, and we discuss only HTTP
servlets.
Listing 3.1 outlines a basic servlet that handles GET requests. GET requests, for those
unfamiliar with HTTP, are the usual type of browser requests for Web pages. A
browser generates this request when the user enters a URL on the address line, fol-
lows a link from a Web page, or submits an HTML form that either does not specify
Listing 3.2 shows a simple servlet that outputs plain text, with the output shown in
Figure 3–2. Before we move on, it is worth spending some time reviewing the pro-
cess of installing, compiling, and running this simple servlet. See Chapter 2 (Server
Setup and Configuration) for a much more detailed description of the process.
First, be sure that you’ve already verified the basics:
Finally, invoke your servlet. This last step involves using either the default URL of
http://host/servlet/ServletName or a custom URL defined in the web.xml file as
described in Section 2.11 (Web Applications: A Preview). During initial develop-
ment, you will almost certainly find it convenient to use the default URL so that you
don’t have to edit the web.xml file each time you test a new servlet. When you deploy
real applications, however, you almost always disable the default URL and assign
explicit URLs in the web.xml file (see Section 2.11, “Web Applications: A Preview”).
In fact, servers are not absolutely required to support the default URL, and a few,
most notably BEA WebLogic, do not.
Figure 3–2 shows the servlet being accessed by means of the default URL, with
the server running on the local machine.
Most servlets generate HTML, not plain text as in the previous example. To generate
HTML, you add three steps to the process just shown:
You accomplish the first step by setting the HTTP Content-Type response
header to text/html. In general, headers are set by the setHeader method of
HttpServletResponse, but setting the content type is such a common task that
there is also a special setContentType method just for this purpose. The way to
designate HTML is with a type of text/html, so the code would look like this:
response.setContentType("text/html");
Although HTML is the most common kind of document that servlets create, it is
not unusual for servlets to create other document types. For example, it is quite com-
mon to use servlets to generate Excel spreadsheets (content type application/
vnd.ms-excel—see Section 7.3), JPEG images (content type image/jpeg—see
Section 7.5), and XML documents (content type text/xml). Also, you rarely use
servlets to generate HTML pages that have relatively fixed formats (i.e., whose lay-
out changes little for each request); JSP is usually more convenient in such a case.
JSP is discussed in Part II of this book (starting in Chapter 10).
Don’t be concerned if you are not yet familiar with HTTP response headers; they
are discussed in Chapter 7. However, you should note now that you need to set
response headers before actually returning any of the content with the PrintWriter.
That’s because an HTTP response consists of the status line, one or more headers, a
blank line, and the actual document, in that order. The headers can appear in any
order, and servlets buffer the headers and send them all at once, so it is legal to set the
status code (part of the first line returned) even after setting headers. But servlets do
not necessarily buffer the document itself, since users might want to see partial results
for long pages. Servlet engines are permitted to partially buffer the output, but the
size of the buffer is left unspecified. You can use the getBufferSize method of
HttpServletResponse to determine the size, or you can use setBufferSize to
specify it. You can set headers until the buffer fills up and is actually sent to the client.
If you aren’t sure whether the buffer has been sent, you can use the isCommitted
method to check. Even so, the best approach is to simply put the setContentType
line before any of the lines that use the PrintWriter.
Core Warning
You must set the content type before transmitting the actual document.
The second step in writing a servlet that builds an HTML document is to have your
println statements output HTML, not plain text. Listing 3.3 shows HelloServlet.java,
the sample servlet used in Section 2.8 to verify that the server is functioning properly.
As Figure 3–3 illustrates, the browser formats the result as HTML, not as plain text.
The final step is to check that your HTML has no syntax errors that could cause
unpredictable results on different browsers. See Section 3.5 (Simple HTML-Build-
ing Utilities) for a discussion of HTML validators.
package somePackage;
Figure 3–4 shows the servlet accessed by means of the default URL.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
<!DOCTYPE ...>
<HTML>
<HEAD><TITLE>...</TITLE>...</HEAD>
<BODY ...>...</BODY>
</HTML>
When using servlets to build the HTML, you might be tempted to omit part of
this structure, especially the DOCTYPE line, noting that virtually all major browsers
ignore it even though the HTML specifications require it. We strongly discourage
this practice. The advantage of the DOCTYPE line is that it tells HTML validators
which version of HTML you are using so they know which specification to check
your document against. These validators are valuable debugging services, helping
you catch HTML syntax errors that your browser guesses well on but that other
browsers will have trouble displaying.
The two most popular online validators are the ones from the World Wide Web Con-
sortium (http://validator.w3.org/) and from the Web Design Group (http://www.html-
help.com/tools/validator/). They let you submit a URL, then they retrieve the page,
check the syntax against the formal HTML specification, and report any errors to you.
Since, to a client, a servlet that generates HTML looks exactly like a regular Web page, it
can be validated in the normal manner unless it requires POST data to return its result.
Since GET data is attached to the URL, you can even send the validators a URL that
includes GET data. If the servlet is available only inside your corporate firewall, simply
run it, save the HTML to disk, and choose the validator’s File Upload option.
Core Approach
Use an HTML validator to check the syntax of pages that your servlets
generate.
import javax.servlet.*;
import javax.servlet.http.*;
/** Some simple time savers. Note that most are static methods. */
...
}
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
Now, if you have a servlet that needs to handle both POST and GET requests iden-
tically, you may be tempted to override service directly rather than implementing
both doGet and doPost. This is not a good idea. Instead, just have doPost call
doGet (or vice versa), as below.
Although this approach takes a couple of extra lines of code, it has several advan-
tages over directly overriding service. First, you can later add support for other
HTTP request methods by adding doPut, doTrace, etc., perhaps in a subclass.
Overriding service directly precludes this possibility. Second, you can add support
for modification dates by adding a getLastModified method, as illustrated in
Listing 3.7. Since getLastModified is invoked by the default service method,
overriding service eliminates this option. Finally, service gives you automatic
support for HEAD, OPTION, and TRACE requests.
Core Approach
If your servlet needs to handle both GET and POST identically, have your
doPost method call doGet, or vice versa. Don’t override service.
to implement doHead so that you can generate responses to HEAD requests (i.e.,
requests from custom clients that want just the HTTP headers, not the actual docu-
ment) more quickly—without building the actual document output.
General Initializations
With the first type of initialization, init simply creates or loads some data that will
be used throughout the life of the servlet, or it performs some one-time computation.
If you are familiar with applets, this task is analogous to an applet calling getImage
to load image files over the network: the operation only needs to be performed once,
so it is triggered by init. Servlet examples include setting up a database connection
pool for requests that the servlet will handle or loading a data file into a HashMap.
Listing 3.7 shows a servlet that uses init to do two things.
First, it builds an array of 10 integers. Since these numbers are based upon com-
plex calculations, we don’t want to repeat the computation for each request. So,
doGet looks up the values that init computed, instead of generating them each
time. The results of this technique are shown in Figure 3–6.
Second, since the output of the servlet does not change except when the server is
rebooted, init also stores a page modification date that is used by the getLast-
Modified method. This method should return a modification time expressed in mil-
liseconds since 1970, as is standard with Java dates. The time is automatically
converted to a date in GMT appropriate for the Last-Modified header. More
importantly, if the server receives a conditional GET request (one specifying that the
client only wants pages marked If-Modified-Since a particular date), the system
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
/** The init method is called only when the servlet is first
* loaded, before the first request is processed.
*/
Figures 3–7 and 3–8 show the result of requests for the same servlet with two
slightly different If-Modified-Since dates. To set the request headers and see
the response headers, we used WebClient, a Java application that lets you inter-
actively set up HTTP requests, submit them, and see the “raw” results. The code
for WebClient is available at the source code archive on the book’s home page
(http://www.coreservlets.com/).
Figure 3–7 Accessing the LotteryNumbers servlet results in normal response (with
the document sent to the client) in two situations: when there is an unconditional GET
request or when there is a conditional request that specifies a date before servlet
initialization. Code for the WebClient program (used here to interactively connect to the
server) is available at the book’s source code archive at http://www.coreservlets.com/.
Figure 3–8 Accessing the LotteryNumbers servlet results in a 304 (Not Modified)
response with no actual document in one situation: when a conditional GET request is
received that specifies a date at or after servlet initialization.
1. Developers.
2. End users.
3. Deployers.
Developers change the behavior of a servlet by changing the code. End users
change the behavior of a servlet by providing data to an HTML form (assuming that
the developer has written the servlet to look for this data). But what about deployers?
There needs to be a way to let administrators move servlets from machine to
machine and change certain parameters (e.g., the address of a database, the size of a
connection pool, or the location of a data file) without modifying the servlet source
code. Providing this capability is the purpose of init parameters.
Because the use of servlet initialization parameters relies heavily on the deploy-
ment descriptor (web.xml), we postpone details and examples on init parameters
until the deployment descriptor chapter in Volume 2 of this book. But, here is a brief
preview:
Normally, the system makes a single instance of your servlet and then creates a new
thread for each user request. This means that if a new request comes in while a pre-
vious request is still executing, multiple threads can concurrently be accessing the
same servlet object. Consequently, your doGet and doPost methods must be care-
ful to synchronize access to fields and other shared data (if any) since multiple
threads may access the data simultaneously. Note that local variables are not shared
by multiple threads, and thus need no special protection.
In principle, you can prevent multithreaded access by having your servlet imple-
ment the SingleThreadModel interface, as below.
If you implement this interface, the system guarantees that there is never more
than one request thread accessing a single instance of your servlet. In most cases, it
does so by queuing all the requests and passing them one at a time to a single servlet
instance. However, the server is permitted to create a pool of multiple instances,
each of which handles one request at a time. Either way, this means that you don’t
have to worry about simultaneous access to regular fields (instance variables) of the
servlet. You do, however, still have to synchronize access to class variables (static
fields) or shared data stored outside the servlet.
Although SingleThreadModel prevents concurrent access in principle, in prac-
tice there are two reasons why it is usually a poor choice.
First, synchronous access to your servlets can significantly hurt performance
(latency) if your servlet is accessed frequently. When a servlet waits for I/O, the
server cannot handle pending requests for the same servlet. So, think twice before
using the SingleThreadModel approach. Instead, consider synchronizing only the
part of the code that manipulates the shared data.
The second problem with SingleThreadModel stems from the fact that the
specification permits servers to use pools of instances instead of queueing up the
requests to a single instance. As long as each instance handles only one request at a
time, the pool-of-instances approach satisfies the requirements of the specification.
But, it is a bad idea.
Suppose, on one hand, that you are using regular non-static instance variables
(fields) to refer to shared data. Sure, SingleThreadModel prevents concurrent
access, but it does so by throwing out the baby with the bath water: each servlet
instance has a separate copy of the instance variables, so the data is no longer shared
properly.
On the other hand, suppose that you are using static instance variables to refer to
the shared data. In that case, the pool-of-instances approach to SingleThreadModel
provides no advantage whatsoever; multiple requests (using different instances) can
still concurrently access the static data.
Now, SingleThreadModel is still occasionally useful. For example, it can be
used when the instance variables are reinitialized for each request (e.g., when they
are used merely to simplify communication among methods). But, the problems with
SingleThreadModel are so severe that it is deprecated in the servlet 2.4 (JSP 2.0)
specification. You are much better off using explicit synchronized blocks.
Core Warning
For example, consider the servlet of Listing 3.8 that attempts to assign unique
user IDs to each client (unique until the server restarts, that is). It uses an instance
variable (field) called nextID to keep track of which ID should be assigned next,
and uses the following code to output the ID.
Now, suppose you were very careful in testing this servlet. You put it in a subdirec-
tory called coreservlets, compiled it, and copied the coreservlets directory to the
WEB-INF/classes directory of the default Web application (see Section 2.10, “Deploy-
ment Directories for Default Web Application: Summary”). You started the server. You
repeatedly accessed the servlet with http://localhost/servlet/coreservlets.UserIDs.
Every time you accessed it, you got a different value (Figure 3–9). So the code is cor-
rect, right? Wrong! The problem occurs only when there are multiple simultaneous
accesses to the servlet. Even then, it occurs only once in a while. But, in a few cases,
the first client could read the nextID field and have its thread preempted before it
incremented the field. Then, a second client could read the field and get the same
value as the first client. Big trouble! For example, there have been real-world
e-commerce applications where customer purchases were occasionally charged to
the wrong client’s credit card, precisely because of such a race condition in the gen-
eration of user IDs.
Now, if you are familiar with multithreaded programming, the problem was very
obvious to you. The question is, what is the proper solution? Here are three possibilities.
1. Shorten the race. Remove the third line of the code snippet and
change the first line to the following.
synchronized(this) {
String id = "User-ID-" + nextID;
out.println("<H2>" + id + "</H2>");
nextID = nextID + 1;
}
This technique tells the system that, once a thread has entered the
above block of code (or any other synchronized section labelled
with the same object reference), no other thread is allowed in until the
first thread exits. This is the solution you have always used in the Java
programming language. It is the right one here, too. Forget error-
prone and low-performance SingleThreadModel shortcuts; fix race
conditions the right way.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
Naturally, when you write servlets, you never make mistakes. However, some of your
colleagues might make an occasional error, and you can pass this advice on to them.
Seriously, though, debugging servlets can be tricky because you don’t execute them
directly. Instead, you trigger their execution by means of an HTTP request, and they
are executed by the Web server. This remote execution makes it difficult to insert
break points or to read debugging messages and stack traces. So, approaches to serv-
let debugging differ somewhat from those used in general development. Here are 10
general strategies that can make your life easier.
you
process data that comes directly or indirectly from a client, be sure to
consider the possibility that it was entered incorrectly or omitted
altogether.
7. Look at the HTML source.
If the result you see in the browser looks odd, choose View Source
from the browser’s menu. Sometimes a small HTML error like
<TABLE> instead of </TABLE> can prevent much of the page from
being viewed. Even better, use a formal HTML validator on the serv-
let’s output. See Section 3.5 (Simple HTML-Building Utilities) for a
discussion of this approach.
8. Look at the request data separately.
Servlets read data from the HTTP request, construct a response, and
send it back to the client. If something in the process goes wrong, you
want to discover if the cause is that the client is sending the wrong data
or that the servlet is processing it incorrectly. The EchoServer class,
discussed in Chapter 19 (Creating and Processing HTML Forms), lets
you submit HTML forms and get a result that shows you exactly how
the data arrived at the server. This class is merely a simple HTTP server
that, for all requests, constructs an HTML page showing what was sent.
Full source code is online at http://www.coreservlets.com/.
9. Look at the response data separately.
Once you look at the request data separately, you’ll want to do the same
for the response data. The WebClient class, discussed in the init
example of Section 3.6 (The Servlet Life Cycle), lets you connect to the
server interactively, send custom HTTP request data, and see every-
thing that comes back—HTTP response headers and all. Again, you can
download the source code from http://www.coreservlets.com/.
10. Stop and restart the server.
Servers are supposed to keep servlets in memory between requests,
not reload them each time they are executed. However, most servers
support a development mode in which servlets are supposed to be
automatically reloaded whenever their associated class file changes. At
times, however, some servers can get confused, especially when your
only change is to a lower-level class, not to the top-level servlet class.
So, if it appears that changes you make to your servlets are not
reflected in the servlet’s behavior, try restarting the server. Similarly,
the init method is run only when a servlet is first loaded, the
web.xml file (see Section 2.11) is read only when a Web application is
first loaded (although many servers have a custom extension for
reloading it), and certain Web application listeners (see Volume 2) are
triggered only when the server first starts. Restarting the server will
simplify debugging in all of those situations.