Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Servlets, Session Tracking Using the Session Tracking API

Java Programming, Lecture Notes # 694, Revised 3/18/99.

Preface

Introduction

The HttpSession Interface

Sample Program

Interesting Code Fragments

Program Listing


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the spring semester of 1999.

This lesson was originally written on March 17, 1999.

The sample servlet in this lesson was tested using Win95, Netscape Navigator, the JDK 1.2 download package from JavaSoft, the JSDK 2.0 download package from JavaSoft, and the servletrunner program that is included in the JSDK. The sample program was tested using servletrunner both from the same machine, and from a different machine on the network.

Introduction

This is one of a series of lessons designed to show you some of the ways to implement session tracking using servlets. The purpose of the sample program in this lesson is to illustrate session tracking using the session tracking API that is part of the JSDK.

Previous lessons have shown you several ways to implement session tracking with servlets. I began by implementing session tracking using hidden fields. Then I progressed through one version of URL rewriting followed by a discussion of the use of cookies. All of the sample programs in these earlier lessons were written at a fairly low level.

Now I am going to show you how to use the session tracking API that allows you to program at a somewhat higher level. Before you get too excited, however, be aware that this may or may not be the answer to your needs. In some cases, depending on the server involved, you may find that the session tracking API relies exclusively on cookies, albeit at a very high level. If your clients don't allow cookies, then in this case the API won't solve your problems and you may find yourself back at square one -- hidden fields.

The capability that I have referred to as the session tracking API is primarily based on the HttpSession Interface that I will discuss below. It also includes some other interfaces and classes as well.

The HttpSession Interface

I will begin the discussion with the following code fragment where req is the standard object of type HttpServletRequest passed to the doGet() method when it is invoked.

HttpSession ses = req.getSession(true);

The invocation of the getSession(true) method in this statement returns an object of the interface type HttpSession.

This object is used to provide an association between an HTTP client and HTTP server. This association, or session, persists over multiple connections and/or requests during a given time period. Sessions are used to maintain state and user identity across multiple page requests.

Apparently the manner in which the association is maintained varies from one server to the next. The documentation states, "A session can be maintained either by using cookies or by URL rewriting." One of my books states that the minimum requirement for servers is to maintain the session using cookies, and that the server may optionally support URL rewriting as well. There are inferences in the documentation to the effect that some servers may not support URL rewriting.

An earlier lesson on cookies discussed the problems with knowing whether or not the client has disabled cookies. The JSDK 2.0 documentation states, "To expose whether the client supports cookies, HttpSession defines an isCookieSupportDetermined() method and an isUsingCookies() method."

This suggests that one or both of these methods can be used to determine if the client browser has disabled cookies. Unfortunately, as near as I can determine, these two methods are not supported by JSDK 2.0. At least there is no documentation for them, and my efforts to invoke them met with compiler errors.

The HttpSession object returned by the previous code fragment behaves as a container for the storage of:

The requirement to maintain the identification of each individual client is transparent to the programmer. That information is encapsulated in the HttpSession object and the process that backs it up.

An HttpSession object represents an ongoing session with a particular client. The session continues until the client exits the browser, the servlet purposely invalidates the session, or the server invalidates the session due to timeout or other condition.

All that the programmer has to do to maintain session state is to put objects into the HttpSession object, and to get objects from the HttpSession object. The programmer can also get information about the session from the HttpSession object as well.

Data objects are stored in the HttpSession object using a dictionary-like interface. Each object stored in the HttpSession object is stored under a String name. Data objects are stored and retrieved under the name associated with the object.

The fact that the data objects are actually being stored on the client machine (possibly as a cookie) is transparent to the programmer. The fact that the data objects are being stored in a cookie on the client machine can be verified by setting the browser preferences to ask permission to set each cookie. Then when the server attempts to set a cookie, the user will be notified.

By paying attention to the requests to set cookies while running the sample program in this lesson, it appears that a single cookie is used to store the entire container that includes potentially many data objects. This gets around the limitation of 20 cookies per host provided the total size of the cookie doesn't exceed the requisite 4-Kbytes.

If you instantiate your data objects from classes of your own design, you can process events which are generated whenever the object is put in or removed from the HttpSession object. In order to be able to receive events, your classes must implement the HttpSessionBindingListener interface.

When an object of a class that implements the HttpSessionBindingListener interface is put into the HttpSession object, the following method is invoked on that object:

valueBound(HttpSessionBindingEvent e)

When an object of a class that implements the HttpSessionBindingListener interface is removed from the HttpSession object, the following method is invoked on that object:

valueUnbound(HttpSessionBindingEvent e)

No special registration of the event listeners is required as is typically the case in Java programming. Simply implementing the interface on the object is sufficient to cause it to receive events when it is put into or removed from the HttpSession object. You can define these two methods to provide whatever behavior may be needed when the events occur.

An implementation of HttpSession represents the server's view of the session. The server considers a session to be new until the client has joined it. Until the client joins the session, the isNew() method returns true.

A value of true can indicate one of the following three cases:

The third case will occur if the client supports only cookies and chooses to reject any cookies sent by the server (the client has disabled cookies on the browser).

According to the documentation, if the server supports URL rewriting, this case will not commonly occur. However, according to one of my books, even if the server supports URL rewriting, that capability is not freely available. The programmer must provide special programming constructs to take advantage of URL rewriting.

Each URL exposed by servlet must be passed through one of two special encoding methods before sending URL to the client. Apparently, the encoding methods add the session ID to the URL.

It is the responsibility of the programmer to design the application to account for situations where a client has not joined a session.

Sample Program

The name of the servlet is Servlet11.

The program begins by getting a valid HttpSession object for the current request from the client. If this is the first request for a new session, the object is created.

The program instantiates a "hit counter" object and stores it in the session object. Each time the servlet is invoked, the value stored in the counter is incremented.

Each time the servlet is invoked, a new Date object containing the current date and time is instantiated and stored in the session object. Each Date object is stored under a name created by converting the current date and time in milliseconds to a String. Thus, the amount of data stored in the session object increases with each invocation of the servlet.

When the value of the hit counter is 1, an object of type MyClass is instantiated and stored in the session object. This object is removed from the session object when the value of the hit counter is 4.

This object is a listener for events of type HttpSessionBindingEvent. Therefore, it receives an event when it is put into the session object, and receives another event when it is removed from the session object. Information about the two events is displayed on the client screen when the events occur.

Each time the servlet is invoked, several pieces of information about the session are obtained from the session object and displayed on the client screen.

Also, each time the servlet is invoked, information about each of the objects stored in the session object is obtained and displayed on the client screen. During the first four hits, this consists of counter information, information about each of the Date objects, and information about the object of type MyClass. On and after the fourth hit, the MyClass object is no longer contained in the session object so only information about the counter and the Date objects is displayed.

The following box shows the screen output for the first invocation of the servlet. I highlighted three lines using boldface to separate the three types of information being displayed.

The Event output resulted from the fact that the object of type MyClass was put into the session object. This caused the valueBound() method to be called.

The Session Characteristics show that the creation time and the time last accessed are equal, and the session is a new session.

The Session Data shows information on one counter object, one Date object, and one object of type MyClass.

Event
In valueBound method
Name = MyClassObj

Session Characteristics:
New Session: true
Session ID: TNSBMZAAAAAABQFINYPQAAA
Session Context: sun.servlet.http.SessionContextImpl@1cc86c
Creation Time: Wed Mar 17 22:07:00 CST 1999
Last Accessed: Wed Mar 17 22:07:00 CST 1999

Session Data:
counter: 1
921730020980: Wed Mar 17 22:07:00 CST 1999
MyClassObj: This is a MyClass object

The next box shows the screen output for the fourth invocation of the servlet. This is the invocation where the MyClass object was removed from the session object. Hence an Event occurred showing that the valueUnbound() method was called.

The Session Characteristics show that the session is no longer new, the Session ID has not changed, and the creation time is no longer equal to the time last accessed.

The Session Data consists of one counter object and four Date objects. The MyClass object no longer appears.

Event
In valueUnbound method
Name = MyClassObj

Session Characteristics:
New Session: false
Session ID: TNSBMZAAAAAABQFINYPQAAA
Session Context: sun.servlet.http.SessionContextImpl@1cc86c
Creation Time: Wed Mar 17 22:07:00 CST 1999
Last Accessed: Wed Mar 17 22:07:50 CST 1999

Session Data:
counter: 4
921730063990: Wed Mar 17 22:07:43 CST 1999
921730070580: Wed Mar 17 22:07:50 CST 1999
921730020980: Wed Mar 17 22:07:00 CST 1999
921730068760: Wed Mar 17 22:07:48 CST 1999

The next box shows the screen output for the seventh invocation of the servlet. There is no Event output and the Session Characteristics are the same as before except for the time last accessed.

The Session Data consists of one counter object and seven Date objects.

Session Characteristics:
New Session: false
Session ID: TNSBMZAAAAAABQFINYPQAAA
Session Context: sun.servlet.http.SessionContextImpl@1cc86c
Creation Time: Wed Mar 17 22:07:00 CST 1999
Last Accessed: Wed Mar 17 22:08:30 CST 1999

Session Data:
counter: 7
921730063990: Wed Mar 17 22:07:43 CST 1999
921730070580: Wed Mar 17 22:07:50 CST 1999
921730106170: Wed Mar 17 22:08:26 CST 1999
921730020980: Wed Mar 17 22:07:00 CST 1999
921730110840: Wed Mar 17 22:08:30 CST 1999
921730108640: Wed Mar 17 22:08:28 CST 1999
921730068760: Wed Mar 17 22:07:48 CST 1999

As I mentioned earlier, my browser preferences were set to provide following notification:

"The server baldwin:8080 wishes to set a cookie that will be sent only back to itself. The name and value of cookie are …

Do you wish to allow cookie to be set?"

I received only one notification when the servlet was first invoked. Apparently after getting permission to set the cookie, the browser doesn't request permission each time the server modifies the contents of the cookie. As you can see from the above screen output, the contents of the cookie were modified each time the servlet was invoked.

 

Interesting Code Fragments

As usual, the first fragment shows the beginning of the controlling class and the beginning of the doGet() method. The fragment also sets the content type and gets the output writer.

There is one minor difference relative to the code that you have seen before. The output stream is declared as an instance variable to make it available to the methods in the event handlers. Otherwise, you have seen this code before, so I won't discuss it further.

public class Servlet11 extends HttpServlet{
  PrintWriter out;//output stream to client
  
  public void doGet(HttpServletRequest req, 
                    HttpServletResponse res)
                      throws ServletException, IOException{
                        

    res.setContentType("text/html");
    out = res.getWriter();

The next fragment gets the current valid HttpSession object associated with the client request. The true parameter causes the getSession() method to instantiate a new HttpSession object if this is the first request of a session.

Apparently the server identifies the first request of the session by the fact that the browser doesn't send a cookie (or the URL hasn't been rewritten).

To ensure that the session is properly maintained, the getSession() method must be called at least once before any output is written to the response object.

    HttpSession ses = req.getSession(true);

The next fragment creates or processes an existing hit counter object. This object is stored in the session object under the name "counter".

The counter object is instantiated during the first invocation of the servlet, initialized to a value of one, and stored in the session object.

During successive invocations of the servlet, the counter is incremented and the updated version is stored in the session object, replacing the old version that was previously stored there. Since an object of the Integer class is immutable, the only way to increment the counter is to create a new Integer object to replace the existing one.

This fragment illustrates both putValue() and getValue(), which are the two methods used to store and retrieve objects from the session object. putValue() requires two parameters, an object and the String name under which the object is being stored. getValue() requires the String name of the object to retrieve.

    Integer cnt = (Integer)ses.getValue("counter");
    if(cnt == null) cnt = new Integer(1);
    else cnt = new Integer(cnt.intValue() +1);
    ses.putValue("counter",cnt);

A new Date object containing the current date and time is instantiated and stored in the session object each time the servlet is invoked. A String representation of the current date and time in milliseconds is used as the unique name for each such Date object.

    Date theDate = new Date();
    long millis = theDate.getTime();
    String strMillis = "" + millis;
    ses.putValue(strMillis,theDate);

During the first invocation of the servlet, when the value of the hit counter is one, a new object of type MyClass is instantiated and put it in the session object under the name MyClassObj.

Because MyClass implements HttpSessionBindingListener, this object is a listener for events of type HttpSessionBindingEvent.

An HttpSessionBindingEvent event happens when the object is put into the session object and happens again when the object is removed from the session object.

The MyClass object is removed from the session object when the value of the hit counter reaches four. The handlers for the two events cause a message to be displayed on the client screen when the events occur.

    if(cnt.intValue() == 1) 
                 ses.putValue("MyClassObj", new MyClass());
    if(cnt.intValue() == 4) ses.removeValue("MyClassObj");

The next fragment contains some standard HTML boilerplate that you have seen many times before. I won't comment on it further here.

    out.println("<HTML>");
    out.println("<HEAD><TITLE>Servlet11</TITLE></HEAD>");

The next fragment uses methods of the HttpSession interface to get and display information about the session on the client screen. This is straightforward programming and shouldn't need any further explanation.

    out.println("<BODY><BR>Session Characteristics:<BR>"); 
    out.println("New Session: " + ses.isNew()+ "<BR>");
    out.println("Session ID: " + ses.getId()+ "<BR>");
    out.println("Session Context: " 
                        + ses.getSessionContext()+ "<BR>");
    out.println("Creation Time: " 
               + new Date(ses.getCreationTime()) + "<BR>");
    out.println("Last Accessed: " 
           + new Date(ses.getLastAccessedTime()) + "<BR>");

The next fragment displays information about all of the objects currently in the session object. Note that the order of retrieval and display of the objects doesn't seem to follow any particular pattern.

This code uses the getValueNames() method to create a String array containing the names of each of the objects currently stored in the session object. A for loop then iterates on that array, invoking the getValue() method on each name to get and display each of the objects stored in the session object.

    out.println("<BR>Session Data:<BR>");
    String[] names = ses.getValueNames();

    for(int i = 0; i < names.length; i++){
      out.println(names[i] + ": " 
                        + ses.getValue(names[i]) + "<BR>");
    }//end for loop

    out.println("</BODY></HTML>");
  }//end doGet()

That ends the doGet() method.

The next fragment is an inner class. It could have been implemented as a top-level class. However, I elected to make it an inner class in order to provide easy access to the output stream used to write on the client screen.

An object of this class is instantiated and put into the session object during the first invocation of the servlet. The object is removed during the fourth invocation of the servlet when the hit counter equals four.

Because the class implements the HttpSessionBindingListener interface, an object of the class is a listener for events of type HttpSessionBindingEvent. An event of this type occurs when the object is first put into the session object. Another event of this type occurs when the object is removed from the session object.

Unlike typical Java event programming, it is not necessary to register the listener on a source. Simply implementing the interface is sufficient to cause the object to be notified of the events when an object of the class is put into or removed from the session object.

When an event occurs, the event handler methods named valueBound() and valueUnbound() receive a reference to an object of type HttpSessionBindingEvent as a parameter. The event handlers invoke the methods of the event class to display information about the events on the client screen when the events occur.

In addition to the two event handler methods, this class also overrides the toString() method.

Except for the fact that there is no requirement to register the event listener object on a source, this is straightforward source-listener event handling material.

class MyClass implements HttpSessionBindingListener,
                                              Serializable{
    public String toString(){
      return "This is a MyClass object";
    }//end toString()
    //---------------------------------------------------//
    
    //This method is called when the object is put into
    // the session.
    public void valueBound(HttpSessionBindingEvent e){
      out.println("<BR>Event<BR>");
      out.println("In valueBound method<BR>");
      //Returns the name of the object as identified when
      // put into the session
      out.println("Name = " +e.getName() + "<BR>");
    }//end valueBound()
    //---------------------------------------------------//

    //This method is called when the object is removed
    // from the session.
    public void valueUnbound(HttpSessionBindingEvent e){
      out.println("<BR>Event<BR>");
      out.println("In valueUnbound method<BR>");
      out.println("Name = " +e.getName() + "<BR>");
    }//end valueUnbound()

  }//end inner class named MyClass 
  //=====================================================//  

}//end class Servlet11

The entire servlet can be viewed in the listing in the next section.

Program Listing

A complete listing of the program follows.

/*File Servlet11.java, Copyright 1999, R.G.Baldwin
Revised 3/17/99

This servlet illustrates use of the session tracking API.
A variety of different aspects of session tracking using
the API are illustrated.

Tested using JDK 1.2, JSDK 2.0, and servletrunner under
Win95.
**********************************************************/
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Servlet11 extends HttpServlet{
  PrintWriter out;//output stream to client
  
  public void doGet(HttpServletRequest req, 
                    HttpServletResponse res)
                      throws ServletException, IOException{
                        

    res.setContentType("text/html");
    out = res.getWriter();
    
    
    //Get the current valid session associated with this 
    // request, If necessary, create a new session for the
    // request. 
    //To ensure the session is properly maintained,this 
    // method must be called at least once before any 
    // output is written to the response. 
    //Returns the session associated with this request.
    HttpSession ses = req.getSession(true);
    
    //Create a hit counter for this servlet to be stored
    // in the session under the name "counter".First, 
    // get access to the counter stored in the session. 
    Integer cnt = (Integer)ses.getValue("counter");
    //If the session doesn't have a "counter", create one
    // and initialize its value to 1.
    if(cnt == null) cnt = new Integer(1);
    //If the session already has a "counter" increment it
    else cnt = new Integer(cnt.intValue() +1);
    //Put the new or incremented counter back in the 
    // session replacing the one that was previously there.
    ses.putValue("counter",cnt);
    
    //Each time the servlet in invoked, create a new Date
    // object containing the current date and time and put
    // it in the session.  Use a String representation of
    // the current date and time in milliseconds as the
    // name of each new Date object.
    Date theDate = new Date();
    long millis = theDate.getTime();
    String strMillis = "" + millis;
    ses.putValue(strMillis,theDate);
    
    //When the value of the hit counter is 1, instantiate
    // an new object of type MyClass and put it in the
    // session under the name "MyClassObj".  Because 
    // MyClass implements HttpSessionBindingListener, this
    // object will be a listener for events of type
    // HttpSessionBindingEvent.  Such an event happens
    // when the object is put into the session and happens
    // again when the object is removed from the session.
    //Remove the object from the session when the value
    // of the hit counter is 4.
    //The handlers for the two events cause a message to
    // be displayed on the client screen when the events
    // occur.
    if(cnt.intValue() == 1) 
                 ses.putValue("MyClassObj", new MyClass());
    if(cnt.intValue() == 4) ses.removeValue("MyClassObj");

    
    out.println("<HTML>");
    out.println("<HEAD><TITLE>Servlet11</TITLE></HEAD>");

    //Display information about the session.
    out.println("<BODY><BR>Session Characteristics:<BR>"); 
    out.println("New Session: " + ses.isNew()+ "<BR>");
    out.println("Session ID: " + ses.getId()+ "<BR>");
    out.println("Session Context: " 
                        + ses.getSessionContext()+ "<BR>");
    out.println("Creation Time: " 
               + new Date(ses.getCreationTime()) + "<BR>");
    out.println("Last Accessed: " 
           + new Date(ses.getLastAccessedTime()) + "<BR>");

    //Display information about all of the objects
    // currently in the session.  Note that the order of
    // the display of objects doesn't seem to follow any 
    // particular pattern.
    out.println("<BR>Session Data:<BR>");
    String[] names = ses.getValueNames();
    for(int i = 0; i < names.length; i++){
      out.println(names[i] + ": " 
                        + ses.getValue(names[i]) + "<BR>");
    }//end for loop


    //The following methods are mentioned in documentation
    // but apparently are not supported by JSDK 2.0.  It
    // appears that these methods could be used to 
    // determine if cookies are disabled on the client
    // browser if they were supported.
    //ses.isCookieSupportDetermined();
    //ses.isUsingCookies();

    out.println("</BODY></HTML>");
  }//end doGet()


  //This is an inner class.  An object of this class is
  // put into the session at the beginning and then
  // removed when the hit counter equals 4.  Because the
  // class implements the HttpSessionBindingListener
  // interface, an object of the class is a listener for
  // events of type HttpSessionBindingEvent.  An event of
  // this type occurs when the object is put into the 
  // session and another event of this type occurs when
  // the object is removed from the session.  Note that it
  // is not necessary to register the listener.  Simply
  // implementing the interface is sufficient to cause
  // the object to be notified of the events when an
  // object of the class is put into or removed from the
  // session.
  //The event handlers display a message on the client
  // screen when these events occur.
  
  class MyClass implements HttpSessionBindingListener,
                                              Serializable{
    public String toString(){
      return "This is a MyClass object";
    }//end toString()
    
    //This method is called when the object is put into
    // the session.
    public void valueBound(HttpSessionBindingEvent e){
      out.println("<BR>Event<BR>");
      out.println("In valueBound method<BR>");
      //Returns the name of the object as identified when
      // put into the session
      out.println("Name = " +e.getName() + "<BR>");
    }//end valueBound()

    //This method is called when the object is removed
    // from the session.
    public void valueUnbound(HttpSessionBindingEvent e){
      out.println("<BR>Event<BR>");
      out.println("In valueUnbound method<BR>");
      out.println("Name = " +e.getName() + "<BR>");
    }//end valueUnbound()
  }//end inner class named MyClass 
  

}//end class Servlet11

-end-