Java Web Applications

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.

The Servlet Lifecycle

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.

Servlets

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.

The HelloClient Servlet

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.

ServletExceptions

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.

Content type

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.

The Servlet Response

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 setContentLength() method.

Servlet Parameters

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.

GET, POST, and “extra path”

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.

GET or POST: Which one to use?

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.

The ShowParameters Servlet

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.

User Session Management

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.

The ShowSession Servlet

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.

The ShoppingCart Servlet

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.

Cookies

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.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>");
    }
}

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.

The ServletContext API

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.

Asynchronous Servlets

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.