Very few web applications are confined to a single page, so having a mechanism for tracking users through a site can often simplify application development. The Web, however, is an inherently stateless environment. A client makes a request, the server fulfills it, and both promptly forget about each other. In the past, applications that needed to deal with a user through multiple pages (for instance, a shopping cart) had to resort to complicated dodges to hold onto state information, such as hidden fields in forms, setting and reading cookies, or rewriting URLs to contain state information.
Fortunately, Version 2.0 of the Servlet API provides classes and methods specifically designed to handle session tracking. A servlet can use the session-tracking API to delegate most of the user-tracking functions to the server. The first time a user connects to a session-enabled servlet, the servlet simply creates a javax.servlet.http.HttpSession object. The servlet can then bind data to this object, so subsequent requests can read the data. After a certain amount of inactive time, the session object is destroyed.
A servlet uses the getSession() method of HttpServletRequest to retrieve the current session object. This method takes a single boolean argument. If you pass true, and there is no current session object, the method creates and returns a new HttpSession object. If you pass false, the method returns null if there is no current session object. For example:
HttpSession thisUser = req.getSession(true);
When a new HttpSession is created, the server assigns a unique session ID that must somehow be associated with the client. Since clients differ in what they support, the server has a few options that vary slightly depending on the server implementation. In general, the server's first choice is to try to set a cookie on the client (which means that getSession() must be called before you write any other data back to the client). If cookie support is lacking, the API allows servlets to rewrite internal links to include the session ID, using the encodeUrl() method of HttpServletResponse. This is optional, but recommended, particularly if your servlets share a system with other, unknown servlets that may rely on uninterrupted session tracking. However, this on-the-fly URL encoding can become a performance bottleneck because the server needs to perform additional parsing on each incoming request to determine the correct session key from the URL. (The performance hit is so significant that the Java Web Server disables URL encoding by default.)
To use URL encoding, you have to run all your internal links through encodeUrl(). Say you have a line of code like this:
out.println("<A HREF=\"/servlet/CheckoutServlet\">Check Out</A>");
You should replace it with:
out.print("<A HREF=\""); out.print(resp.encodeUrl("/servlet/CheckoutServlet"); out.println("\">Check Out</A>");
JWS, in this case, adds an identifier beginning with $ to the end of the URL. Other servers have their own methods. Thus, with JWS, the final output looks like this:
<A HREF="/servlet/CheckoutServlet$FASEDAW23798ASD978">CheckOut</A>"
In addition to encoding your internal links, you need to use encodeRedirectUrl() to handle redirects properly. This method works in the same manner as encodeUrl(). Note that in Version 2.1 of the Servlet API, both methods have been deprecated in favor of identical methods that use the more standard "URL" in their names: encodeURL() and encodeRedirectURL().
You can access the unique session ID via the getID() method of HttpSession. This is enough for most applications, since a servlet can use some other storage mechanism (i.e., a flat file, memory, or a database) to store the unique information (e.g., hit count or shopping cart contents) associated with each session. However, the API makes it even easier to hold onto session-specific information by allowing servlets to bind objects to a session using the putValue() method of HttpSession. Once an object is bound to a session, you can use the getValue() method.
Objects bound using putValue() are available to all servlets running on the server. The system works by assigning a user-defined name to each object (the String argument); this name is used to identify objects at retrieval time. In order to avoid conflicts, the general practice is to name bound objects with names of the form applicationname.objectname. For example:
session.putValue("myservlet.hitcount", new Integer(34));
Now that object can be retrieved with:
Integer hits = (Integer)session.getValue("myservlet.hitcount")
Example 5-7 demonstrates a basic session-tracking application that keeps track of the number of visits to the site by a particular user. It works by storing a counter value in an HttpSession object and incrementing it as necessary. When a new session is created (as indicated by isNew(), which returns true if the session ID has not yet passed through the client and back to the server), or the counter object is not found, a new counter object is created.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; public class VisitCounterServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); resp.setContentType("text/html"); HttpSession thisUser = req.getSession(true); Integer visits; if(!thisUser.isNew()) { //Don't check newly created sessions visits = (Integer)thisUser.getValue("visitcounter.visits"); if(visits == null) visits = new Integer(1); else visits = new Integer(visits.intValue() + 1); } else visits = new Integer(1); // Put the new count in the session thisUser.putValue("visitcounter.visits", visits); // Finally, display the results and give them the session ID too out.println("<HTML><HEAD><TITLE>Visit Counter</TITLE></HEAD>"); out.println("<BODY>You have visited this page " + visits + " time[s]"); out.println("since your last session expired."); out.println("Your Session ID is " + thisUser.getId()); out.println("</BODY></HTML>"); } }
Sometimes it is useful to know when an object is getting bound or unbound from a session object. For instance, in an application that binds a JDBC java.sql.Connection object to a session (something that, by the way, is ill-advised in all but very low traffic sites), it is important that the Connection be explicitly closed when the session is destroyed.
The javax.servlet.http.HttpSessionBindingListener interface handles this task. It includes two methods, valueBound() and valueUnbound(), that are called whenever the object that implements the interface is bound or unbound from a session, respectively. Each of these methods receives an HttpSessionBindingEvent object that provides the name of the object being bound/unbound and the session involved in the action. Here is an object that implements the HttpSessionBindingListener interface in order to make sure that a database connection is closed properly:
class ConnectionHolder implements HttpSessionBindingListener { java.sql.Connection dbCon; public ConnectionHolder(java.sql.Connection con) { dbCon = con; } public void valueBound(HttpSessionBindingEvent event) { // Do nothing } public void valueUnbound(HttpSessionBindingEvent event) { dbCon.close(); } }
Version 2.0 of the Servlet API included the getContext() method of HttpSession, coupled with an interface named HttpSessionContext. Together, these allowed servlets to access other sessions running in the same context. Unfortunately, this functionality also allowed a servlet to accidentally expose all the session IDs in use on the server, meaning that an outsider with knowledge could spoof a session. To eliminate this minor security risk, the session context functionality has been deprecated in Version 2.1 of the Servlet API.
Copyright © 2001 O'Reilly & Associates. All rights reserved.