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.
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.
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. Information
saved in the session scope is available to all pages requested
by the same browser during the lifetime of a session.
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.
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.
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>
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>
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.
|