Session tracking is an essential part of most web applications. By nature, the HTTP protocol is connectionless. This means that each time users click on a hyperlink or submit an XHTML form, the browser establishes a new connection to the web server. Once the request is sent and the response is received, the connection between browser and server is broken.
This presents a problem for servlet authors. Although the browser and web server do not maintain a persistent connection between page views, applications must maintain state information for each user. Stateful applications make technologies like shopping carts possible, for instance. With each request from the browser, the servlet must reestablish the identity of the user and locate his session information.
The traditional servlet approach to session tracking utilizes the javax.servlet.http.HttpSession interface. This interface allows a web application to store information about a user that persists across page requests. The interface is easy to use, mapping attribute names to attribute values. The code shown here is part of a servlet that uses HttpSession:
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // retrieve an instance of HttpSession for this user. The "true" parameter // indicates that the object should be created if it does not exist. HttpSession session = req.getSession(true); // retrieve the cart for this user Cart cart = (Cart) session.getAttribute("shoppingCart"); if (cart == null) { cart = new Cart( ); session.setAttribute("shoppingCart", cart); } ... }
The first line of the doGet( ) method locates the HttpSession instance associated with the current user. The true parameter indicates that a new session should be created if one does not already exist. Once the session is created, a Cart object can be retrieved using HttpSession's getAttribute( ) method.
Browser cookies provide the standard method of implementing HttpSession. A cookie is a small piece of information stored on the client machine and generally contains a randomly generated sequence of characters that uniquely identifies a user. When the browser issues a request to the servlet, the servlet looks for a cookie named jsessionid and uses its value to locate an instance of HttpSession. Figure 8-3 illustrates the normal session-tracking model.
Cookies are a mixed blessing. Although they make session tracking very easy to implement, this leads to security concerns because people do not want their browsing habits monitored. Therefore, quite a few people set their browsers to disable all cookies. When users disable cookies, servlets must use another technique to enable session tracking.
The standard servlet API has a fallback mechanism when cookies are disabled. It reverts to a technique called URL rewriting. If cookies are disabled, the session identifier is appended to the URL. This way, whenever a user clicks on a hyperlink or submits an XHTML form, the session identifier is transmitted along with the request. This cannot happen without some level of programmer intervention, however. Imagine a scenario where a servlet is requested, and it returns an XHTML page with the following content:
Click on the link to move next: <a href="/shopping/moveNext"/>Move Next</a>
This causes session tracking to fail, because the session identifier is lost whenever the user clicks on the hyperlink. We really want the HTML to look like this:
Click on the link to move next: <a href="/shopping/moveNext;jsessionid=0e394s8a576f67b38s7"/>Move Next</a>
Now, when the user clicks on the hyperlink, the session identifier (jsessionid) is transmitted to the servlet as part of the requested URL.
The value for jsessionid cannot be hardcoded. It must be dynamically generated for each instance of HttpSession, making it much harder for hackers to obtain session identifiers to impersonate users.[37] This means that the XHTML cannot be entirely static; the session identifier must be dynamically inserted into the XHTML whenever a link or form action is required. HttpServletResponse has a method called encodeURL( ) that makes this possible:
[37] Sessions and their associated identifiers typically expire after 30 minutes of inactivity and must be regenerated.
String originalURL = "/shopping/moveNext"; String encodedURL = response.encodeURL(originalURL);
Now, encodedURL will be encoded with the session id if the jsessionid cookie was not found. For session tracking to work, this technique must be used consistently for every hyperlink and form action on a web site.
With XSLT, session tracking is a little harder because the stylesheet generates the URL rather than the servlet. For instance, a stylesheet might contain the following code:
<xsl:template match="cart"> Click on the link to move next: <a href="/shopping/moveNext"/>Move Next</a> ... </xsl:template>
Like before, the jsessionid needs to be concatenated to the URL. To make this happen, the following steps must be performed:
In the servlet, determine if cookies are enabled or disabled.
If cookies are disabled, get the value of jsessionid.
Pass ;jsessionid=XXXX as a parameter to the XSLT stylesheet, where XXXX is the session identifier.
In the stylesheet, append the session id parameter to all URLs in hyperlinks and form actions.
If cookies are enabled, there is no reason to manually implement session tracking. This is easy to check because the javax.servlet.http.HttpServletRequest interface provides the isRequestedSessionIdFromCookie( ) method. When this method returns true, cookies are enabled, and the remaining steps can be ignored. The code in Example 8-5 shows what a servlet's doGet( ) method looks like when implementing session tracking.
public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { try { // retrieve current settings from the session HttpSession session = req.getSession(true); Cart cart = (Cart) session.getAttribute("shoppingCart"); if (cart == null) { cart = new Cart( ); session.setAttribute("shoppingCart", cart); } // produce the DOM tree Document doc = CartDOMProducer.createDocument(cart); // prepare the XSLT transformation Transformer trans = StylesheetCache.newTransformer( this.xsltFileName); // allow cookieless session tracking if (!req.isRequestedSessionIdFromCookie( )) { String sessionID = session.getId( ); trans.setParameter("global.sessionID", ";jsessionid=" + sessionID); } // send the web page back to the user res.setContentType("text/html"); trans.transform(new javax.xml.transform.dom.DOMSource(doc), new StreamResult(res.getWriter( ))); } catch (ParserConfigurationException pce) { throw new ServletException(pce); } catch (TransformerConfigurationException tce) { throw new ServletException(tce); } catch (TransformerException te) { throw new ServletException(te); } }
The critical lines of code are emphasized. The first of these checks to see if the session was not obtained using a cookie:
if (!req.isRequestedSessionIdFromCookie( )) {
For the very first request, the cookie will not be present because the servlet has not had a chance to create it. For all subsequent requests, the cookie will be missing if the user has disabled cookies in the browser. Under either scenario, the session identifier is obtained from the HttpSession instance:
String sessionID = session.getId( );
The servlet API takes care of generating a random session identifier; you are responsible for preserving this identifier by passing it as a parameter to the stylesheet. This is done as follows:
trans.setParameter("global.sessionID", ";jsessionid=" + sessionID);
This servlet also takes the liberty of prefixing the session identifier with ";jessionid=". This makes the XSLT stylesheet simpler, because it does not have to check if the session ID is an empty string or not. As implemented here, the value of global.sessionID can be appended to all URLs:
<a href="/whatever{$global.sessionID}">click here</a>
The end result is that if cookies are enabled, the URL will be unaffected. Otherwise, it will be properly encoded to use session tracking. A larger XSLT example follows in Example 8-6.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- ********************************************************************* ** global.sessionID : Used for URL-rewriting to implement ** session tracking without cookies. ******************************************************************--> <xsl:param name="global.sessionID"/> <!-- This stylesheet produces XHTML --> <xsl:output method="xml" indent="yes" encoding="UTF-8" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/> <!-- ********************************************************************* ** This template produces the skeletal XHTML document. ******************************************************************--> <xsl:template match="/"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Shopping Example</title> </head> <body> <!-- Create a form for this page --> <form method="post" action="/chap8/shopping{$global.sessionID}"> <h1>Shopping Example</h1> ...remainder of page omitted </form> </body> </html> </xsl:template> <xsl:template match="cart"> Click on the link to move next: <a href="/shopping/moveNext{$global.sessionID}?param=value"/>Move Next</a> ... </xsl:template> </xsl:stylesheet>
This stylesheet fully illustrates the three key components that make session tracking with XSLT possible. First, the session identifier is passed to the stylesheet as a parameter:
<xsl:param name="global.sessionID"/>
Next, this session identifier is used for the form action:
<form method="post" action="/chap8/shopping{$global.sessionID}">
And finally, it is used for all hyperlinks:
<a href="/shopping/moveNext{$global.sessionID}?param=value"/>Move Next</a>
The ?param=value string was added here to illustrate that request parameters are appended after the session identifier. Therefore, the full URL will look similar to the following when the user clicks on the hyperlink:
http://localhost:8080/shopping/moveNext;jsessionid=298ulkj2348734jkj43?param=value
Tracking sessions is essential, and the technique shown in this section works when browser cookies are disabled. You should always test your web applications by disabling all browser cookies to see if every URL is properly encoded with the session identifier.
Copyright © 2002 O'Reilly & Associates. All rights reserved.