Now we’re going to take a leap from the client side over to the server side, to write Java applications for web servers. The Java servlet API is a framework for writing servlets, which are application components for a web server or other type of server; just as applets are application components for a web browser.
The servlet APIs live in the
javax.servlet
package, which is a standard Java API
extension, not part of the core Java APIs. In this book we
haven’t talked about many standard extension packages, but this
is one is particularly important. (It should probably be a core API.)
You’ll want to grab the latest
Java Servlet Development Kit (JSDK)
from http://java.sun.com/products/servlet.
The servlet APIs are useless without a server on which to run them, so you’ll also want to find an implementation of the servlet environment for your favorite web server: Netscape, Apache, or whatever. We won’t try to anticipate which environment you have in this book, so the details about how to install your servlets and invoke them will be up to you. But it should be pretty easy.
Why would we want to use Java on the server side, as opposed to a scripting language, such as Perl? The simplest answer to that question is: for the same reasons that you would use Java anywhere else. Servlets simply let you write in Java and derive all of the benefits of Java and the virtual machine environment on the server side. (You also have the limitations of Java.) Historically, servlets had speed advantages over CGI programs written in scripting languages or even in C/C++. That is because servlets execute in a multithreaded way within one instance of a virtual machine. Older CGI applications required the server to start a separate process, “pipe” data to it, and then receive the response as a stream as well. Speed is still a factor, but a more important reason for using Java is that Java makes writing large applications much more manageable. Java isn’t as easy to use as a scripting language, but it’s much easier to come back to your program next year and add a new feature, and it’s a lot better at scaling to large applications.
Writing server code with servlets allows you to access all of the standard Java APIs within the virtual machine while your servlets are handling requests. This means that your Java server code can access “live” database connections, or communicate with other network services that have already been established. This kind of behavior has been hacked into other CGI environments, but it has always been there in Java in a robust and natural way.
The Servlet API is very simple, almost exactly paralleling the Applet
API. There are three
life-cycle methods, init( )
, service( )
, and destroy( )
, along with a couple of methods for getting configuration
parameters and servlet info. Before a servlet is used for the first
time, it is initialized by the server through its init( )
method. Thereafter the servlet spends its time handling
service( )
requests and doing its job until
(presumably) the server is shut down and the servlet’s
destroy( )
method is called, giving it an
opportunity to clean up.
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 in
detail in the examples.
By default, servlets are expected to handle multithreaded requests; that is, the servlet’s service methods may be invoked by many threads at the same time. This means that you cannot store client-related 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, which persists across client requests. We’ll talk about that in detail later.
If for some reason you have developed a servlet that cannot support
multi-threaded access, you can tell the runtime system this by
implementing the flag interface
SingleThreadModel
. It has no methods, serving only to
indicate that the servlet should be invoked in a single-threaded
manner.
There are actually two packages of interest in the Servlet API. The
first is the
javax.servlet
package, which contains the most general
servlet APIs. The second important package is
javax.servlet.http
, which contains APIs specific to
servlets that handle HTTP requests for web servers. In the rest of this section,
we are going to discuss servlets pretty much as if all servlets were
HTTP-related. Although you can write servlets for other protocols,
that’s not what we’re currently interested in.
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 related to handling an HTTP
request. In particular, it overrides the generic servlet
service( )
request and breaks it out into several
HTTP-related methods for you, 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 corresponding web server behavior.
doGet( )
and doPost( )
correspond to the standard HTTP
GET
and POST
operations.
GET
is the standard request for the object at a
specified URL: e. g., a file or document. POST
is
the method by which a client sends data to the server. HTML forms are
the most common example of users of POST
.
To round these out, HttpServlet
provides the
doPut( )
and doDelete( )
methods. These methods correspond to a poorly supported part of the
HTTP protocol, meant to provide a way to upload and remove
files. doPut( )
is
supposed to be like POST but with different semantics;
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 performing the
GET
and then sending only the headers).
doTrace( )
and doOptions( )
implement other features of HTTP that allow for debugging and for
simple client/server capabilities negotiation. Again, you generally
shouldn’t need to override these.
Along with HttpServlet
,
javax.servlet.http
also includes subclasses of the
ServletRequest
and ServletResponse
objects, namely:
HttpServletRequest
and
HttpServletResponse
. These 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
just show them in the context of some examples. As usual, we’ll
start with the simplest example possible.
Here’s our “Hello World” of
servlet land,
HelloClient
:
//file: HelloClient.java import java.io.*; import javax.servlet.ServletException; import javax.servlet.http.*; public class HelloClient extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // must come first response.setContentType("text/html"); PrintWriter out = response.getWriter( ); out.println( "<html><head><title>Hello Client</title></head><body>" + "<h1> Hello Client </h1>" + "</body></html>" ); out.close( ); } }
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”. We get the output writer from our
HttpServletResponse
parameter using the
getWriter( )
method and print the message to it.
Then we close the stream to indicate that we are done generating
output.
Before fetching the output stream and writing to it, however,
it’s very important that we specify what 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. But in
general, it’s possible for a servlet to generate any kind of
data, including sound, video, or some other kind of text. If we were
writing a generic FileServlet
that serves 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.
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 is how your web browser is able 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
)
that information is also used by the servlet engine to set the
character encoding of the PrintWriter
output
stream. So you should call the setContentType( )
method before fetching the writer with the getWriter( )
method.
Our first
example
shows how to accept a basic request. You can imagine how we might do
arbitrary processing, database queries, etc., to generate an
interesting response. Of course, to do anything really useful we are
going to have to get some information from the user. Fortunately, the
servlet engine handles this for us, interpreting both
GET
- and POST
-encoded form
information from the client and providing it to us through the simple
getParameter( )
method of the servlet request.
There are essentially
two ways to pass information from your
web browser to a servlet
or CGI program. The most general is to “post” it, which
means 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 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 will append the parameters to
the end of the URL string in an encoded way and the server will
decode them and pass them to the application.
As we described earlier in this chapter, GET
-style
encoding says that you take the parameters and append 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 (like
spaces, ?
, and &
in the
string) are specially encoded.
A less sophisticated form of encoding 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 simply hands it 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 subsequently given the second URL,
the server would still invoke MyServlet
, but would
consider /foo/bar
to be “extra path”
that could be retrieved through the servlet request
getExtraPath( )
method.
Both GET
and POST
encoding can be used with
HTML forms on the client,
simply 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 will handle the decoding.
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 never visible to the user and ceases to exist after
it’s sent to the server. This behavior goes along with the
protocol’s perspective that GET
and
POST
are intended 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 consequential operations (such as
making an e-commerce purchase). In theory, that’s the job of
POST
. That’s why your web browser warns you
about “re-posting form data” if you hit reload on a page
that was the result of a form posting.
The extra path method is not useful for form data, but would be useful if you wanted to make a servlet that retrieves files or handles a range of URLs not driven by forms.
Our first
example didn’t
do anything interesting. This 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:
//file: ShowParameters.java import java.io.*; import javax.servlet.ServletException; import javax.servlet.http.*; import java.util.Enumeration; public class ShowParameters extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, 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>"); for ( Enumeration e=request.getParameterNames( ); e.hasMoreElements( ); ) { String name = (String)e.nextElement( ); String value = request.getParameter( name ); if (! value.equals("") ) out.println("<li>"+ name +" = "+ value ); } out.close( ); } }
There’s not much new here. As in the first example, we override
the doGet( )
method. Here, we delegate the request
to a helper method that we’ve created, called
showRequestParameters( )
. All this method does is
enumerate the parameters using the request object’s
getParameterNames( )
method and print the names and values. (To make it pretty,
we’ve listed them in an HTML list by prepending each with an
<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 simply override the doPost( )
method. The
implementation of doPost( )
could simply call our
showRequest-Parameters( )
method as well, 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 it will get the data. We use the
getRequestURI( )
method to ask for 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 that you couldn’t do easily with your average CGI script. Next, we’ll show some more interesting stuff: how to manage a user session.
One of the nicest features of the servlet API is that it provides a 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. Providing continuity through a series of web pages is important in many kinds of applications, like providing 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 engine; you 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 some 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. First, we’ll talk about cookies that live only for the duration of a typical user session (although it is possible to use cookies to store state across multiple user visits).
URL rewriting appends the 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.
To the servlet programmer, the state information is made available
through an HttpSession
object, which acts like a hashtable for
storing whatever 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 in a session.
//file: ShowSession.java 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 { response.setContentType("text/html"); PrintWriter out = response.getWriter( ); out.println( "<html><head><title>Show Session</title></head><body>"); HttpSession session = request.getSession( ); out.println("<h1>In this session:</h1><ul>"); String [] names = session.getValueNames( ); for (int i=0; i< names.length; i++) out.println( "<li>"+names[i]+" = "+session.getValue( names[i] )); // add new name-value to session String name = request.getParameter("Name"); if ( name != null ) { String value = request.getParameter("Value"); session.putValue( name, value ); } 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\"></form>" ); out.close( ); } }
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 refers 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
parameter using getSession( )
. The HttpSession
object supplied by
the request functions like a hashtable. There is a putValue( )
method, which takes a string name and an
Object
argument and a corresponding
getValue( )
method. In our example, we use the
getValueNames( )
method to enumerate the values
currently stored in the session and print them.
By default, getSession( )
creates a session if one does not yet 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. This method returns
null
if there is no session yet.
Next, we’ll 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 check-out time:
//file: ShoppingCart.java import java.io.*; import javax.servlet.ServletException; import javax.servlet.http.*; import java.util.Enumeration; public class ShoppingCart extends HttpServlet { // from your database 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.getValue("purchases"); if ( purchases == null ) { purchases = new int [ items.length ]; session.putValue( "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, purchases, request.getRequestURI( ) ); } showPurchases( out, purchases ); out.close( ); } void addPurchases( HttpServletRequest request, int [] purchases ) { for (int i=0; i<items.length; i++) { String added = (String)request.getParameter( items[i] ); if ( !added.equals("") ) purchases[i] += Integer.parseInt( added ); } } void doForm( PrintWriter out, int [] purchases, 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>" ); } }
First we should point out 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.
Next, 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. Here 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 check-out,
and one for clearing the cart. In each case we show the user what his
or her purchases are. Depending on the button chosen in the form, we
either add new purchases, clear the list, or simply show the results
as a check out 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 have 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, getValue( )
gives us a null array that we can then populate. Since we
generate our 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.[39] Finally,
showPurchases( )
simply 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-lived kinds of user tracking or identification 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:
//file: CookieCutter.java import java.io.*; import java.text.*; import java.util.*; 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.print("<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>"); out.close( ); } }
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 3600 seconds, so it
will remain in a web browser for one hour before being discarded. You
can specify an arbitrary time period here, or a negative time period
to indicate that the cookie should not be stored persistently on the
client.
Two other methods of Cookie
are of interest:
setDomain( )
and setPath( )
.
These allow you to specify the domain name and
path component that limits the
servers to which 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.) The default
domain is the domain of the server sending the cookie. (You may not
be able to 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 server by setting this
parameter manually.
[39] We also test for the value being equal to the null string, because some web browsers send empty strings for all field values.
Get Learning Java 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.