ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Pseudo Sessions for JSP, Servlets and HTTP

by Budi Kurniawan
03/01/2001

Session tracking is a method for maintaining the state of a series of requests from the same user. However, this method is not a perfect solution to many situations, especially if your application needs to scale. This article discusses pseudo sessions and how they can overcome drawbacks in the session tracking method. At the end of the article you will find a project implementing the pseudo session mechanism in a bean that you can use in any JavaServer Pages (JSP) applications.

HTTP is by design a stateless protocol. The implication is Web applications do not have information about previous HTTP requests by the same user. One of the ways to maintain state is to use the session tracking feature from the servlets or JSP container. The servlet API specification defines a simple HttpSession interface that allows a servlet container to use any number of approaches to track a user's session without involving the developer in the nuances of any one approach.

The HttpSession interface

In particular, the HttpSession interface provides methods that store and return standard session properties, such as a session identifier, and application data, which is stored as a name-value pair. In short, the HTTPSession interface provides a seamless way to store an object into memory and then retrieve it when the same user comes back later. The method for storing objects in a session is setAttribute(String s, Object o), and the method for retrieving stored objects in a session is the getAttribute(String s) method.

Also, in the HTTP protocol, there is no explicit termination signal when a client is no longer active. Not having explicit termination and not knowing whether or not a user will come back means there will be a pile of HttpSession objects in memory as new users visits your Web application. Fortunately, the servlet designer has devised a mechanism that can be used to indicate when a client is no longer active: a timeout period. If a user does not come back for a certain period of time, the user's session becomes expired and the corresponding HttpSession object will be removed from memory. The default timeout period for sessions is defined by the servlet container and can be obtained via the getMaxInactiveInterval method. This timeout can be changed by using the setMaxInactiveInterval. The timeout periods used by these methods is defined in seconds. If the timeout period for a session is set to -1, the session will never expire.

The getLastAccessedTime method allows a servlet to determine the last time the session was accessed before the current request. The session is considered accessed when a request that is part of the session is handled by the servlet context.

To get a user's HTTPSession object you can use the getSession method of the HttpServletRequest object. When you call the method with its create argument as true, the servlet reference implementation creates a session if necessary. To properly maintain the session, you must call getSession before any output to response.

The following code demonstrates the use of the HttpSession interface to maintain a user's session. For example, to store a String value "bulbul" associated with userName, you can use the following code from your JSP page.

<%
  HttpSession session = request.getSession();
  String userName = session.getAttribute("userName");
%>

One last thing that's worth mention about the HTTPSession interface is that a user's session can be invalidated manually or, depending on where the servlet is running, automatically. For example, the Java Web Server automatically invalidates a session when there have been no page requests for some period of time, 30 minutes by default. To invalidate a session means to remove the HttpSession object and its values from the system.

Disadvantages of the Session Tracking Mechanism

Session tracking mechanisms come at a price:

To understand the session tracking mechanism, you first must understand how sessions work in a servlet/JSP container.

How Session Objects Work with Session Identifiers

By default, your JSP application participates in the session tracking mechanism. Every time a new user requests a JSP page that uses HTTPSession objects, the JSP container sends back a response plus a special number to the browser. This special number is called a session identifier and is guaranteed as a unique user identifier. The HTTPSession object resides in memory waiting for its methods to be called again when the same user returns.

On the client side, the browser keeps the session identifier and sends it back to the server on the next request. This session identifier tells the JSP container that this request is not a first visit by the user and that a HTTPSession object has been created for this user. Instead of creating a new HTTPSession object, the JSP container then looks for a HTTPSession object with the same session identifier and associates the request with the HTTPSession object.

Session identifiers are transmitted between the server and the browser as cookies. What if the browser doesn't accept cookies? All subsequent requests to the server will not carry a session identifier. As a result, the JSP container will think it is a request from a new user, and it will create a HTTPSession object again, and the previous HTTPSession object remains in memory and the previous state information for that user is lost.

Further a session identifier can only be recognized by the JSP container that issues it. If you have copies of your application installed in more than one machine in a web farm, there must be a way to guarantee that requests from the same user will be directed to the server that first handles the user's request.

Pseudo Sessions

The solution to the problems posed by the cookie-based session tracking mechanism is pseudo sessions, which have the following properties.

In addition, the implementation of pseudo sessions must take into account the following points.

The Project

The project described here is simply called PseudoSession and is a very simple implementation of the pseudo session mechanism. For portability, it is implemented in a Java Bean called PseudoSessionBean. To use pseudo sessions in a JSP application, you need to import this bean into your project and following the instructions below. The code for the bean is given in Listing 1.

The PseudoSessionBean has the following fields:

public String path;
public long timeOut;

path is the directory path where all session text files are stored. This must be an area accessible to all web servers if you are using more than one. The path, however, must not be visible to users in order to prevent them from accessing the file directly. One way to do this is to allocate a directory outside the web root.

timeOut is the time that has to pass after the last request of a user before the session is invalidated. In the code in the Listing, timeOut is set to 20 minutes (1,200,000 milliseconds), a somewhat reasonable value. Any user who comes back after his or her session expires will get a new session identifier.

The PseudoSessionBean has four methods: getSessionID, setValue, getValue, and deleteAllInvalidSessions. These four methods are explained in detail in the following section.

The getSessionID Method

The getSessionID method has the following signature.

public String getSessionID(HttpServletRequest request)

This method should be called at the beginning of every JSP page. It does the following.

Thos method works in the following way.

String sessionId = request.getParameter("sessionId");

A flag is used to indicate whether or not the session identifier is a valid one. Initially the value of this flag is false.

boolean validSessionIdFound = false;

There is a long called now that contains server time when the request occurs. This will be used to determine the validity of the user session.

long now = System.currentTimeMillis();

If a session identifier is found, the method will check its validity by doing the following.

This is done by using the File class that is constructed by passing the path to the session text file:

if (sessionId!=null) {
File f = new File(path + sessionId);
if (f.exists()) {
  if (f.lastModified() + timeOut > now) { // session valid
// with setLastModified, if the file is locked by other apps
// there won't be any exception but the file data does not change
f.setLastModified(now);
validSessionIdFound = true;
  }
  else { // session expired
   // delete the file
f.delete();
  }
} // end if (f.exists)
  } // end if (sessionId!=null)

If a valid session identifier is not found, a session identifier is generated and a corresponding text file is created.

if (!validSessionIdFound) {
  sessionId = Long.toString(now);
  //create a file
  File f = new File(path + sessionId);
  try {
f.createNewFile();
  }
  catch (IOException ioe) {}
} // end of if !validSessionIdFound

A very simple random generator has been created by converting the system time (current) into the session identifier.

sessionId = Long.toString(now);

If your application contains sensitive data, you should consider implementing a more secure random number generator for session identifiers.

getSessionID does not usually return a valid session identifier. This could be the same as the session identifier passed to the method or it could be a newly generated session identifier.

return sessionId;

getSessionID should be invoked at the beginning of every JSP file to ensure that the page has a valid session identifier for URL rewriting (explained in the next section) and for invoking the setValue and getValue methods.

The setValue method

setValue is used to store a String value associated with a String called name. This name-value pair should remind you of a Dictionary. The setValue method also needs a valid session identifier for its first argument. It is assumed that the getSessionID method has been invoked before this method is called and so a validated session identifier is certain to exist. The session identifier passed to this method will not be validated again.

The setValue method effectively does the following.

q

The setValue method stores the name-value pair in the following format.

name-1 value-1
name-2 value-2
name-3 value-3
.
.
.
name-n value-n

Like any other Java applications, name is case-sensitive.

The setValue method has the following signature.

public void setValue(String sessionId, String name, String value)

It first tries to find the corresponding session text file. If the file does not exist, the method will return without doing anything. If the session text file is found, the method will read every line of the text file and compare the line with name. If the line begins with name followed by a white space, it means the name already exists and the value is replaced. If the comparison does not result in a match, the line will simply be copied to the temporary file.

This functionality is achieved by the following code.

try {
  FileReader fr = new FileReader(path + sessionId);
  BufferedReader br = new BufferedReader(fr);

  FileWriter fw = new FileWriter(path + sessionId + ".tmp");
  BufferedWriter bw = new BufferedWriter(fw);

  String s;
  while ((s = br.readLine()) != null)
if (!s.startsWith(name + " ")) {
  bw.write(s); //write the line to the file
  bw.newLine();
}
  bw.write(name + " " + value);
  bw.newLine();

  bw.close();
  br.close();
  fw.close();
  bw.close();

  .
  .
  .

}
catch (FileNotFoundException e) {}
catch (IOException e) { System.out.println(e.toString());}

After all lines are copied into the temporary files, the original session text file is deleted and the temporary file is renamed the session text file.

File f = new File(path + sessionId + ".tmp");
File dest = new File(path + sessionId);
dest.delete();
f.renameTo(dest);

The getValue method

This method allows you to retrieve values that you have stored in the pseudo session. Like the setValue method, this method also requires you to pass a valid session identifier. The session identifier won't be checked again for validity. The second argument is the name that the value you want to retrieve is associated with. The getValue method returns the value associated with name.

The getValue method has the following signature:

public String getValue(String sessionId, String name)

It basically finds the session text file and reads it line-by-line until it finds a match with name. When a match is found, the method returns the value; if a match is not found, it returns null.

The deleteAllInvalidSessions method

This method deletes text files associated with sessions that have expired. When the method is called depends on your application. Because an expired session text file has to be deleted when the getSessionID method is called, the deleteAllInvalidSessions method is not critical. You can, for example, write a program that runs in the background, which gets activated once a day to delete all expired session text files. However, the easiest way is to call this method at the end of a JSP file. If your site is very busy, however, the repeated call to this method would waste CPU time. You would be wise to write a background processor that calls this method every day during off-peak hours.

The signature of this method is given below.

public void deleteAllInvalidSessions()

It first reads all session text filenames into a String array called files.

File dir = new File(path); String[] files = dir.list();

It then needs to determine whether or not a session has expired by comparing the text file's last modified time with the System's current time after being offset by timeOut. The long variable now is used to store the System's current time.

long now = System.currentTimeMillis();

It then loops through the String array files and reads each text file's lastModified property. All text files associated with expired sessions will be deleted.

for (int i=0; i<files.length; i++) {
  File f = new File(path + files[i]);
  if (f.lastModified() + timeOut > now)
    f.delete();  // delete expired session text file.
}

Using the PseudoSessionBean

After compiling the PseudoSessionBean bean, you can use pseudo sessions to manage state information for your Web application. You don't have to use the server's session tracking mechanism. You indicate this by setting the session attribute to false in your page directive.

<%@ page session="false" %>

You then use the JSP Bean tags to tell the JSP container that you want to use the PseudoSessionBean bean.

<jsp:useBean id="PseudoSessionId" scope="application" 
 class="pseudosession.PseudoSessionBean" />

In the JSP Bean tags above the class attribute has the value of package.ClassName. This, of course, will be different if you have a different package name. Note that the scope of the bean is application because we want to use the same bean throughout all pages in the application. In this application, using the application scope is the most efficient one because you only want to create the bean object once. Also, as mentioned previously, getSessionID must be invoked before anything else.

<%
  String sessionId = PseudoSessionId.getSessionID(request);
%>

To illustrate the use of the PseudoSessionBean bean, the following are two JSP pages called index.jsp and secondPage.jsp. The index.jsp page stores the user's name value in the pseudo session object and the secondPage.jsp page retrieves the value.

The index.jsp page is given here.

<%@ page session="false" %>
<jsp:useBean id="PseudoSessionId" scope="application"   class="pseudosession.PseudoSessionBean" />
<%
  String sessionId = PseudoSessionId.getSessionID(request);
%>
<html>
<head>
<title>Pseudo Sessions</title>
</head>
<body>
<h1>
Pseudo Sessions for maintaining state
</h1>
<br />
<%
  String userName = "bulbul";
  PseudoSessionId.setValue(sessionId, "userName", userName);
%>
<a href=secondPage.jsp?sessionId=<%=sessionId%>>click here</a>
<br />
<form method="post" action=anotherPage.jsp?sessionId=<%=sessionId%>>
<br />Enter new value:
<input type="text" name="sample"><br />
<input type="submit" name="Submit" value="Submit">
</form>
</body>
</html>
<%
  PseudoSessionId.deleteAllInvalidSessions();
%>

Note that the hyperlinks are rewritten to include the session identifier in all occurrences. This includes the action attribute of the <form> tag. Also notice that the deleteAllInvalidSessions method is called at the end of the page.

The secondPage.jsp page simply returns the value of the user name stored previously.

<%@ page session="false" %>
<jsp:useBean id="PseudoSessionId" scope="application"
  class="pseudosession.PseudoSessionBean" />
<%
  String sessionId = PseudoSessionId.getSessionID(request);
%>
<html>
<head>
<title>Second Page</title>
</head>
<body>
<%
  String userName = PseudoSessionId.getValue(sessionId, "userName");
  out.println("The user name is " + userName);
%>
</body>
</html>

Conclusion

This article has shown how to use pseudo sessions to maintain user state information without the drawbacks of traditional session tracking mechanism. A bean that can be used in a JSP application has been described, and JSP pages that utilize the bean have also been presented. However, the project has been kept at its simplest form for the sake of clarity. There is still much room for improvement. For example, you can build a more sophisticated random number generator for the session identifiers. Also, you can extend the setValue and getValue methods so that you can store any type of object, not only String objects.

However, for most applications, the work in this article is sufficient to guarantee that you can have a good session tracking mechanism without sacrificing scalability.

Budi Kurniawan is a senior J2EE architect and author.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.