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.
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.
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.
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.
The solution to the problems posed by the cookie-based session tracking mechanism is pseudo sessions, which have the following properties.
ACTION
attributes of HTML forms.In addition, the implementation of pseudo sessions must take into account the following points.
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 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.
timeOut must be less than the current
time.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.
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.
String value is associated with a
name that already exists, the old value is replaced by the new
value.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);
|
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.
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.
}
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>
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.