Book: JavaServer Pages™, 2nd Edition
Section: Chapter 10.  Sharing Data Between JSP Pages, Requests, and Users



10.2 Sharing Session and Application Data

The request scope makes data available to multiple pages processing the same request. But in many cases, data must be shared over multiple requests.

Imagine a travel agency application. It's important to remember the dates and destination entered to book the flight so that the customer doesn't have to reenter the information when it's time to make hotel and rental car reservations. This type of information, available only to requests from the same user, can be shared through the session scope.

Some information is needed by multiple pages independent of who the current user is. JSP supports access to this type of shared information through the application scope. Information saved in the application scope by one page can later be accessed by another page, even if the two pages were requested by different users. Examples of information typically shared through the application scope are database connection pool objects, information about currently logged-in users, and cache objects that avoid unnecessary database queries for data that is the same for all users.

Figure 10-4 shows how the server provides access to the two scopes for different clients.

Figure 10-4. Session and application scopes
figs/Jsp2_1004.gif

The upcoming examples in this chapter will help you to use the session and application scopes.

10.2.1 Session Tracking Explained

Keeping track of which requests come from the same user isn't as easy as it may look. As described in Chapter 2, HTTP is a stateless, request-response protocol. What this means is that the browser sends a request for a web resource; the web server processes the request and returns a response. The server then forgets this transaction ever happened. So when the same browser sends a new request; the web server has no idea that this request is related to the previous one. This is fine as long as you're dealing with static files, but it's a problem in an interactive web application.

There are two ways to solve this problem, and they have both been used extensively for web applications with a variety of server-side technologies. The server can either return all information related to the current user (the client state) with each response and let the browser send it back as part of the next request, or it can save the state somewhere on the server and send back only an identifier that the browser returns with the next request. The identifier is then used to locate the state information saved on the server.

In both cases, the information can be sent to the browser in one of three ways:

  • As a cookie

  • Embedded as hidden fields in an HTML form

  • Encoded in the URLs in the response body, typically as links to other application pages (this is known as URL rewriting)

Figure 10-5 outlines these methods.

Figure 10-5. Client state information transportation methods
figs/Jsp2_1005.gif

A cookie is a name/value pair that the server passes to the browser in a response header. The browser stores the cookie for the time specified by the cookie's expiration time attribute. When the browser sends a request to a server, it checks its "cookie jar" and includes all cookies it has received from the same server (that have not yet expired) in the request headers. Cookies used for state management don't have an expiration time and expire as soon as the user closes the browser. Using cookies is the easiest way to deal with the state issue, but some browsers don't support cookies. In addition, a user may disable cookies in a browser that does support them because of privacy concerns. Hence, we can't rely on cookies alone.

If hidden fields in an HTML form are used to send the state information to the browser, the browser returns the information to the server as regular HTTP parameters when the form is submitted. When the state information is encoded in URLs, it's returned to the server as part of the request URL path, for instance when the user clicks on an encoded link.

Sending all state information back and forth between the browser and server isn't efficient, so most modern server-side technologies keep the information on the server and pass only an identifier between the browser and the server. This is called session tracking; all requests from a browser that contains the same identifier (session ID) belong to the same session, and the server keeps track of all information associated with the session.

JSP hides all details of cookie-based session tracking and supports the URL rewriting variety with a bit of help from the page author. In addition, the specification allows a container to use the session mechanism built into the Secure Socket Layer (SSL), the encryption technology used by HTTPS. SSL-based session tracking is currently not supported by any of the major servlet containers, but all of them support the cookie and URL rewriting techniques. No matter which mechanism is used, session data is always available to JSP pages through the session scope.[2] Information saved in the session scope is available to all pages requested by the same browser during the lifetime of a session.

[2] Unless the page directive session attribute is set to false -- see Appendix A for details.

A session starts when the browser makes the first request for a JSP page in a particular application. The application can explicitly end the session (for instance when the user logs out or completes a transaction), or the JSP container can end it after a period of user inactivity (the default value is typically 30 minutes after the last request). Note that there's no way for the server to tell if the user closes the browser, because there's no permanent connection between the browser and the server, and no message is sent to the server when the browser disappears. Still, closing the browser usually means losing the session ID; the cookie expires, or the encoded URLs are no longer available. So when the user opens a browser again, the server can't associate the new request with the previous session, and therefore creates a new session. However, all session data associated with the previous session remains on the server until the session times out.

10.2.2 Counting Page Hits

A simple page counter can be used to illustrate how the scope affects the lifetime and reach of shared information. The difference between the session and application scopes becomes apparent when you place a counter in each scope. Consider the page shown in Example 10-4.

Example 10-4. A page with counter beans (counter1.jsp)
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
  
<html>
  <head>
    <title>Counter page</title>
  </head>
  <body bgcolor="white">
  
    <%-- Increment counters --%>
    <c:set var="sessionCounter" scope="session"
      value="${sessionCounter + 1}" />
    <c:set var="applCounter" scope="application"
      value="${applCounter + 1}" />
    <h1>Counter page</h1>
  
    This page has been visited <b>
    <c:out value="${sessionCounter}" />
    </b> times within the current session, and <b>
    <c:out value="${applCounter}" />
    </b> times by all users since the application was started.
  </body>
</html>

In Example 10-4, JSTL <c:set> actions increment counters in the session and application scopes. Note how each counter variable is placed in a specific scope using the scope attribute. The variable placed in the session scope is found every time the same browser requests this page, and therefore counts hits per browser. The application scope variable, on the other hand, is shared by all users, so it counts the total number of hits for this page. If you run this example, you should see a page similar to Figure 10-6.

Figure 10-6. A page with session and application page hit counters
figs/Jsp2_1006.gif

The first time you access the page, none of the counter variables exist, so the <c:set> actions create them and set them to 1 (the EL interprets a missing variable as 0 when it's used in an arithmetic operation). As long as you use the same browser, the session and application counters stay in sync. If you exit your browser and restart it, however, a new session is created when you access the first page. The session counter starts from 1 again but the application counter takes off from where it was at the end of the first session.

Note that the counter variables are stored in memory only, so if you restart the server, both counters are reset.

Sessions and Multiple Windows

Even though session tracking lets an application recognize related requests, there's still one problem. This problem is related to the server's lack of knowledge of the client, and doesn't become obvious until you start testing an application that depends on session information. Consider what happens if you open two browser windows and start accessing the same web application. Will each window be associated with its own session, or will they share the same session? Unfortunately there's not a clear answer. And it doesn't matter if the server-side logic is implemented as servlets, JSP, ASP, CGI, or any other server-side technology.

The most commonly used browsers, Netscape Navigator and Microsoft Internet Explorer (IE), both let you open multiple windows that are actually controlled by the same operating system process. Older versions of IE (before Version 5) can be configured so that a separate process controls each window instead, and on operating systems other than Windows, you can do this with any browser. When each window runs in its own process, it's easy to answer the question: each window is associated with its own session. It's only when one process controls multiple windows that it gets a bit tricky; in this case, the answer depends on whether URL rewriting or cookies are used for session tracking.

When URL rewriting is used, the first request to the application from one window doesn't include a session ID, because no response with the session ID has been received yet. The server sends back the new session ID encoded in all URLs in the page. If a request is then submitted from the other window, the same thing happens; the server sends back a response with a new session ID. Hence, in this scenario each window is associated with a separate session.

If cookies are used to pass the session ID, the reverse is true. The first request submitted from one window doesn't contain a session ID, so the server generates a new ID and sends it back as a cookie. Cookies are shared by all windows controlled by the same process. When a request is then made from the other window, it contains the session ID cookie received as a result of the first request. The server recognizes the session ID and therefore assumes that the request belongs to the same session as the first request; both windows share the same session.

There's not much you can do about this. If you want each window to have its own session, most servers can be configured to always use the URL rewriting method for session tracking. But this is still not foolproof. The user can open a new window using the mouse pop-up menu for a link (with the session ID encoded in the URI) and ask to see the linked page in a new window. Now there are two windows with the same session ID anyway. The only way to handle this is, unfortunately, to educate your users.

10.2.3 URL Rewriting

As I mentioned earlier, the session ID needed to keep track of requests within the same session can be transferred between the server and the browser in a number of different ways. One way is to encode it in the URLs created by the JSP pages. This is called URL rewriting. It's an approach that works even if the browser doesn't support cookies (perhaps because the user has disabled them). A URL with a session ID looks like this:

counter2.jsp;jsessionid=be8d691ddb4128be093fdbde4d5be54e00

When the user clicks on a link with an encoded URL, the server extracts the session ID from the request URI and associates the request with the correct session. The JSP page can then access the session data in the same way as when cookies keep track of the session ID, so you don't have to worry about how it's handled. What you do need to do, however, is tell the JSP container to encode the URL when needed. To see how it's done, let's add HTML links in the counter page -- one link without rewriting and one with. Example 10-5 shows a counter page with this addition.

Example 10-5. A page with links, with and without URL rewriting (counter2.jsp)
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
  
<html>
  <head>
    <title>Counter page</title>
  </head>
  <body bgcolor="white">
  
    <%-- Increment the counter --%>
    <c:set var="sessionCounter" scope="session"
      value="${sessionCounter + 1}" />
  
    <h1>Counter page</h1>
  
    This page has been visited <b>
    <c:out value="${sessionCounter}" />
    </b> times within the current session.
    <p>
    Click here to load the page through a
    <a href="counter2.jsp">regular link</a>.
    <p>
    Click here to load the page through an
    <a href="<c:url value="counter2.jsp" />">encoded link</a>.
  </body>
</html>

The only differences compared to Example 10-4 are that only the session counter is used and that links back to the same page have been added.

The <a> element's href attribute value for the second link is converted using the JSTL <c:url> action, described in Table 10-3. If the container has received a session ID cookie with the request for the page, the action adds the URL untouched to the response. But for the first request in a session and for requests from a browser that doesn't support cookies or with cookie support disabled, this action adds a rewritten URL, with the session ID added to the URL as shown earlier.

Table 10-3. Attributes for JSTL <c:url>

Attribute name

Java type

Dynamic value accepted

Description

value
String

Yes

Mandatory. An absolute URL, or a context- or page-relative path to encode.

context
String

Yes

Optional. The context path for the application, if the resource is n't part of the current application.

var
String

No

Optional. The name of the variable to hold the encoded URL.

scope
String

No

Optional. The scope for the variable, one of page, request, session, or application. page is the default.

The <c:url> action also encodes query string parameters defined by nested <c:param> actions (see Table 10-4) according to the syntax rules for HTTP parameters:

<c:url value="product.jsp">
  <c:param name="id" value="${product.id}" />
  <c:param name="customer" value="Hans Bergsten" />
</c:url>

Recall that all special characters, such as space, quote, etc., in a parameter value must be encoded. For instance, all spaces in a parameter value must be replaced with plus signs. When you use the <c:param> action, it takes care of all encoding for the parameters, but in the rare event that the URL specified as the <c:url> value attribute contains special characters, you must replace them yourself. The encoded URL created by the action for this example looks something like this:

product.jsp;jsessionid=be8d691ddb4128be0?id=3&customer=Hans+Bergsten

Here, the session ID and the request parameters are added, and encoded if needed (the space between "Hans" and "Bergsten" is replaced with a plus sign).

If you're sure that the parameter values never contain special characters that need encoding (or are easy to encode manually in a static value), you can include them as a query string in the <c:url> value instead of using nested <c:param> actions:

<c:url value="product.jsp?id=${product.id}&customer=Hans+Bergsten">

Table 10-4. Attributes for JSTL <c:param>

Attribute name

Java type

Dynamic value accepted

Description

name
String

Yes

Mandatory. The parameter name.

value
String

Yes

Mandatory, unless the value is provided as the body instead. The parameter value.

If you want to provide session tracking for browsers that don't support cookies, you must use the <c:url> action to rewrite all URL references in your application: in <a> tags, <form> tags, and <frameset> tags. This means all pages in your application (or at least all pages with references to other pages) must be JSP pages, so that all references can be dynamically encoded. If you miss one single URL, the server will lose track of the session.

I recommend that you spend the time to add <c:url> actions for all references up front, even if you know that all your current users have browsers that support cookies. One day you may want to extend the user base and may lose control over the browsers they use. It's also common that users disable cookies in fear of Big Brother watching. Yet another reason to prepare for URL rewriting from the beginning is to support new types of clients that are becoming more and more common, such as PDAs and cell phones. Cookie support in these small devices isn't a given.

Besides URL encoding, the <c:url> action also converts a context-relative path into a server-relative path, suitable for use in an HTML element. What this means is that all you have to do to refer to a file that's located in a top-level directory for the application from an HTML element is to use <c:url> to convert it to a path the browser interprets correctly. Here's how you can add an image located in the /images directory for the application from any JSP page, no matter how deep in the directory structure it's located:

<img src="<c:url value="/images/logo.gif" />">

For an application installed with the context path /example, the result of processing this snippet is:

<img src="/example/images/logo.gif">

Note how the context path has been prepended to the context-relative path specified as the attribute value. A browser needs this type of server-relative path because it doesn't know anything about contexts or how to handle context-relative paths; these are concepts only the container knows about.

    [http://safari.oreilly.com/059600317X/jserverpages2-CHP-10-SECT-2]


    Copyright © 2002 O'Reilly & Associates, Inc. All rights reserved.
    1005 Gravenstein Highway North
    Sebastopol, CA 95472