Section 15.2. Web Applications


15.2. 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 have to be more precise with that term. In the context of the Java Servlet API, a web application is a collection of servlets, supporting 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 essentially JAR archives containing 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 all the application's partsits Java classes, resources, and configurationis standardized.

At the heart of the WAR archive is the web.xml file. This XML configuration file describes which servlets and JSPs are to be deployed, their names and URL paths, their initialization parameters, and a host of other information, including security and authentication requirements.

Web applications, or web apps, also have a very 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., 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 address security and reliability concerns. Web apps are intended to be coarse-grained, relatively complete applicationsnot 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.

15.2.1. The Servlet Life Cycle

Let's jump ahead to the Servlet API itself so that we can get started building servlets right away. 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, almost exactly paralleling the Applet API. There are three life-cycle methodsinit( ), service( ), and destroy( )along with some methods for getting configuration parameters and servlet resources. Before a servlet is used the first time, it's initialized by the container 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.

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 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 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 in detail in the examples.

15.2.2. Servlets

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 as if all servlets were HTTP-related. In theory, you can write servlets for other protocols, but 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 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 web server 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 are the most common use for 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. These aren't widely used.

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 need to override these.

Along with HttpServlet, javax.servlet.http also includes subclasses of the ServletRequest and ServletResponse objects, HttpServletRequest and HttpServletRe-sponse. 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.

15.2.3. The HelloClient Servlet

Here's our servlet version of "Hello, World," 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(  );         }     } 

If you want to try this servlet right away, skip ahead to "WAR Files and Deployment," where we walk through the process of running this servlet. Simply package up the servlet class file along with a simple web.xml file that describes it and place it on your server. But for now, we're going to discuss just the servlet example code itself, which is pretty simple.

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. Then, we get the output stream using the getWriter( ) method and print the message to it. Finally, we close the stream to indicate we're done generating output. (It shouldn't strictly be necessary to close the output stream, but we show it for completeness.)

15.2.3.1 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 when a ServletException is thrown, but often during development 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" later in this chapter for details.)

Alternatively, a servlet may throw an UnavailableException, a subclass of ServletException, to indicate that it cannot handle requests. This exception can be constructed to indicate that the condition is permanent or that it should last for a specified period of seconds.

15.2.3.2 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 sound, video, or some other kind of text. 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. 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. As of Java 5.0, the character encoding can also be set separately, via the servlet response object's setCharacterEncoding( ) method.

15.2.4. 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" later in this chapter.)

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.

15.2.5. Servlet Parameters

Our first example shows how to accept a basic request. Of course, to do anything really useful, we'll need to get some information from the user. 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.

15.2.5.1 GET, POST, and the "extra path"

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, 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 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 still invokes MyServlet, but considers /foo/bar to be an "extra path" that can be retrieved through the servlet request getExTRaPath( ) method.

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 the same as that for any CGI: "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 in the input stream and can be read by the servlet directly.

15.2.5.2 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 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 persistent 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 reposting form data again if you hit reload on a page that was the result of a form posting.

The extra path method is not used for form data but would be useful for a servlet that retrieves files or handles a range of URLs in a human-readable way not driven by forms. Extra path information is often useful for URLs that the user must see or remember, because it looks like any other path.

15.2.6. The ShowParameters Servlet

Our first example didn't do anything interesting. 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:

     //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. 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 getParameterNames( ) method and prints the names and values. (To make it pretty, we listed them in an HTML list by prefixing 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 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 the servlet 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 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 example above.

15.2.6.1 SnoopServlet

Most servlet containers come with some useful servlets that serve as examples or debugging aids. One of the most basic tools you have for debugging servlets is the "SnoopServlet." We place that name in quotes because you will find many different implementations of this with various names. But the original SnoopServlet came with the Java servlet development kit and an equivalent is supplied with the Tomcat server distribution. This very simple debugging tool displays everything about its environment and the incoming request, including all of the request parameters, just as our ShowParameters example did and more. There is a lot of useful information there. In the default Tomcat 4.x distribution, you can access the snoop servlet at http://myserver:8080/examples/snoop. In Tomcat 5.x, the servlet has been replaced by a JSP and is available at : http://myserver:8080/jsp-examples/snp/snoop.jsp.

15.2.7. 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 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.

15.2.8. The ShowSession Servlet

Here's a simple servlet that shows how to store some string information to track 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         {             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 should have encoded the server form URL retrieved from getrequestURI( ) before passing it to the client, if we wanted to allow for users without cookies.

15.2.9. 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 that's the sort of thing you'd introduce with proper separation of the presentation and business logic. Here we are just concentrating on the Servlet API;

     //file: ShoppingCart.java     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>" );         }     } 

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 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, getAttribute( ) gives us a null value and we create an empty array to populate. Since 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 broken 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.

15.2.10. 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:

     //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 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 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 manually setting this parameter.

15.2.11. 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 post 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. Use the standard package-naming conventions for your attributes to avoid conflicts. 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 may 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.



    Learning Java
    Learning Java
    ISBN: 0596008732
    EAN: 2147483647
    Year: 2005
    Pages: 262

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net