So far we’ve used the term web application generically, referring to any kind of browser-based application that is located on a web server. Now we are going to be more precise with that term. In the context of the Java Servlet API, a web application is a collection of servlets and Java web services that support Java classes, content such as HTML or JSP pages and images, and configuration information. For deployment (installation on a web server), a web application is bundled into a WAR file. We’ll discuss WAR files in detail later, but suffice it to say that they are really just JAR archives that contain all the application files along with some deployment information. The important thing is that the standardization of WAR files means not only that the Java code is portable, but also that the process of deploying the application to a server is standardized.
Most WAR archives have at their core a web.xml file. This is an XML configuration file that describes which servlets are to be deployed, their names and URL paths, their initialization parameters, and a host of other information, including security and authentication requirements. In recent years, however, the web.xml file has become optional for many applications due to the introduction of Java annotations that take the place of the XML configuration. In most cases, you can now deploy your servlets and Java web services simply by annotating the classes with the necessary information and packaging them into the WAR file, or using a combination of the two. We’ll discuss this in detail later in the chapter.
Web applications, or web apps, also have a well-defined runtime environment. Each web app has its own “root” path on the web server, meaning that all the URLs addressing its servlets and files start with a common unique prefix (e.g., http://www.oreilly.com/someapplication/). The web app’s servlets are also isolated from those of other web applications. Web apps cannot directly access each other’s files (although they may be allowed to do so through the web server, of course). Each web app also has its own servlet context. We’ll discuss the servlet context in more detail, but in brief, it is a common area for servlets within an application to share information and get resources from the environment. The high degree of isolation between web applications is intended to support the dynamic deployment and updating of applications required by modern business systems and to address security and reliability concerns. Web apps are intended to be coarse-grained, relatively complete applications—not to be tightly coupled with other web apps. Although there’s no reason you can’t make web apps cooperate at a high level, for sharing logic across applications you might want to consider web services, which we’ll discuss later in this chapter.
Let’s jump now to the Servlet API and get started building
servlets. We’ll fill in the gaps later when we discuss various parts of
the APIs and WAR file structure in more detail. The Servlet API is very
simple (reminiscent of the old Applet API). The base Servlet
class has three
lifecycle methods—init()
, service()
, and destroy()
—along with some methods for getting
configuration parameters and servlet resources. However, these methods
are not often used directly by developers. Generally developers will
implement the doGet()
and doPost()
methods of the
HttpServlet
subclass
and access shared resources through the servlet context, as we’ll
discuss shortly.
Generally, only one instance of each deployed servlet class is
instantiated per container. More precisely, it is one instance per
servlet entry in the web.xml file, but we’ll talk
more about servlet deployment later. In the past, there was an exception
to that rule when using the special SingleThreadModel
type of servlet. As of
Servlet API 2.4, single-threaded servlets have been deprecated.
By default, servlets are expected to handle requests in a multithreaded way; that is, the servlet’s service methods may be invoked by many threads at the same time. This means that you should not store per-request or per-client data in instance variables of your servlet object. (Of course, you can store general data related to the servlet’s operation, as long as it does not change on a per-request basis.) Per-client state information can be stored in a client session object on the server or in a client-side cookie, which persists across client requests. We’ll talk about client state later as well.
The service()
method of a
servlet accepts two parameters: a servlet “request” object and a servlet
“response” object. These provide tools for reading the client request
and generating output; we’ll talk about them (or rather their HttpServlet
versions) in detail in the
examples.
The package of primary interest to us here is javax.servlet.http
,
which contains APIs specific to servlets that handle HTTP requests for
web servers. In theory, you can write servlets for other protocols, but
nobody really does that and we are going to discuss servlets as if all
servlets were HTTP-related.
The primary tool provided by the javax.servlet.http
package is the HttpServlet
base class.
This is an abstract servlet that provides some basic implementation
details related to handling an HTTP request. In particular, it overrides
the generic servlet service()
request
and breaks it out into several HTTP-related methods, including doGet()
, doPost()
, doPut()
, and doDelete()
. The default service()
method
examines the request to determine what kind it is and dispatches it to
one of these methods, so you can override one or more of them to
implement the specific protocol behavior you need.
doGet()
and doPost()
correspond to the standard HTTP
GET
and POST
operations.
GET
is the standard request for
retrieving a file or document at a specified URL. POST
is the method by which a client sends an
arbitrary amount of data to the server. HTML forms utilize POST
to send data as do most web
services.
To round these out, HttpServlet
provides the doPut()
and doDelete()
methods. These methods correspond
to a less widely used part of the HTTP protocol, which is meant to
provide a way to upload and remove files or file-like entities. doPut()
is supposed to be like POST
but with slightly different semantics (a
PUT
is supposed to logically replace
the item identified by the URL, whereas POST
presents data to it); doDelete()
would be its
opposite.
HttpServlet
also implements
three other HTTP-related methods for you: doHead()
, doTrace()
, and
doOptions()
. You don’t
normally need to override these methods. doHead()
implements the HTTP HEAD
request, which asks for the headers of a
GET
request without the body.
HttpServlet
implements this by
default in the trivial way, by performing the GET
method and then sending only the headers.
You may wish to override doHead()
with a more efficient implementation if you can provide one as an
optimization. doTrace()
and doOptions()
implement other features of HTTP
that allow for debugging and simple client/server capabilities
negotiation. You shouldn’t normally need to override these.
Along with HttpServlet
,
javax.servlet.http
also includes
subclasses of the objects ServletRequest
and
ServletResponse
,
HttpServletRequest
and
HttpServletResponse
.
These subclasses provide, respectively, the input and output streams
needed to read and write client data. They also provide the APIs for
getting or setting HTTP header information and, as we’ll see, client
session information. Rather than document these dryly, we’ll show them
in the context of some examples. As usual, we’ll start with the simplest
possible example.
Here’s our servlet version of “Hello, World,” HelloClient
:
@WebServlet
(
urlPatterns
={
"/hello"
})
public
class
HelloClient
extends
HttpServlet
{
public
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
response
.
setContentType
(
"text/html"
);
// must come first
PrintWriter
out
=
response
.
getWriter
();
out
.
println
(
"<html><head><title>Hello Client!</title></head><body>"
+
"<h1>Hello Client!</h1>"
+
"</body></html>"
);
}
}
If you want to try this servlet right away, skip ahead to WAR Files and Deployment, where we walk through the process
of deploying this servlet. Because we’ve included the WebServlet
annotation
in our class, this servlet does not need a web.xml file for deployment. All you have to
do is bundle the class file into a particular folder within a WAR
archive (a fancy ZIP file) and drop it into a directory monitored by the
Tomcat server. For now, we’re going to focus on just the servlet example
code itself, which is pretty simple in this case.
Let’s have a look at the example. HelloClient
extends the base HttpServlet
class and overrides the doGet()
method to handle simple requests. In
this case, we want to respond to any GET
request by sending back a one-line HTML
document that says “Hello Client!” First, we tell the container what
kind of response we are going to generate, using the setContentType()
method
of the HttpServletResponse
object. We
specify the MIME type “text/html” for our HTML response. Then, we get
the output stream using the getWriter()
method and print the message to
it. It is not necessary for us to explicitly close the stream. We’ll
talk more about managing the output stream throughout this
chapter.
The doGet()
method of our example servlet
declares that it can throw a ServletException
. All
of the service methods of the Servlet API may throw a ServletException
to indicate that a request
has failed. A ServletException
can
be constructed with a string message and an optional Throwable
parameter that can carry any
corresponding exception representing the root cause of the
problem:
throw
new
ServletException
(
"utter failure"
,
someException
);
By default, the web server determines exactly what is shown to
the user whenever a ServletException
is thrown; often there
is a “development mode” where the exception and its stack trace are
displayed. Using the web.xml
file, you can designate custom error pages. (See the section Error and Index Pages for details.)
Alternatively, a servlet may throw an UnavailableException
,
a subclass of ServletException
, to
indicate that it cannot handle requests. This exception can be thrown
to indicate that the condition is permanent or that it should last for
a specified period of seconds.
Before fetching the output stream and writing to it, we
must specify the kind of output we are sending by calling the response
parameter’s setContentType()
method. In this case, we set the content type to text/html
, which is the proper MIME type for
an HTML document. In general, though, it’s possible for a servlet to
generate any kind of data, including audio, video, or some other kind
of text or binary document. If we were writing a generic FileServlet
to serve files like a regular
web server, we might inspect the filename extension and determine the
MIME type from that or from direct inspection of the data. (This is a
good use for the java.nio.file.Files
probeConentType()
method!) For writing binary data, you can
use the getOutputStream()
method to get an OutputStream
as
opposed to a Writer
.
The content type is used in the Content-Type:
header of the server’s HTTP
response, which tells the client what to expect even before it starts
reading the result. This allows your web browser to prompt you with
the “Save File” dialog when you click on a ZIP archive or executable
program. When the content-type string is used in its full form to
specify the character encoding (for example, text/html; charset=ISO-8859-1
), the
information is also used by the servlet engine to set the character
encoding of the PrintWriter
output
stream. As a result, you should always call the setContentType()
method before fetching the
writer with the getWriter()
method.
The character encoding can also be set separately via the servlet
response setCharacterEncoding()
method.
In addition to providing the output stream for writing
content to the client, the HttpServletResponse
object provides methods for controlling other aspects of the HTTP
response, including headers, error result codes, redirects, and servlet
container buffering.
HTTP headers are metadata name/value pairs sent with the response.
You can add headers (standard or custom) to the response with the
setHeader()
and
addHeader()
methods
(headers may have multiple values). There are also convenience methods
for setting headers with integer and date values:
response
.
setIntHeader
(
"MagicNumber"
,
42
);
response
.
setDateHeader
(
"CurrentTime"
,
System
.
currentTimeMillis
()
);
When you write data to the client, the servlet container
automatically sets the HTTP response code to a value of 200, which means
OK. Using the sendError()
method, you
can generate other HTTP response codes. HttpServletResponse
contains predefined
constants for all of the standard codes. Here are a few common
ones:
HttpServletResponse
.
SC_OK
HttpServletResponse
.
SC_BAD_REQUEST
HttpServletResponse
.
SC_FORBIDDEN
HttpServletResponse
.
SC_NOT_FOUND
HttpServletResponse
.
SC_INTERNAL_SERVER_ERROR
HttpServletResponse
.
SC_NOT_IMPLEMENTED
HttpServletResponse
.
SC_SERVICE_UNAVAILABLE
When you generate an error with sendError()
, the response is over and you
can’t write any actual content to the client. You can specify a short
error message, however, which may be shown to the client. (See the
section A Simple Filter.)
An HTTP redirect is a special kind of response that tells the
client web browser to go to a different URL. Normally this happens
quickly and without any interaction from the user. You can send a
redirect with the sendRedirect()
method:
response
.
sendRedirect
(
"http://www.oreilly.com/"
);
While we’re talking about the response, we should say a few words
about buffering. Most responses are buffered internally by the servlet
container until the servlet service method has exited or a preset
maximum size has been reached. This allows the container to set the HTTP
content-length header automatically, telling the client how much data to
expect. You can control the size of this buffer with the setBufferSize()
method,
specifying a size in bytes. You can even clear it and start over if no
data has been written to the client. To clear the buffer, use isCommitted()
to test whether any data has
been set, then use resetBuffer()
to dump
the data if none has been sent. If you are sending a lot of data, you may wish to set the content
length explicitly with the setContent
Length()
method.
Our first example showed how to accept a basic request. Of
course, to do anything really useful, we’ll need to get some information
from the client. Fortunately, the servlet engine handles this for us,
interpreting both GET
and POST
form-encoded data from the client and
providing it to us through the simple getParameter()
method of the servlet
request.
There are two common ways to pass information from your
web browser to a servlet or CGI program. The most general is to “post”
it, meaning that your client encodes the information and sends it as a
stream to the program, which decodes it. Posting can be used to upload
large amounts of form data or other data, including files. The other
way to pass information is to somehow encode the information in the
URL of your client’s request. The primary way to do this is to use
GET
-style encoding of parameters in
the URL string. In this case, the web browser encodes the parameters
and appends them to the end of the URL string. The server decodes them
and passes them to the application.
As we described in Chapter 14,
GET
-style encoding takes the
parameters and appends them to the URL in a name/value fashion, with
the first parameter preceded by a question mark (?) and the rest
separated by ampersands (&). The entire string is expected to be
URL-encoded: any special characters (such as
spaces, ?, and & in the string) are specially encoded.
Another way to pass data in the URL is called extra path. This simply means that when the server has located your servlet or CGI program as the target of a URL, it takes any remaining path components of the URL string and hands them over as an extra part of the URL. For example, consider these URLs:
http:
//www.myserver.example/servlets/MyServlet
http:
//www.myserver.example/servlets/MyServlet/foo/bar
Suppose the server maps the first URL to the servlet called
MyServlet
. When given the second
URL, the server also invokes MyServlet
, but considers /foo/bar
to be “extra path” that can be
retrieved through the servlet request getExtraPath()
method. This technique is
useful for making more human-readable and meaningful URL pathnames,
especially for document-centric content.
Both GET
and POST
encoding can be used with HTML forms on
the client by specifying get
or
post
in the action
attribute of the form tag. The
browser handles the encoding; on the server side, the servlet engine
handles the decoding.
The content type used by a client to post form data to a servlet
is: “application/x-www-form-urlencoded.” The Servlet API automatically
parses this kind of data and makes it available through the getParameter()
method. However, if you do not call the getParameter()
method, the data remains
available, unparsed, in the input stream and can be read by the
servlet directly.
To users, the primary difference between GET
and POST
is that they can see the GET
information in the encoded URL shown in
their web browser. This can be useful because the user can cut and
paste that URL (the result of a search, for example) and mail it to a
friend or bookmark it for future reference. POST
information is not visible to the user
and ceases to exist after it’s sent to the server. This behavior goes
along with the protocol’s intent that GET
and POST
are to have different semantics. By
definition, the result of a GET
operation is not supposed to have any side effects; that is, it’s not
supposed to cause the server to perform any persistent operations
(such as making a purchase in a shopping cart). In theory, that’s the
job of POST
. That’s why your web
browser warns you about reposting form data again if you hit reload on
a page that was the result of a form posting.
The extra path style would be useful for a servlet that retrieves files or handles a range of URLs in a human-readable way. Extra path information is often useful for URLs that the user must see or remember, because it looks like any other path.
Our first example didn’t do much. This next example prints
the values of any parameters that were received. We’ll start by handling
GET
requests and then make some
trivial modifications to handle POST
as well. Here’s the code:
import
java.io.*
;
import
javax.servlet.http.*
;
import
java.util.*
;
public
class
ShowParameters
extends
HttpServlet
{
public
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
IOException
{
showRequestParameters
(
request
,
response
);
}
void
showRequestParameters
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
IOException
{
response
.
setContentType
(
"text/html"
);
PrintWriter
out
=
response
.
getWriter
();
out
.
println
(
"<html><head><title>Show Parameters</title></head><body>"
+
"<h1>Parameters</h1><ul>"
);
Map
<
String
,
String
[]>
params
=
request
.
getParameterMap
();
for
(
String
name
:
params
.
keySet
()
)
{
String
[]
values
=
params
.
get
(
name
);
out
.
println
(
"<li>"
+
name
+
" = "
+
Arrays
.
asList
(
values
)
);
}
out
.
close
(
);
}
}
As in the first example, we override the doGet()
method. We
delegate the request to a helper method that we’ve created, called
showRequestParameters()
, a method
that enumerates the parameters using the request object’s getParameterMap()
method, which returns a map of parameter name to values, and prints the
names and values. Note that a parameter may have multiple values if it
is repeated in the request from the client, hence the map contains
String []
. To make thing pretty, we
listed each parameter in HTML with <li>
tag.
As it stands, our servlet would respond to any URL that contains a
GET
request. Let’s round it out by
adding our own form to the output and also accommodating POST
method requests. To accept posts, we
override the doPost()
method. The
implementation of doPost()
could
simply call our showRequestParameters()
method, but we can
make it simpler still. The API lets us treat GET
and POST
requests interchangeably because the
servlet engine handles the decoding of request parameters. So we simply
delegate the doPost()
operation to
doGet()
.
Add the following method to the example:
public
void
doPost
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
doGet
(
request
,
response
);
}
Now, let’s add an HTML form to the output. The form lets the user
fill in some parameters and submit them to the servlet. Add this line to
the showRequestParameters()
method before the call to out.close()
:
out
.
println
(
"</ul><p><form method=\"POST\" action=\""
+
request
.
getRequestURI
()
+
"\">"
+
"Field 1 <input name=\"Field 1\" size=20><br>"
+
"Field 2 <input name=\"Field 2\" size=20><br>"
+
"<br><input type=\"submit\" value=\"Submit\"></form>"
);
The form’s action
attribute is
the URL of our servlet so that our servlet will get the data back. We
use the getRequestURI()
method
to get the location of our servlet. For the method
attribute, we’ve specified a POST
operation, but you can try changing the
operation to GET
to see both
styles.
So far, we haven’t done anything terribly exciting. In the next
example, we’ll add some power by introducing a user session to store
client data between requests. But before we go on, we should mention a
useful standard servlet, SnoopServlet
, that is akin to our previous
example.
One of the nicest features of the Servlet API is its simple mechanism for managing a user session. By a session, we mean that the servlet can maintain information over multiple pages and through multiple transactions as navigated by the user; this is also called maintaining state. Providing continuity through a series of web pages is important in many kinds of applications, such as handling a login process or tracking purchases in a shopping cart. In a sense, session data takes the place of instance data in your servlet object. It lets you store data between invocations of your service methods.
Session tracking is supported by the servlet container; you normally don’t have to worry about the details of how it’s accomplished. It’s done in one of two ways: using client-side cookies or URL rewriting. Client-side cookies are a standard HTTP mechanism for getting the client web browser to cooperate in storing state information for you. A cookie is basically just a name/value attribute that is issued by the server, stored on the client, and returned by the client whenever it is accessing a certain group of URLs on a specified server. Cookies can track a single session or multiple user visits.
URL rewriting appends
session-tracking information to the URL, using GET
-style encoding or extra path information.
The term rewriting applies because the server
rewrites the URL before it is seen by the client and absorbs the extra
information before it is passed back to the servlet. In order to support
URL rewriting, a servlet must take the extra step to encode any URLs it
generates in content (e.g., HTML links that may return to the page)
using a special method of the HttpServletResponse
object. We’ll describe
this later. You need to allow for URL rewriting by the server if you
want your application to work with browsers that do not support cookies
or have them disabled. Many sites simply choose not to work without
cookies.
To the servlet programmer, state information is made available
through an HttpSession
object,
which acts like a hashtable for storing any objects you would like to
carry through the session. The objects stay on the server side; a
special identifier is sent to the client through a cookie or URL
rewriting. On the way back, the identifier is mapped to a session, and
the session is associated with the servlet again.
Here’s a simple servlet that shows how to store some string information to track a session:
import
java.io.*
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.*
;
import
java.util.Enumeration
;
public
class
ShowSession
extends
HttpServlet
{
public
void
doPost
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
doGet
(
request
,
response
);
}
public
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
HttpSession
session
=
request
.
getSession
();
boolean
clear
=
request
.
getParameter
(
"clear"
)
!=
null
;
if
(
clear
)
session
.
invalidate
();
else
{
String
name
=
request
.
getParameter
(
"Name"
);
String
value
=
request
.
getParameter
(
"Value"
);
if
(
name
!=
null
&&
value
!=
null
)
session
.
setAttribute
(
name
,
value
);
}
response
.
setContentType
(
"text/html"
);
PrintWriter
out
=
response
.
getWriter
();
out
.
println
(
"<html><head><title>Show Session</title></head><body>"
);
if
(
clear
)
out
.
println
(
"<h1>Session Cleared:</h1>"
);
else
{
out
.
println
(
"<h1>In this session:</h1><ul>"
);
Enumeration
names
=
session
.
getAttributeNames
();
while
(
names
.
hasMoreElements
()
)
{
String
name
=
(
String
)
names
.
nextElement
();
out
.
println
(
"<li>"
+
name
+
" = "
+
session
.
getAttribute
(
name
)
);
}
}
out
.
println
(
"</ul><p><hr><h1>Add String</h1>"
+
"<form method=\"POST\" action=\""
+
request
.
getRequestURI
()
+
"\">"
+
"Name: <input name=\"Name\" size=20><br>"
+
"Value: <input name=\"Value\" size=20><br>"
+
"<br><input type=\"submit\" value=\"Submit\">"
+
"<input type=\"submit\" name=\"clear\" value=\"Clear\"></form>"
);
}
}
When you invoke the servlet, you are presented with a form that prompts you to enter a name and a value. The value string is stored in a session object under the name provided. Each time the servlet is called, it outputs the list of all data items associated with the session. You will see the session grow as each item is added (in this case, until you restart your web browser or the server).
The basic mechanics are much like our ShowParameters
servlet. Our doGet()
method generates the form, which
points back to our servlet via a POST
method. We override doPost()
to
delegate back to our doGet()
method,
allowing it to handle everything. Once in doGet()
, we attempt to fetch the user session
object from the request
object using
getSession()
. The
HttpSession
object
supplied by the request functions like a hashtable. There is a
setAttribute()
method,
which takes a string name and an Object
argument, and a corresponding getAttribute()
method. In our example, we use
the getAttributeNames()
method to
enumerate the values currently stored in the session and to print
them.
By default, getSession()
creates a session if one does not exist. If you want to test for a
session or explicitly control when one is created, you can call the
overloaded version getSession(false)
,
which does not automatically create a new session and returns null
if there is no session. Alternately, you
can check to see if a session was just created with the isNew()
method. To clear a session
immediately, we can use the invalidate()
method.
After calling invalidate()
on a
session, we are not allowed to access it again, so we set a flag in our
example and show the “Session Cleared” message. Sessions may also become
invalid on their own by timing out. You can control session timeout in
the application server or through the web.xml file (via the
“session-timeout” value of the “session config” section). It is
possible, through an interface we’ll talk about later in this chapter,
to find out when a session times out. In general, this appears to the
application as either no session or a new session on the next request.
User sessions are private to each web application and are not shared
across applications.
We mentioned earlier that an extra step is required to support URL
rewriting for web browsers that don’t support cookies. To do this, we
must make sure that any URLs we generate in content are first passed
through the HttpServletResponse
encodeURL()
method. This method takes a string URL and returns
a modified string only if URL rewriting is necessary. Normally, when
cookies are available, it returns the same string. In our previous
example, we could have encoded the server form URL that was retrieved
from getRequestURI()
before passing
it to the client if we wanted to allow for users without
cookies.
Now we build on the previous example to make a servlet
that could be used as part of an online store. ShoppingCart
lets users choose items and add
them to their basket until checkout time. The page generated is not that
pretty, but you can have your web designer guy clean that up with some
CSS (smiley). Here we are just concentrating on the Servlet API:
import
java.io.*
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.*
;
import
java.util.Enumeration
;
public
class
ShoppingCart
extends
HttpServlet
{
String
[]
items
=
new
String
[]
{
"Chocolate Covered Crickets"
,
"Raspberry Roaches"
,
"Buttery Butterflies"
,
"Chicken Flavored Chicklets(tm)"
};
public
void
doPost
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
IOException
,
ServletException
{
doGet
(
request
,
response
);
}
public
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
response
.
setContentType
(
"text/html"
);
PrintWriter
out
=
response
.
getWriter
();
// get or create the session information
HttpSession
session
=
request
.
getSession
();
int
[]
purchases
=
(
int
[])
session
.
getAttribute
(
"purchases"
);
if
(
purchases
==
null
)
{
purchases
=
new
int
[
items
.
length
];
session
.
setAttribute
(
"purchases"
,
purchases
);
}
out
.
println
(
"<html><head><title>Shopping Cart</title>"
+
"</title></head><body><p>"
);
if
(
request
.
getParameter
(
"checkout"
)
!=
null
)
out
.
println
(
"<h1>Thanks for ordering!</h1>"
);
else
{
if
(
request
.
getParameter
(
"add"
)
!=
null
)
{
addPurchases
(
request
,
purchases
);
out
.
println
(
"<h1>Purchase added. Please continue</h1>"
);
}
else
{
if
(
request
.
getParameter
(
"clear"
)
!=
null
)
for
(
int
i
=
0
;
i
<
purchases
.
length
;
i
++)
purchases
[
i
]
=
0
;
out
.
println
(
"<h1>Please Select Your Items!</h1>"
);
}
doForm
(
out
,
request
.
getRequestURI
()
);
}
showPurchases
(
out
,
purchases
);
out
.
close
();
}
void
addPurchases
(
HttpServletRequest
request
,
int
[]
purchases
)
{
for
(
int
i
=
0
;
i
<
items
.
length
;
i
++)
{
String
added
=
request
.
getParameter
(
items
[
i
]
);
if
(
added
!=
null
&&
!
added
.
equals
(
""
)
)
purchases
[
i
]
+=
Integer
.
parseInt
(
added
);
}
}
void
doForm
(
PrintWriter
out
,
String
requestURI
)
{
out
.
println
(
"<form method=POST action="
+
requestURI
+
">"
);
for
(
int
i
=
0
;
i
<
items
.
length
;
i
++)
out
.
println
(
"Quantity <input name=\""
+
items
[
i
]
+
"\" value=0 size=3> of: "
+
items
[
i
]
+
"<br>"
);
out
.
println
(
"<p><input type=submit name=add value=\"Add To Cart\">"
+
"<input type=submit name=checkout value=\"Check Out\">"
+
"<input type=submit name=clear value=\"Clear Cart\">"
+
"</form>"
);
}
void
showPurchases
(
PrintWriter
out
,
int
[]
purchases
)
throws
IOException
{
out
.
println
(
"<hr><h2>Your Shopping Basket</h2>"
);
for
(
int
i
=
0
;
i
<
items
.
length
;
i
++)
if
(
purchases
[
i
]
!=
0
)
out
.
println
(
purchases
[
i
]
+
" "
+
items
[
i
]
+
"<br>"
);
}
}
Note that ShoppingCart
has some
instance data: a String
array that
holds a list of products. We’re making the assumption that the product
selection is the same for all customers. If it’s not, we’d have to
generate the product list on the fly or put it in the session for the
user. We cannot store any per-request or per-user data in instance
variables.
We see the same basic pattern as in our previous servlets, with
doPost()
delegating to doGet()
, and doGet()
generating the body of the output and
a form for gathering new data. We’ve broken down the work using a few
helper methods: doForm()
, addPurchases()
, and showPurchases()
. Our shopping cart form has
three submit buttons: one for adding items to the cart, one for
checkout, and one for clearing the cart. In each case, we display the
contents of the cart. Depending on the button pressed (indicated by the
name of the parameter), we add new purchases, clear the list, or show
the results as a checkout window.
The form is generated by our doForm()
method, using the list of items for
sale. As in the other examples, we supply our servlet’s address as the
target of the form. Next, we placed an integer array called purchases
into the user session. Each element
in purchases
holds a count of the
number of each item the user wants to buy. We create the array after
retrieving the session simply by asking the session for it. If this is a
new session, and the array hasn’t been created, getAttribute()
gives us a null value and we
create an empty array to populate. Because we generate the form using
the names from the items
array, it’s
easy for addPurchases()
to check for
each name using getParameter()
and
increment the purchases
array for the
number of items requested. We also test for the value being equal to the
empty string, because some web browsers send empty strings for unused
field values. Finally, showPurchases()
loops over the purchases array
and prints the name and quantity for each item that the user has
purchased.
In our previous examples, a session lived only until you
shut down your web browser or the server. You can do more long-term user
tracking or identification that lasts beyond a single browser session by
managing cookies explicitly. You can send a cookie to the client by
creating a javax.servlet.http.Cookie
object and adding it
to the servlet response using the addCookie()
method.
Later, you can retrieve the cookie information from the servlet request
and use it to look up persistent information in a database. The
following servlet sends a “Learning Java” cookie to your web browser and
displays it when you return to the page:
import
java.io.*
;
import
javax.servlet.*
;
import
javax.servlet.http.*
;
public
class
CookieCutter
extends
HttpServlet
{
public
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
IOException
,
ServletException
{
response
.
setContentType
(
"text/html"
);
PrintWriter
out
=
response
.
getWriter
(
);
if
(
request
.
getParameter
(
"setcookie"
)
!=
null
)
{
Cookie
cookie
=
new
Cookie
(
"Learningjava"
,
"Cookies!"
);
cookie
.
setMaxAge
(
3600
);
response
.
addCookie
(
cookie
);
out
.
println
(
"<html><body><h1>Cookie Set...</h1>"
);
}
else
{
out
.
println
(
"<html><body>"
);
Cookie
[]
cookies
=
request
.
getCookies
(
);
if
(
cookies
.
length
==
0
)
{
out
.
println
(
"<h1>No cookies found...</h1>"
);
}
else
{
for
(
int
i
=
0
;
i
<
cookies
.
length
;
i
++)
out
.
(
"<h1>Name: "
+
cookies
[
i
].
getName
()
+
"<br>"
+
"Value: "
+
cookies
[
i
].
getValue
()
+
"</h1>"
);
}
out
.
println
(
"<p><a href=\""
+
request
.
getRequestURI
()
+
"?setcookie=true\">"
+
"Reset the Learning Java cookie.</a>"
);
}
out
.
println
(
"</body></html>"
);
}
}
This example simply enumerates the cookies supplied by the request
object using the getCookies()
method and
prints their names and values. We provide a GET
-style link that points back to our servlet
with a parameter setcookie
, indicating
that we should set the cookie. In that case, we create a Cookie
object using the specified name and
value and add it to the response with the addCookie()
method. We set the maximum age of
the cookie to 3,600 seconds, so it remains in the browser for an hour
before being discarded (we’ll talk about tracking a cookie across
multiple sessions later). Specifying a negative time period indicates
that the cookie should not be stored persistently and should be erased
when the browser exits. A time period of 0
deletes any existing cookie
immediately.
Two other Cookie
methods are of
interest: setDomain()
and
setPath()
. These
methods allow you to specify the domain name and path component that
determines where the client will send the cookie. If you’re writing some
kind of purchase applet for L.L. Bean, you don’t want clients sending
your cookies over to Eddie Bauer. In practice, however, this cannot
happen. The default domain is the domain of the server sending the
cookie. (You cannot in general specify other domains for security
reasons.) The path
parameter defaults
to the base URL of the servlet, but you can specify a wider (or
narrower) range of URLs on the host server by manually setting this
parameter.
Web applications have access to the server environment
through the ServletContext
API, a
reference to which can be obtained from the HttpServlet getServletContext()
method:
ServletContext
context
=
getServletContext
();
Each web app has its own ServletContext
. The context provides a shared
space in which a web app’s servlets may rendezvous and share objects.
Objects may be placed into the context with the setAttribute()
method
and retrieved by name with the getAttribute()
method:
context
.
setAttribute
(
"myapp.statistics"
,
myObject
);
Object
stats
=
context
.
getAttribute
(
"myapp.statistics"
);
Attribute names beginning with “java.” and “javax.” are reserved for use by Java. You can opt to use the standard package-naming conventions for your attributes to avoid conflicts.
The ServletContext
provides a
listener API that can be used to add items to the servlet context when
the application server starts up and to tear them down when it shuts
down. This is a good way to initiate shared services. We’ll show an
example of this in the next section when we talk about asynchronous
servlets.
One standard attribute that can be accessed through the servlet
context is a reference to a private working directory represented by a
java.io.File
object. This temp
directory is guaranteed unique to the web app. No guarantees are made
about it being cleared upon exit, however, so you should use the
temporary file API to create files here (unless you wish to try to keep
them beyond the server exit). For example:
File
tmpDir
=
(
File
)
context
.
getAttribute
(
"javax.servlet.context.tempdir"
);
File
tmpFile
=
File
.
createTempFile
(
"appprefix"
,
"appsuffix"
,
tmpDir
);
The servlet context also provides direct access to the web app’s
files from its root directory. The getResource()
method is similar to the
Class getResource()
method (see Chapter 12). It takes a pathname and returns a
special local URL for accessing that resource. In this case, it takes a
path rooted in the servlet base directory (WAR file). The servlet may
obtain references to files, including those in the WEB-INF directory, using this method.
For example, a servlet could fetch an input stream for its own
web.xml file:
InputStream
in
=
context
.
getResourceAsStream
(
"/WEB-INF/web.xml"
);
It could also use a URL reference to get one of its images:
URL
bunnyURL
=
context
.
getResource
(
"/images/happybunny.gif"
);
The method getResourcePaths()
may
be used to fetch a directory-style listing of all the resource files
available matching a specified path. The return value is a java.util.Set
collection of strings naming the
resources available under the specified path. For example, the path
/ lists all files in the WAR; the path
/WEB-INF/ lists at least the
web.xml file and classes
directory.
The ServletContext
is also a
factory for RequestDispatcher
objects, which we won’t cover here, but which allow for servlets to
forward to or include the results of other servlets in their
responses.
The following is a somewhat advanced topic, but we’ll cover it now to round out our discussion of the Servlet API. Servlets may run in an asynchronous mode, where the servlet service method is allowed to exit, but the response to the user is held open until it can be completed efficiently. While the response is held open, it does not actively consume resources or block threads in the servlet container. This is intended to support nonblocking, NIO-style services as discussed in Chapters 13 and 14.
Asynchronous servlets are an excellent way to handle very slow servlet processes, as long as there is a way to efficiently poll for or receive some truly asynchronous notification of their completion. As we discussed when talking about NIO, one of the limiting factors in the scalability of web services is thread consumption. Threads hold a lot of resources and so simply allowing them to block and wait for completion of a task is inefficient. As we saw earlier, NIO supports a style of programming where one thread can manage a large number of network connections. Asynchronous servlets allow servlets to participate in this model. The basic idea is that you pass a job to a background service and put the servlet request on the shelf until it can be completed. As long as the background processor is implemented in such a way that it can manage the jobs without waiting (via polling or receiving updates asynchronously), then there is no point where threads must block.
Later in this chapter, we’ll utilize a simple test servlet called
WaitServlet
that simply goes to sleep
for a specified period of time before returning a result. This is a
prime example of an inefficient use of threads. Our
dumb WaitServlet
blocks a thread (by
sleeping) until it is “ready” to complete the transaction. In the
following example, we’ll get ahead of ourselves a bit and create a more
efficient version of this tool, BackgroundWaitServlet
, that will not block any
threads in the servlet container while it waits.
Before we start, let’s check our preconditions for whether an
asynchronous servlet will be useful: do we have an efficient way to poll
or receive notification when our “task” is complete without blocking a
thread? (It’s important to ask this to avoid simply moving thread
blocking from the servlet to another location.) Yes, in our case, we can
use a timer to notify us when the time has passed. An efficient timer
implementation like java.util.Timer
will use only one thread to manage many timed requests. We’ll choose to
use a ScheduledExecutorService
from the java.util.concurrent
package for this. It will execute any Runnable
for us after a specified delay and
makes a perfect shared background service for our asynchronous
servlet.
The following example servlet returns a generic response after a delay of five seconds. The difference between this servlet and the naive one we use elsewhere in this chapter would become apparent if we flooded our server with requests. We should find that the asynchronous version would be limited primarily by TCP/IP resources in the host OS and not by more valuable memory on the server.
import
javax.servlet.*
;
import
javax.servlet.annotation.*
;
import
javax.servlet.http.*
;
import
java.io.*
;
import
java.util.concurrent.*
;
@WebServlet
(
urlPatterns
={
"/bgwait"
},
asyncSupported
=
true
)
public
class
BackgroundWaitServlet
extends
HttpServlet
{
public
void
doGet
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
final
AsyncContext
asyncContext
=
request
.
startAsync
();
ScheduledExecutorService
executor
=
(
ScheduledExecutorService
)
request
.
getServletContext
().
getAttribute
(
"BackgroundWaitExecutor"
);
executor
.
schedule
(
new
RespondLaterJob
(
asyncContext
),
5
,
TimeUnit
.
SECONDS
);
}
}
class
RespondLaterJob
implements
Runnable
{
private
AsyncContext
asyncContext
;
RespondLaterJob
(
AsyncContext
asyncContext
)
{
this
.
asyncContext
=
asyncContext
;
}
@Override
public
void
run
()
{
try
{
ServletResponse
response
=
asyncContext
.
getResponse
();
response
.
setContentType
(
"text/html"
);
PrintWriter
out
=
response
.
getWriter
();
out
.
println
(
"<html><body><h1>WaitServlet Response</h1></body></html>"
);
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
e
);
}
asyncContext
.
complete
();
}
}
We’ve included the WebServlet
annotation
in this example in order to show the asyncSupported
attribute. This attribute must be set on any servlets and servlet
filters (discussed later) that will be involved in the request.
The implementation of our doGet()
method is straightforward: we initiate
the asynchronous behavior by calling the startAsync()
method on
the servlet request. That method returns to us an AsyncContext
object
that represents the caller context and includes the servlet request and
response objects. At this point, we are free to arrange to service the
request using any means we wish; the only requirement is that we must
keep the AsyncContext
object with our
task so that it can be used later to send the results and close the
transaction.
In our example, we look up our shared ScheduledExcecutorService
from the servlet
context by name (“BackgroundWaitExecutor”) and pass it a custom Runnable
object. (We’ll talk about how the
service got there in a bit.) We’ve created a RespondLaterJob
that implements Runnable
and holds onto the AsyncContext
for later use. When the job runs
in the future, we simply get the servlet response from the AsyncContext
and
send our response as usual. The final step is to call the
complete()
method on AsyncContext
in
order to close the call and return to the client.
The final step raises a couple of interesting issues: first, we do
not necessarily have to call complete()
immediately after writing to the
response. Instead, we could write part of the result and go back to
sleep, waiting for our service to wake us up when there is more data.
Indeed, this is how we might work with an NIO data source. Second,
instead of calling complete()
to
finalize the results for the client, we could use an alternate method,
dispatch()
, to forward the servlet
request to another servlet, perhaps in a chain of servlets. The next
servlet could write additional content or perhaps simply use resources
put into the servlet context by the first servlet to handle the request.
The dispatch()
method accepts a URL
string for the target servlet or, when called with no arguments, sends
the request back to the original servlet.
OK, so how did our ScheduledExecutorService
get into the servlet
context? The best way to manage shared services and resources in the
servlet context is via a ServletContextListener
.
A context listener has two lifecycle methods that can be used to set up
and tear down services when the servlet container starts up and shuts
down, respectively. We can deploy our listener simply by marking the
class with a WebListener
annotation
and placing it in the WAR file as usual.
import
javax.servlet.*
;
import
javax.servlet.annotation.*
;
import
java.util.concurrent.*
;
@WebListener
public
class
BackgroundWaitService
implements
ServletContextListener
{
ScheduledExecutorService
executor
;
public
void
contextInitialized
(
ServletContextEvent
sce
)
{
this
.
executor
=
Executors
.
newScheduledThreadPool
(
3
);
sce
.
getServletContext
().
setAttribute
(
"BackgroundWaitExecutor"
,
executor
);
}
public
void
contextDestroyed
(
ServletContextEvent
sce
)
{
ScheduledExecutorService
executor
=
Executors
.
newScheduledThreadPool
(
3
);
executor
.
shutdownNow
();
}
}
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.